PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Pas Hexactement

samedi 11 octobre 2014 à 12:50

Je préfère utiliser le terme francophone que français pour le public du blog. D’ailleurs, quand je retweet des offres d’emploi, je le fais pour les pays limitrophes à la France, et également le Canada ou l’Afrique du Nord et de l’Ouest.

Ce n’est pas un hasard. En effet, si on regarde les stats du blog :

France 	5,135
Belgium 	302
Canada 	130
Switzerland 	129
United States  States	105
Luxembourg 	64
Tunisia 	44
United Kingdom  Kingdom	40
Bulgaria 	39
New Caledonia	33
Algeria 	32
Réunion 	28
Morocco 	24

302 + 130 + 129 + 64 + 32 + 24 = 681 vues qui viennent de pays francophones, soit plus de 10 %. Le français est beaucoup plus parlé dans le monde qu’on ne le croit. Sans parler des expats, évidement.

Donc à tout ceux qui ne sont pas de l’hexagone, coucou !

Je suis assez épaté du chiffre Bulgare ceci dit.

L’équation du nichon

vendredi 10 octobre 2014 à 14:46

Alors que je remontais l’Ecosse en stop avec un vieil ami, nous nous sommes faits ramasser par des Suisses. On a terminé dans un village au dessus d’Inverness. Un de nos hôtes, bien pinté, nous confie de but en blanc avec son accent de Genève qu’il aime quand les femmes lui roulent la bite.

Je sais, ce n’est pas facile à expliquer sans geste. Vous voyez les brulures indiennes ? C’est le même principe, mais à la verticale, et sur sa teub.

Enrichi de cette révélation, j’ai compris à quelle point la diversité des plaisirs était vaste. Ici, parlant de la queue d’un inconnu suisse dans le décor de flashback de Highlanders, et surtout de la prise en main qu’il préférait, et qui me paraissant, à moi, parfaitement désagréable.

Malgré cela, je continue d’être surpris par la variété de manières de toucher des nibars.

Ma maman (avec laquelle je parle beaucoup de cul, allez comprendre mon évolution psychique plus tard) m’avait toujours dis qu’il fallait y aller tout doucement.

Mais sur le terrain, le manuel de formation ne sert plus à rien. Il y a celles qui aiment qu’on les caresse. Celles qui aiment qu’on les suce. Celles qui aiment qui les mordille. Les morde. Les prenne en main. Les malaxe. Les tire. Les tord. Les pince. Les giffle. Ou qu’on leur souffle doucement dessus.

Une meuf m’a sorti une fois qu’elle demandait à un copain boulanger de lui bouler les seins, comme les miches de pain, et qu’elle adorait ça.

Il y a les sensibles, et celles qui ne voient pas l’interêt. Les fragiles, et les masos. Les amoureuses de têton, et les fans de la colerette.

Et toutes s’attendent à ce qu’on sache quoi faire parce que “c’est pas bien compliqué”.

Bref, ayant hier couché avec une meuf qui adore qu’on lui tire-bouchonne les tétés, la mémoire de la précédente qui avait mal dès qu’on posait la main dessus m’est revenu en mémoire.

L’équation du nichon a trop d’inconnues pour être résolue a priori. Il faut de la data pour pouvoir en faire quelque chose. De la collecte IRL, avec une mise à jour régulière.

Aussi, en ce jour, je poste cet article solidaire de tous les hommes qui, une fois qu’ils ont retiré le soutif, doivent se lancer dans une étude empirique pour savoir quoi faire avec ces nouvelles mamelles.

Petite astuce cependant, apprenez à retirer un soutien-gorge d’une main, quitte à vous entrainer seul. Ca coûte rien, et c’est la classe.

Bien nommer ses variables en Python

jeudi 9 octobre 2014 à 11:42

There are only two hard things in Computer Science: cache invalidation and naming things.

Phil Karlton

Utiliser des bons noms est le geste de documentation le plus important d’un code. Je ne parle pas de bien formater le nom des variables. Pour ça, il y a le PEP8 et ce qu’il recommande tient en 3 lignes :

C’est tout.

Non, je parle de choisir un nom adapté et descriptif.

Le post est long, et vous savez que quand le post est long, je vous mets une musique d’attente.

Explicit is better than implicit

En Python, il n’y a pas de déclaration de type. Le nom d’une variable a donc d’autant plus d’importance pour expliquer ce qu’il y a dedans.

Prenez cet exemple :

best = []
for k, v in data.items():
    if v > top:
        best.append(k)

Quand on lit ce bout de code, on se demande :

Maintenant, avec des noms explicites :

best_players = []
for player, score in data.items():
    if score > top_score :
        best_players.append(player)

On comprend tout de suite de quoi il est question. L’algo n’a pas changé, seul le nommage a changé.

Et si on passe à une écriture plus compacte, le gain est encore plus net :

best = [k for k, v in data.items() if k > top]

VS

best_players = [player for player, score in data.items() if score > top_score]

Parfois, on veut la concision, mais faute de nommage, on doit se retourner vers les commentaires :

# Get players with the best scores
best = [k for k, v in data.items() if k > top]

Néanmoins, si on doit faire le choix entre commenter et bien nommer, le nommage doit avoir priorité. Le commentaire est important, mais c’est le dernier recours d’un code qui n’est pas explicite. Avoir un code clair doit être l’objectif premier. Ensuite, seulement, on le commente (abondamment toutefois, faut pas être radin).

Si vous avez suivi, je nomme mes variables en fonction de leur nature, pas leur type. On peut utiliser les règles de l’orthographe pour encore plus de précision, par exemple le pluriel.

fruits = ["kiwi", "banane", 'poire']
for fruit in fruits:
    print(fruit)

J’utilise le pluriel pour une liste de données, qui va donc potentiellement contenir plusieurs fruits. Mais j’utilise un singulier dans la boucle pour le fruit en court.

L’utilisation d’adjectifs est aussi le bienvenu :

fruits peut devenir, après un traitement filtered_fruits, indiquant que la liste a subit un filtrage. Les mots en “ed” en anglais aident beaucoup à la qualification.

On évite au maximum les variables courtes. Certains cas sont néanmoins tolérés. Le premier est l’utilisation de i, x, yet z pour des indices.

for i, fruit in enumerate(fruits):
    # faire un truc

Les indices sont quelque chose de tellement courant en informatique qu’on ne va pas se gaver à l’appeler “indice” à chaque fois.

Le second est dans le cadre scientifique. On a souvent des variables pour un algo, des coordonnées, des valeurs géométriques ou mathématiques, qui n’ont pas de dénomination. Dans ce cas, inutile d’essayer d’inventer une nomenclature tordue. Exemple typique, l’algo pour pondre un MD5. Mais il faut compenser par des commentaires, sinon on s’y perd.

Il ne faut pas avoir peur des noms longs. Si, j’ai un jeu de données, que je filtre plusieurs fois, il est de bon ton de distinguer les différents jeux avec des noms détaillés :

sample = range(10)
squares = [x * x for x in sample]
even_squares = [x for x in squares if x % 2 == 0]
even_squares_tail = even_squares[-3:]

Faire des noms de plus de 10 caractères n’est pas sale. On est pas sur un tableau des scores d’une borne d’arcade des années 80.

J’utilise bien entendu des noms en anglais, ce qui est toujours préférable, mais si vous devez en mettre en fr, évitez à tout prix les accents malgré la possibilité de les utiliser en Python 3.

Conventions

Il existe quelques noms qui sont toujours utilisés de la même façon en Python.

self et cls sont les plus connus, en j’en parle déjà dans le dossier sur la POO.

Il y a args et kwargs, qu’on utilise avec l’opérateur splat, dont je parle ici.

Et puis il y en a de plus discrets.

_ est utilisé pour une variable qu’on ignore. Certaines opérations, comme l’unpacking, supposent la création de plusieurs variables. Si on n’est pas intéressé par l’une d’elles, on peut le signaler. Par exemple, l’ORM django permet d’obtenir un objet, et si il n’existe pas, de le créer. Cette fonction retourne un tuble (objet, bool), l’élément indiquant si l’objet a été créé ou non. Si cette information nous intéresse :

user, created = User.objects.get_or_create(username="sam")

Si cette information ne nous intéresse pas :

user, _ = User.objects.get_or_create(username="sam")

Ainsi le lecteur saura qu’il peut ignorer cette variable quand il parcourt le code.

Il y a aussi les alias. On ne peut pas utiliser certains noms comme list ou id qui sont des fonctions existantes en Python.

On s’arrange généralement en trouvant un synonyme, mais si ce n’est pas pratique, on change une lettre. list devient lst (rappelez vous que list est un nom déjà assez pourri, nommez plutôt le contenu), class devient klass, dict devient dct, etc.

Si on ne peut pas le faire, la convention est de rajouter un underscore à la fin du nom : id devient id_, max devient max_… Mais faites l’effort, avant, de chercher un synonyme. Je vois trop souvent des from_/to alors que certains contextes permettent parfaitement de les nommer start/end ou source/destination.

A ne pas confondre avec l’underscore AVANT le nom, qui est une convention pour dire qu’une variable ne fait pas partie de l’API publique.

Ce sont des béquilles, le choix d’un nom judicieux et clair est toujours préférable, mais ce sont des béquilles utiles.

Savoir quand nommer

Au delà de donner un bon nom, il y a le fait de choisir quand il faut nommer, et quand il faut éviter de le faire.

Si seul le résultat final d’un traitement m’intéresse, alors, il vaut mieux utiliser une seule variable et mettre le nouveau résultat dedans à chaque fois :

fstab = [line.strip() for line in open('/etc/fstab') if line]
fstab = [line.lower() for line in fstab if not line.startswith('#')]
fstab = [line.split()[:3] for line in fstab]

il faut aussi savoir quand ne pas du tout créer une variable :

row = line.strip().split()
for col in row:
    # do something

ici, la variable intermédiaire est inutile :

for col in line.strip().split():
    # do something

L’inverse est aussi vrai :

for col in [int(x) for x in line.strip().split()]:
    # do something

La ligne devient beaucoup trop complexe, et ajouter une variable intermédiaire avec un bon nom va améliorer la lisibilité du programme :

numeric_col = [int(x) for x in line.strip().split()]
for col in numeric_col:
    # do something

On pourrait croire que je précise le type ici en utilisant “numeric”, mais je n’ai pas utilisé integer ou float. J’ai précisé la nature : des colonnes numériques. Il se trouve que pour des données aussi brutes, la nature se rapproche du type.

Si vous êtes du genre à utiliser des lambdas, cela s’applique aussi à vous.

Pour quelque chose de simple, une lambda inline est très lisible :

sorted(scores.items(), key=lambda score: score[1])

Mais pour quelque chose de complexe, une fonction complète est bien plus adaptée :

def calculate_rank(score):
    return sum(goals for sort, goal in score[1] if sort == 'A')
 
sorted(scores.items(), key=calculate_rank)

Plutôt qu’un horrible :

sorted(scores.items(), key=lambda x: sum(g for s, g in x[1] if s == 'A'))

Pourquoi je parle de lambda alors qu’on est sur du nommage ? Parce qu’une lambda est anonyme, alors qu’une fonction normale a un nom. Et ce nom exprime l’action de la fonction. Il documente.

Habitudes stylistiques

Ces règles là ne sont pas officielles, mais j’ai pu les constater dans nombre de bons codes.

La nom d’une fonction/méthode est aussi important, sinon plus, que le nom d’une variable. Il n’est pas rare que j’écrive des fonctions avec des noms bien dodus comme :

def get_last_downloaded_shemale_vids()

Si on oublie la docstring, on a déjà une bonne idée de ce que cette fonction fait. Cela n’empêche pas de docstringuer quand même pour annoncer des subtilités sur les types, les potentiels side effects, des choses à savoir sur le temps d’exécution, le format, les perfs, etc.

Mais il arrive souvent qu’une fonction ne fasse pas quelque chose d’aussi concret. Prenez par exemple une fonction dont le but est de sluggifier les strings d’une list.

Si la fonction transforme la liste, on va utiliser un verbe dans le nom :

slugify_items(data)

Si par contre la fonction retourne une liste avec les éléments modifiés, on va utiliser le participe passé :

data = slugified_items(data)

C’est subtil, mais la sémantique est différente. Dans le premier cas, on s’attend à un effet de bord. Dans le second cas, on s’attend à une nouvelle liste, ou comme souvent en Python, à un générateur.

Quand on a affaire à une méthode en Python, on utilise rarement le préfixe get_. Personnellement je l’utilise parfois pour des actions complexes, ou des méthodes de classe.

Mais généralement, on préférera utiliser directement le nom de ce qu’on veut récupérer. Exemple :

comments = blog_post.get_comments(spam=False) # NON
comments = blog_post.comments(spam=False) # Oui

Si on n’a pas besoin de passer de paramètre, alors une property est plus appropriée :

comments = blog_post.comments # Ouiiiiiiiiiiiiiii

J’en profite pour faire remarquer qu’il est très classe de prononcer plusieurs fois très vite “sans paramètre, une propriété est plus appropriée”.

Enfin, il arrive qu’on ait besoin de spécifier des rôles techniques et des interactions entre plusieurs bouts de code : hiérarchie, composition, dépendances, etc. Ce sont les choses les plus compliquées à comprendre quand on lit du code : voir le tableau au complet, ce qui lie les différents blocs.

Il ne faut pas hésiter à nommer ses éléments pour cela. Apprendre le nom des design patterns aide beaucoup, mais même si on n’est pas top moumoute niveau vocabulaire, on peut faire des choses aussi simple que :

class BaseAuthenticator(object):
    #...
 
class PwdAuthenticator(BaseAuthenticator):
    #...
 
class KeyAuthenticator(BaseAuthenticator):
    #...

Si vous lisez BaseAuthentificator, vous n’avez pas besoin de voir qu’elle est parente d’autres classes plus bas pour savoir que ce n’est probablement pas une classe instanciable, mais sans doute une classe interface ou une classe abstraite. De quoi se faciliter une lecture en diagonale.

FAIL

Voici quelques exemples de noms qui ratent complètement l’objectif de documentation :

 
def do_query_database():
    # ...
 
def query_database():
    # ...
    do_query_database()
    # ...

J’en croise dans le code source de Django, et ça me fait hurler. Sérieux ça veut dire quoi ? Qu’est-ce qui a été extrait ? Dans quel but ? On a plus de question APRÈS avoir lu le nom qu’avant, c’est encore pire qu’un mauvais nom, c’est un nom méchant.

Dans ce cas, il faut essayer d’expliquer au maximum ce que l’appendice – qui va me faire choper une péritonite – que vous avez mis de côté fait :

 
def excute_and_send_query():
    # ...
 
def query_database():
    # ...
    excute_and_send_query()
    # ...

Un truc également exaspérant, c’est l’usage d’un vocabulaire ambigu :

def make_best_player_list():
    # ...

On sait ce que ça fait, ça fabrique une liste des meilleurs joueurs. Le contexte nous permet d’évaluer le résultat le plus probable. Maintenant un cas beaucoup moins clair :

def make_query():
    # ...

Ca envoie la requête, ça construit la requête ou les deux ? Make est un mot qui peut vouloir dire fabriquer ou exécuter. Ici il vaut mieux utiliser un vocabulaire plus explicite comme :

def build_sql_query():
    # ...

ou

def send_db_query():
    # ...

Là on sait qui fait quoi. Quitte à faire :

def db_query():
    build_sql_query()
    send_db_query()

Et oui, diviser le travail en plusieurs sous unités bien nommées, puis les regrouper dans un bloc plus général est aussi une forme de documentation. Créer des fonctions n’est pas qu’une question de maintenance ou de perf.

Savoir bien nommer les choses vient avec de l’entraînement. Au début, il faut prendre le temps de le faire. Il faut s’arrêter, et se mettre à la place d’un autre dev qui n’a pas encore eu son café.

Allez, détendez-vous, ce blog est plein de code qui ne suit pas les conseils de cet article. Faites juste au mieux ok ?

Restauration des fonctionnalités

mercredi 8 octobre 2014 à 14:52

Après les iframes et la coloration syntaxique, le formulaire de contact, le flux rss des tweets et le planet Python fr sont de nouveau opérationnels.

Ça me fait penser que si vous connaissez des blogs fr sur Python, proposez les pour le planet.

Le compteur mal connu : l’HyperLogLog

mardi 7 octobre 2014 à 16:02

Non, ce n’est pas une faute de frappe. l’HyperLogLog est un algo qui permet de compter approximativement des éléments uniques en prenant très peu de mémoire.

Prenez par exemple un compteur d’IP uniques. En Python, on l’implémenterait en mettant les adresses IP dans un set() (puisqu’ils éliminent les doublons) et pour obtenir le nombre d’IP uniques, on ferait len() sur le set.

Une bonne stratégie, performante (les sets sont très rapides pour ce genre d’usage), simple, mais avec un défaut : la taille du set ne va cesser d’augmenter au fur et à mesure qu’on le remplit d’IP puisqu’il nous faut l’historique de toutes celles rencontrées pour éviter les doublons.

L’HyperLogLog répond à cette problématique : il tient un journal probabiliste qui va se remplir au fur et à mesure qu’on rencontre des nouveaus éléments. On peut ensuite demander au journal combien d’éléments uniques il a rencontré, et il répond avec une marge d’erreur.

Avantage : la taille en mémoire est fixe.
Désavantage : le compteur n’est pas parfaitement précis.

La précision obtenue est dépendante de la place en mémoire, par exemple si on on tolère 1% d’erreur, le journal prendra au maximum 12kb, permettant de compter jusqu’à 2^64 items.

Bref, si vous faites juste un compteur à afficher en pied de page de votre site, c’est un très bon compromis. On peut accepter d’avoir un peu plus ou un peu moins de visiteurs que la réalité qui s’affiche, sachant que la stat elle-même n’est pas vraiment réprésentative de la réalité (IP != de visiteurs uniques).

En Python, il existe une lib (uniquement 2.7 il me semble) pour ça :

>>> import hyperloglog, random
>>> hll = hyperloglog.HyperLogLog(0.01)  # on accepte une erreur de 1%
>>> hll.add("119.250.66.95")
>>> print len(hll)  
1
>>> hll.add("119.250.66.95")
>>> print len(hll)  
1
>>> hll.add("219.81.118.147")
>>> print len(hll)  
2
>>> for x in xrange(1000):
... ip = ".".join(str(random.randint(1, 255)) for x in range(4))
... print ip
... hll.add(ip)
114.208.49.91
11.72.239.16
67.56.229.66
191.62.59.163
61.104.232.43
110.58.69.141
246.123.30.234
244.246.65.219
98.93.193.114
185.143.143.69
191.177.161.213
...
>>> print len(hll) # 1000 items unique. Environ :)
1004

Vous pouvez également profiter de l’HyperLogLog via une extension PostGres ou en utilisant une version récente de Redis.

La plupart des compteurs sur les sites sont complètement bidons, alors vous, honnête que vous êtes, embrassez l’approximatif ! C’est presque la vérité. Presque.