Pièges communs¶
Pour la plus grande partie, Python vise à être un langage propre et cohérent qui permet d’éviter des surprises. Cependant, il y a quelques cas qui peuvent être sources de confusion pour les nouveaux arrivants.
Certains de ces cas sont intentionnels, mais peuvent être potentiellement surprenants. Certains pourraient sans doute être considérés comme des verrues du langage. En général, ce qui suit est une collection de comportements potentiellement délicats qui pourraient sembler étranges à première vue, mais qui sont généralement raisonnables une fois que vous êtes au courant de la cause sous-jacente de cette surprise.
Arguments par défaut mutables¶
Apparemment la surprisela plus commune que les nouveaux programmeurs Python rencontre est le traitement Python des arguments par défaut mutables dans les définitions de fonction.
Ce que vous écrivez¶
defappend_to(element,to=[]):to.append(element)returnto
Qu’est-ce que vous auriez pu attendre qu’il se passe¶
my_list=append_to(12)printmy_listmy_other_list=append_to(42)printmy_other_list
Une nouvelle liste est créée chaque fois que la fonction est appelée si un second argument n’est pas fourni, de sorte que la sortie est:
[12][42]
Ce qui se passe¶
[12][12, 42]
Une nouvelle liste est crééeune seule fois quand la fonction est définie, et la même liste est utilisée dans chaque appel successif.
Les arguments par défaut de Python sont évaluésune seule fois lorsque la fonction est définie, pas chaque fois que la fonction est appelée (comme c’est le cas, disons en Ruby). Cela signifie que si vous utilisez un argument par défaut mutable et le mutez, vousaurez muté l’argument ici et pour tous les futurs appels à la fonction aussi.
Ce que vous devriez faire à la place¶
Créez un nouvel objet à chaque fois que la fonction est appelée, en utilisant un argument par défaut pour signaler que aucun argument n’a été fourni (None
est souvent un bon choix).
defappend_to(element,to=None):iftoisNone:to=[]to.append(element)returnto
Quand le piège n’est pas un piège¶
Parfois, vous pouvez spécifiquement “exploiter” (lisez: utilisé comme prévu) ce comportement pour maintenir l’état entre les appels d’une fonction. C’est souvent fait lors de l’écriture d’une fonction de mise en cache.
Closures des bindings tardives¶
Une autre source de confusion est la manière dont Python bind ses variables dans les closures (ou dans la portée globale entourante)
Ce que vous écrivez¶
defcreate_multipliers():return[lambdax:i*xforiinrange(5)]
Qu’est-ce que vous auriez pu attendre qu’il se passe¶
formultiplierincreate_multipliers():printmultiplier(2)
Une liste contenant cinq fonctions qui ont chacun leur propre variablei
fermée sur elle-même qui multiplie leur argument, produisant:
02468
Ce qui se passe¶
88888
Cinq fonctions sont créées; au lieu que toutes ne multiplient justex
par 4.
Les closures de Python sont deslate binding. Cela signifie que les valeurs des variables utilisées dans les closures sont regardées au moment où la fonction interne est appelée.
Ici, chaque fois quen’importe lesquelles des fonctions retournées sont appelées, la valeur dei
est recherché dans la portée environnante au moment de l’appel. D’ici là, la boucle est terminée eti
est laissé à sa valeur finale de 4.
Ce qui est particulièrement déplaisant sur ce piège est la désinformation apparemment répandue que cela a quelque chose à voir avec leslambdas en Python. Les fonctions créées avec une expressionlambda
ne sont en aucune façon particulière, et en fait le même comportement est exposé en utilisant simplement un ordinairedef
:
defcreate_multipliers():multipliers=[]foriinrange(5):defmultiplier(x):returni*xmultipliers.append(multiplier)returnmultipliers
Ce que vous devriez faire à la place¶
La solution la plus générale est sans doute une forme de hack. En raison du comportement de Python déjà mentionné concernant l’évaluation des arguments par défaut aux fonctions (voirArguments par défaut mutables), vous pouvez créer une closure qui se bind immédiatement à ses arguments en utilisant un argument par défaut comme ceci:
defcreate_multipliers():return[lambdax,i=i:i*xforiinrange(5)]
Alternativement, vous pouvez utiliser la fonction functools.partial:
fromfunctoolsimportpartialfromoperatorimportmuldefcreate_multipliers():return[partial(mul,i)foriinrange(5)]
Quand le piège n’est pas un piège¶
Parfois, vous voulez que vos closures se comportent de cette façon. Les late binding sont biens dans beaucoup de situations. Boucler pour créer des fonctions uniques est malheureusement un cas où ils peuvent causer le hoquet.
Fichiers Bytecode (.pyc) partout!¶
Par défaut, lors de l’exécution du code Python à partir de fichiers, l’interpréteur Python va automatiquement écrire une version bytecode de ce fichier sur le disque, par exemplemodule.pyc
.
Ces fichiers.pyc
ne doivent pas être versionnés dans vos dépôts de code source.
Théoriquement, ce comportement est activé par défaut, pour des raisons de performance. Sans ces fichiers bytecode présents, Python regénérerait le bytecode chaque fois que le fichier est chargé.
Désactiver les fichiers Bytecode (.pyc)¶
Heureusement, le processus de génération du bytecode est extrêmement rapide, et n’est pas quelque chose dont vous avez besoin de vous soucier quand vous développez votre code.
Ces fichiers sont ennuyeux, débarrassons-nous d’eux!
$ export PYTHONDONTWRITEBYTECODE=1
Avec la variable d’environnement$PYTHONDONTWRITEBYTECODE
définie, Python n’écrira pas plus longtemps ces fichiers sur le disque, et votre environnement de développement restera agréable et propre.
Je recommande la définition de cette variable d’environnement dans votre~/.profile
.
Enlever les fichiers Bytecode (.pyc)¶
Voici une astuce sympathique pour enlever tous ces fichiers, s’ils existent déjà:
$ find . -name "*.pyc" -delete
Exécutez ceci depuis le répertoire racine de votre projet, et tous les fichiers.pyc
vont soudainement disparaître. Beaucoup mieux.