Sam & Max: Python, Django, Git et du cul » pep8 http://sametmax.com Deux développeurs en vadrouille qui se sortent les doigts du code Wed, 05 Feb 2014 14:20:37 +0000 en hourly 1 http://wordpress.org/?v=3.3.1 Le PEP8, en résumé http://sametmax.com/le-pep8-en-resume/ http://sametmax.com/le-pep8-en-resume/#comments Thu, 26 Dec 2013 19:15:10 +0000 Sam http://sametmax.com/?p=8514 PEP8, comme ça si vous avez la flemme de le lire, au moins vous aurez l'essentiel. ]]> Internet, c’est la culture du TL;DR, donc plutôt je vais faire une petite synthèse des trucs les plus importants du PEP8, comme ça si vous avez la flemme de le lire, au moins vous aurez l’essentiel.

Ce texte liste les règles stylistiques recommandées, invitant toute la communauté Python à écrire un code de la même façon.

Je vais également y ajouter des éléments de style qui ne sont pas dedans, mais que j’ai pu constater comme étant les choix les plus courants dans les sources que j’ai pu lire.

Espaces

Les opérateurs doivent être entourés d’espaces.

Il faut faire ceci :

variable = 'valeur'
 
ceci == cela
 
1 + 2

Et non:

variable='valeur'
 
ceci==cela
 
1+2

Il y a deux exceptions notables.

La première étant qu’on groupe les opérateurs mathématiques ayant la priorité la plus haute pour distinguer les groupes :

a = x*2 - 1
b = x*x + y*y
c = (a+b) * (a-b)

La seconde est le signe = dans la déclaration d’arguments et le passage de paramètres :

def fonction(arg='valeur'): #  ça c'est ok
    pass
 
resultat = fonction(arg='valeur') #  ça aussi

On ne met pas d’espace à l’intérieur des parenthèses, crochets ou accolades.

Oui :

2 * (3 + 4)
 
def fonction(arg='valeur'):
 
{str(x): x for x in range(10)}
 
val = dico['key']

Non :

2 * ( 3 + 4 )
 
def fonction( arg='valeur' ):
 
{ str(x): x for x in range(10) }
 
val = dico[ 'key' ]

On ne met pas d’espace avant les deux points et les virgules, mais après oui.

Oui :

def fonction(arg1='valeur', arg2=None):
 
dico = {'a': 1}

Non :

def fonction(arg='valeur' , arg2=None) :
 
dico = {'a' : 1}

Lignes

Une ligne doit se limiter à 79 charactères. Cette limite, héritée des écrans touts petits, est toujours en vigeur car il est plus facile de scanner un code sur une courte colonne qu’en faisant des aller-retours constant.

Si une ligne est trop longue, il existe plusieurs manières de la racourcir :

foo = la_chose_au_nom_si_long_quelle_ne_tient_pas_sur(
          une, 
          carte, 
          de, 
          munchkin)

Ici l’indentation entre le nom de la fonction et des paramètres est légèrement différente pour mettre en avant la distinction.

Une variante :

foo = la_chose_au_nom_si_long_quelle(ne, tient, pas, sur, carte 
                                     une, de, munchkin)

Si c’est un appel chainé, on peut utiliser \ pour mettre à la ligne :

queryset = ModelDjangoALaNoix.objects\
                             .filter(banzai=True)\
                             .exclude(chawarma=False)

Si c’est une structure de données, on peut se la jouer langage fonctionel de la vibes du flex :

chiffres = [
    1, 2, 3,
    4, 5, 6,
]
 
contacts = {
    'Cleo': (),
    'Ramses': (
        ('maman', '0248163264'),
        ('papa', '01234567890'),
        ('mamie', '55555555'),
        ('momie', '066642424269')
    )
}

Séparer les fonctions et les classes à la racine d’un module par 2 lignes vides. Les méthodes par 1 ligne vide. Parfois je triche et je fais 3 et 2 au lieu de 2 et 1.

Les imports de plusieurs modules doivent être sur plusieurs lignes :

import sys
import os

Et non :

import sys, os

Bien sûr, ce n’est pas valable pour from x import z.

Souvenez-vous qu’on peut utiliser les parenthèses pour diviser de longues lignes. Par exemple :

from minibelt import (dmerge, get, iget, normalize, 
                      chunks, window, skip_duplicates, flatten)

Idem pour les chaînes très longues :

s = ("Les chaînes Python sont automatiquement"
     "concaténées par la VM si elles sont "
     "uniquement séparées par des espaces "
     "ou sauts de lignes.")

Pour en revenir aux lignes d’import, on doit les ordonner ainsi :

  • Import de module > import du contenu du module
  • Import de la lib standard > import de libs tierces parties > import de votre projet

Exemple :

import os  # import module de la lib standard
import sys # on groupe car même type 
 
from itertools import islice  # import du contenu du module
from collections on namedtuple # import groupe car même type
 
import requests # import lib tierce partie
import arrow # on groupe car même type
 
from django.conf import settings # tierce partie, contenu du module
from django.shortcuts import redirect # on groupe car même type
 
from mon_projet.mon_module import ma_bite # mon projet

Format du fichier

Indentation : 4 espaces. Pas de tab. C’est tout. Ce n’est pas du PEP8, c’est juste que les codes qui utilisent les tabs sont en (très très très très très très très très) grande minorité dans la communauté Python. Donc faites pas chier. Sinon on vous attend à la sortie de l’école. Tous.

Encoding : UTF8 ou ASCII (ce qui est de l’UTF8 de toute façon).

Docstrings

(c.f. PEP257)

On utilise toujours des triples quotes :

def fonction_avec_docstring_courte():
    """Résumé en une ligne."""
    pass

Si la docstring est longue (elle peut être très très très longue si vous le souhaitez) :

def fonction():
    """Résumé en une ligne suivi d'une line vide.
 
    Description longue de la fonction qui 
    se termine par une ligne vide puis une
    triple quotes sur sa propre ligne.
 
    """

Noms de variables

Lettres seules, en minuscule : pour les boucles et les indices.

Exemple :

for x in range(10):
    print(x)
 
i = get_index() + 12
print(ma_liste[i])

Lettres minuscules + underscores : pour les modules, variables, fonctions et méthodes.

une_variable = 10
 
def une_fonction():
    return locals() or {}
 
class UneClasse:
    def une_methode_comme_une_autre(self):
        return globals()

Lettres majuscules + underscores : pour les (pseudo) constantes.

MAX_SIZE = 100000  # à mettre après les imports

Camel case : nom de classe.

class CeciEstUneClasse:
    def methodiquement(self):
        pass

Si le nom contient un acronyme, on fait une entorse à la règle :

class HTMLParserCQFDDDTCCMB:
    def methodiquement(self):
        pass

On n’utilise PAS le mixedCase.

flattr this!

]]>
http://sametmax.com/le-pep8-en-resume/feed/ 12
Obfuscating Python http://sametmax.com/obfuscating-python/ http://sametmax.com/obfuscating-python/#comments Fri, 05 Apr 2013 13:55:53 +0000 Sam http://sametmax.com/?p=4381 Python est un langage qu’il est difficile de rendre illisible. Difficile, mais pas impossible.

D’abord, les techniques habituelles de substitutions de lettres par leurs codes ASCII sont toujours valables. Le fameux easter egg import this en fait d’ailleurs usage; si vous ouvrez le fichier dont le chemin est contenu dans this.__file__, vous trouverez :

s = """Gur Mra bs Clguba, ol Gvz Crgref
 
Ornhgvshy vf orggre guna htyl.
Rkcyvpvg vf orggre guna vzcyvpvg.
Fvzcyr vf orggre guna pbzcyrk.
Pbzcyrk vf orggre guna pbzcyvpngrq.
Syng vf orggre guna arfgrq.
Fcnefr vf orggre guna qrafr.
Ernqnovyvgl pbhagf.
Fcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf.
Nygubhtu cenpgvpnyvgl orngf chevgl.
Reebef fubhyq arire cnff fvyragyl.
Hayrff rkcyvpvgyl fvyraprq.
Va gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff.
Gurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg.
Nygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu.
Abj vf orggre guna arire.
Nygubhtu arire vf bsgra orggre guna *evtug* abj.
Vs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn.
Vs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn.
Anzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!"""
 
d = {}
for c in (65, 97):
    for i in range(26):
        d[chr(i+c)] = chr((i+13) % 26 + c)
 
print "".join([d.get(c, c) for c in s])

Ensuite, il y a le fait qu’il est possible de spécifier (en Python 2.7, mais plus Python 3), l’encoding du module en ROT13, base64 ou même zip. Au lieux de mettre # -*- coding: utf8 -*-, vous pouvez faire :

# -*- coding: rot13 -*-
 
cevag h'Fnz rg Znk, cnepr dhr obver frhy rfg zbvaf qebyr dhr pbqre n cyhfvrhef'

Mais au dela de ces petites astuces, il y a bien entendu la véritable offuscation, celle qui utilise des imbrications d’instructions capilotractées avec de multiples niveaux de nesting dans des onliners qui s’étendent sur des kilomètres en incluant des noms de variables alambiqués le tout organisé dans des structures syntaxiques volontairement obscures en détournant des capacités du langage pour en faire une indigeste bouillie immonde dont la lecture provoquera des saignement durant la phrase de vomi. Comme cette phrase.

Généralement ça passe par un usage massif des features que Guido déteste comme les lambdas ou les ;.

Il y a les classiques onliners à base de map / reduce, par exemples les 1000 premiers nombres … premiers :

print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))

Ensuite il y a ceux qui aiment se la jouer “mes variables ont des noms de code Fortran” :

print (lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24)

Ce qui va afficher cette belle composition de Mandelbrot (si votre terminal fait 80 colonnes de large) :

Mandelbrot dans un terminal

La génération procédurale : ou comment faire de Zolies choZes avec 1ko de code.

D’ailleurs, pour l’offuscation, on adore les trucs de maths parce que ça donne l’air intelligent et compliqué, et Mandelbrot est un truc très prisé puisqu’avec peu de lignes on peut outputer du Dali Période Guimauve automatiquement :

_                                      =   (
                                        255,
                                      lambda
                               V       ,B,c
                             :c   and Y(V*V+B,B,  c
                               -1)if(abs(V)<6)else
               (              2+c-4*abs(V)**-0.4)/i
                 )  ;v,      x=1500,1000;C=range(v*x
                  );import  struct;P=struct.pack;M,\
            j  ='<QIIHHHH',open('M.bmp','wb').write
for X in j('BM'+P(M,v*x*3+26,26,12,v,x,1,24))or C:
            i  ,Y=_;j(P('BBB',*(lambda T:(T*80+T**9
                  *i-950*T  **99,T*70-880*T**18+701*
                 T  **9     ,T*i**(1-T**45*2)))(sum(
               [              Y(0,(A%3/3.+X%v+(X/v+
                               A/3/3.-x/2)/1j)*2.5
                             /x   -2.7,i)**2 for  \
                               A       in C
                                      [:9]])
                                        /9)
                                       )   )

Notez cette fois que le code, en plus d’être particulièrement imbuvable, a été gentiment indenté pour ressembler à la figure qu’il va lui même pondre dans le fichier M.bmp que voici :

Mais bien entendu il y a des gens qui arrivent à faire des trucs moches sans entrainement. Par exemple des codeurs JS qui vous font ça:

if "longue chaine".find('chaine') != -1:

Au lieu de :

if 'chaine' in 'longue chaine':

Ou alors les programmeurs C qui font :

for x in range(0, len(ma_list)):
    print ma_list[x]

Alors qu’on a :

for x in ma_list:
    print x

Et au besoin:

for i, x in enumerate(ma_list):
    print i, x

Et à peu près la moitié des programmeurs d’autres langages qui font :

if truc == True:
    if not len(ma_list):

Alors que:

if truc:
     if not ma_list:

Marche très bien.

Je vous passe les isintance() que nous font les programmeurs Java, les check au lieu des gestions des exceptions et les gens qui écrivent les variables en camelCase. Des gens très compétents font du code horrible.

Certains trouvent que le créateur du langage a été un nazi pour avoir forcé les gens à utiliser l’indentation et les sauts de ligne ou pour avoir limité les lambdas. Quand je lis le code de certains, j’ai tendance au contraire à trouver la politique actuelle proche de l’anarchisme hippie. Interdisons les tabs. Les “;” plus de 3 fois d’affilé. Les nombres de saut de ligne qui ne sont pas standard face au PEP8. En fait faisons des syntax errors sur le PEP8. Et les syntaxes errors déclencheront des chocs électriques via le clavier.

Et on enverra tous ceux qui sont pas d’accord dans des camps d’entraînement pour qu’ils puissent tous se concentrer un peu plus sur leur code.

Sinon à quoi ça sert d’être un BDFL ?

flattr this!

]]>
http://sametmax.com/obfuscating-python/feed/ 16
TypeError: Error when calling the metaclass bases function() argument 1 must be code, not str http://sametmax.com/typeerror-error-when-calling-the-metaclass-bases-function-argument-1-must-be-code-not-str/ http://sametmax.com/typeerror-error-when-calling-the-metaclass-bases-function-argument-1-must-be-code-not-str/#comments Wed, 16 Jan 2013 12:16:23 +0000 Sam http://sametmax.com/?p=4150 Cette erreur est souvent déclenchée quand on essaye d’hériter d’une fonction au lieu d’une classe. Cela peut arriver par erreur avec des fonctions qui sont nommées en CamelCase, en dépit du PEP8.

Par exemple:

class Truc(threading.Condition):
    pass
 
class Machine(tempfile.NamedTemporaryFile):
    pass

Lèveront l’exception :

TypeError: Error when calling the metaclass bases
    function() argument 1 must be code, not str

Car:

>>> import threading, tempfile
>>> type(threading.Condition)
<type 'function'>
>>> type(tempfile.NamedTemporaryFile)
<type 'function'>

Malgré leurs noms en majuscule.

Le message d’erreur est lui-même complètement obscure. Bref, le genre de truc qui est 100% lié à des erreurs d’autres personnes que vous aller payer par une après-midi de debug si on ne vous donne pas la solution.

Mais bon, la queue de celui qui n’a jamais pourri l’après-midi d’un autre codeur avec son travail merdique jette le premier parpaing.

flattr this!

]]>
http://sametmax.com/typeerror-error-when-calling-the-metaclass-bases-function-argument-1-must-be-code-not-str/feed/ 6