Python 2.x pièges et mines

Le but de ma question est de renforcer ma base de connaissances avec Python et de la mieux comprendre, notamment en connaissant ses défauts et ses sursockets. Pour garder les choses spécifiques, je ne suis intéressé que par l’interpréteur CPython.

Je suis à la recherche de quelque chose de similaire à ce que ma question des mines antipersonnel m’a appris, à savoir que certaines des réponses me sont bien connues, mais que certaines étaient horribles.

Mise à jour: Apparemment, une ou deux personnes sont contrariées d’avoir posé une question qui avait déjà été partiellement résolue en dehors de Stack Overflow. Comme une sorte de compromis, voici l’URL http://www.ferg.org/projects/python_gotchas.html

Notez qu’une ou deux réponses ici sont déjà originales de ce qui a été écrit sur le site référencé ci-dessus.

Les expressions dans les arguments par défaut sont calculées lorsque la fonction est définie, pas quand elle est appelée.

Exemple: considérez la valeur par défaut d’un argument à l’heure actuelle:

>>>import time >>> def report(when=time.time()): ... print when ... >>> report() 1210294387.19 >>> time.sleep(5) >>> report() 1210294387.19 

L’argument when ne change pas. Il est évalué lorsque vous définissez la fonction. Cela ne changera pas jusqu’à ce que l’application soit redémarrée.

Stratégie: vous ne trébucherez pas sur cette option si vous atsortingbuez par défaut des arguments à None , puis faites quelque chose d’utile lorsque vous le voyez:

 >>> def report(when=None): ... if when is None: ... when = time.time() ... print when ... >>> report() 1210294762.29 >>> time.sleep(5) >>> report() 1210294772.23 

Exercice: pour vous assurer que vous avez compris: pourquoi cela se passe-t-il?

 >>> def spam(eggs=[]): ... eggs.append("spam") ... return eggs ... >>> spam() ['spam'] >>> spam() ['spam', 'spam'] >>> spam() ['spam', 'spam', 'spam'] >>> spam() ['spam', 'spam', 'spam', 'spam'] 

Vous devez savoir comment les variables de classe sont gérées dans Python. Considérons la hiérarchie de classes suivante:

 class AAA(object): x = 1 class BBB(AAA): pass class CCC(AAA): pass 

Maintenant, vérifiez le résultat du code suivant:

 >>> print AAA.x, BBB.x, CCC.x 1 1 1 >>> BBB.x = 2 >>> print AAA.x, BBB.x, CCC.x 1 2 1 >>> AAA.x = 3 >>> print AAA.x, BBB.x, CCC.x 3 2 3 

Surpris? Vous ne le serez pas si vous vous rappelez que les variables de classe sont traitées en interne en tant que dictionnaires d’un object de classe. Pour les opérations de lecture , si un nom de variable ne figure pas dans le dictionnaire de la classe en cours, les classes parentes sont recherchées. Donc, le code suivant à nouveau, mais avec des explications:

 # AAA: {'x': 1}, BBB: {}, CCC: {} >>> print AAA.x, BBB.x, CCC.x 1 1 1 >>> BBB.x = 2 # AAA: {'x': 1}, BBB: {'x': 2}, CCC: {} >>> print AAA.x, BBB.x, CCC.x 1 2 1 >>> AAA.x = 3 # AAA: {'x': 3}, BBB: {'x': 2}, CCC: {} >>> print AAA.x, BBB.x, CCC.x 3 2 3 

Il en va de même pour la gestion des variables de classe dans les instances de classe (traiter cet exemple comme une continuation de celui ci-dessus):

 >>> a = AAA() # a: {}, AAA: {'x': 3} >>> print ax, AAA.x 3 3 >>> ax = 4 # a: {'x': 4}, AAA: {'x': 3} >>> print ax, AAA.x 4 3 

Boucles et lambdas (ou toute fermeture, vraiment): les variables sont liées par leur nom

 funcs = [] for x in range(5): funcs.append(lambda: x) [f() for f in funcs] # output: # 4 4 4 4 4 

Un travail de contournement consiste à créer une fonction distincte ou à passer les arguments par nom:

 funcs = [] for x in range(5): funcs.append(lambda x=x: x) [f() for f in funcs] # output: # 0 1 2 3 4 

La liaison dynamic rend les fautes de frappe dans vos noms de variables étonnamment difficiles à trouver. Il est facile de passer une demi-heure à réparer un bug sortingvial.

EDIT: un exemple …

 for item in some_list: ... # lots of code ... # more code for tiem in some_other_list: process(item) # oops! 

L’une des plus grandes sursockets que j’ai eu avec Python est celle-ci:

 a = ([42],) a[0] += [43, 44] 

Cela fonctionne comme on pourrait s’y attendre, sauf pour lever une erreur TypeError après la mise à jour de la première entrée du tuple! Donc, a volonté sera ([42, 43, 44],) après l’exécution de l’instruction += , mais il y aura une exception quand même. Si vous essayez cela d’autre part

 a = ([42],) b = a[0] b += [43, 44] 

vous n’obtiendrez pas d’erreur.

 try: int("z") except IndexError, ValueError: pass 

La raison en est que cela ne fonctionne pas, car IndexError est le type d’exception que vous attrapez et ValueError est le nom de la variable à laquelle vous atsortingbuez l’exception.

Le code correct pour intercepter plusieurs exceptions est:

 try: int("z") except (IndexError, ValueError): pass 

Il y a eu beaucoup de discussions sur les fonctionnalités du langage caché à un moment donné: hidden-features-of-python . Où certains pièges ont été mentionnés (et certains des bons trucs aussi).

Aussi, vous pourriez vouloir vérifier Python Warts .

Mais pour moi, la division entière est un gotcha:

 >>> 5/2 2 

Vous avez probablement voulu:

 >>> 5*1.0/2 2.5 

Si vous voulez vraiment ce comportement, vous devriez écrire:

 >>> 5//2 2 

Comme cela fonctionnera aussi avec des flottants (et cela fonctionnera quand vous finirez par aller à Python 3 ):

 >>> 5*1.0//2 2.0 

GvR explique comment la division en nombres entiers a fonctionné comme elle le fait dans l’histoire de Python .

Le découpage de liste m’a causé beaucoup de chagrin. Je considère en fait le comportement suivant comme un bogue.

Définir une liste x

 >>> x = [10, 20, 30, 40, 50] 

Index d’access 2:

 >>> x[2] 30 

Comme prévu

Tranchez la liste de l’index 2 et à la fin de la liste:

 >>> x[2:] [30, 40, 50] 

Comme prévu

Indice d’access 7:

 >>> x[7] Traceback (most recent call last): File "", line 1, in  IndexError: list index out of range 

Encore une fois, comme prévu.

Cependant , essayez de découper la liste de l’index 7 jusqu’à la fin de la liste:

 >>> x[7:] [] 

???

Le remède est de mettre beaucoup de tests lors de l’utilisation du découpage de liste. Je voudrais juste avoir une erreur à la place. Beaucoup plus facile à déboguer.

Non compris un __init__ .py dans vos paquets. Celui-ci m’obtient encore parfois.

Le seul piège / surprise que j’ai rencontré concerne le GIL de CPython. Si pour quelque raison que ce soit vous vous attendez à ce que les threads python de CPython s’exécutent simultanément … eh bien, ils ne le sont pas et ceci est assez bien documenté par la foule Python et même Guido lui-même.

Une explication longue mais approfondie du threading CPython et de certaines des choses qui se passent sous le capot et pourquoi la véritable concurrence avec CPython n’est pas possible. http://jessenoller.com/2009/02/01/python-threads-and-the-global-interpreter-lock/

James Dumay m’a rappelé avec éloquence un autre Python gotcha:

Les «stacks incluses» de Python ne sont pas toutes merveilleuses .

L’exemple spécifique de James était les bibliothèques HTTP: httplib , urllib , urllib2 , urlparse , mimetools et ftplib . Certaines fonctionnalités sont dupliquées et certaines fonctionnalités que vous attendez sont totalement absentes, par exemple la gestion de la redirection. Franchement, c’est horrible.

Si je dois récupérer quelque chose via HTTP ces jours-ci, j’utilise le module urlgrabber issu du projet Yum.

Les flotteurs ne sont pas imprimés avec une précision maximale par défaut (sans repr ):

 x = 1.0 / 3 y = 0.333333333333 print x #: 0.333333333333 print y #: 0.333333333333 print x == y #: False 

repr trop de chiffres:

 print repr(x) #: 0.33333333333333331 print repr(y) #: 0.33333333333300003 print x == 0.3333333333333333 #: True 

Mélanger involontairement des classes oldstyle et newstyle peut provoquer des erreurs apparemment mystérieuses.

Supposons que vous ayez une hiérarchie de classes simple composée de la super-classe A et de la sous-classe B. Lorsque B est instancié, le constructeur de A doit être appelé en premier. Le code ci-dessous le fait correctement:

 class A(object): def __init__(self): self.a = 1 class B(A): def __init__(self): super(B, self).__init__() self.b = 1 b = B() 

Mais si vous oubliez de faire de A une classe newstyle et définissez-la comme suit:

 class A: def __init__(self): self.a = 1 

vous obtenez cette traceback:

 Traceback (most recent call last): File "AB.py", line 11, in  b = B() File "AB.py", line 7, in __init__ super(B, self).__init__() TypeError: super() argument 1 must be type, not classobj 

Deux autres questions relatives à cette question sont 489269 et 770134

 def f(): x += 1 x = 42 f() 

entraîne une UnboundLocalError , car les noms locaux sont détectés de manière statique. Un exemple différent serait

 def f(): print x x = 43 x = 42 f() 

Vous ne pouvez pas utiliser locals () [‘x’] = quoi que ce soit pour modifier les valeurs des variables locales comme vous vous en doutez.

 This works: >>> x = 1 >>> x 1 >>> locals()['x'] = 2 >>> x 2 BUT: >>> def test(): ... x = 1 ... print x ... locals()['x'] = 2 ... print x # *** prints 1, not 2 *** ... >>> test() 1 1 

Cela m’a réellement brûlé dans une réponse ici sur SO, puisque je l’avais testé en dehors d’une fonction et obtenu le changement que je voulais. Par la suite, je l’ai trouvé mentionné et contrasté avec le cas des globals () dans “Dive Into Python”. Voir exemple 8.12. (Bien qu’il ne soit pas noté que le changement via les locaux () fonctionnera au niveau supérieur, comme je le montre ci-dessus.)

Répétez la liste avec des listes nestedes

Cela m’a surpris aujourd’hui et j’ai perdu une heure de mon temps à déboguer:

 >>> x = [[]]*5 >>> x[0].append(0) # Expect x equals [[0], [], [], [], []] >>> x [[0], [0], [0], [0], [0]] # Oh dear 

Explication: Problème de liste Python

Utiliser des variables de classe lorsque vous voulez des variables d’instance. La plupart du temps, cela ne pose pas de problème, mais si c’est une valeur modifiable, cela provoque des sursockets.

 class Foo(object): x = {} 

Mais:

 >>> f1 = Foo() >>> f2 = Foo() >>> f1.x['a'] = 'b' >>> f2.x {'a': 'b'} 

Vous voulez presque toujours des variables d’instance, ce qui vous oblige à assigner dans __init__ :

 class Foo(object): def __init__(self): self.x = {} 

Python 2 a un comportement surprenant avec les comparaisons:

 >>> print x 0 >>> print y 1 >>> x < y False 

Que se passe-t-il? repr() à la rescousse:

 >>> print "x: %r, y: %r" % (x, y) x: '0', y: 1 

x += [...] n’est pas la même chose que x = x + [...] lorsque x est une liste`

 >>> x = y = [1,2,3] >>> x = x + [4] >>> x == y False >>> x = y = [1,2,3] >>> x += [4] >>> x == y True 

On crée une nouvelle liste tandis que l’autre modifie en place

Si vous affectez une variable dans une fonction, Python suppose que la variable est définie dans cette fonction:

 >>> x = 1 >>> def increase_x(): ... x += 1 ... >>> increase_x() Traceback (most recent call last): File "", line 1, in  File "", line 2, in increase_x UnboundLocalError: local variable 'x' referenced before assignment 

Utilisez global x (ou nonlocal x dans Python 3) pour déclarer que vous souhaitez définir une variable définie en dehors de votre fonction.

Les valeurs de range(end_val) ne sont pas seulement ssortingctement inférieures à end_val , mais ssortingctement inférieures à int(end_val) . Pour qu’un argument float puisse être range , cela peut être un résultat inattendu:

 from future.builtins import range list(range(2.89)) [0, 1] 

En raison de la «véracité», cela a du sens:

 >>>bool(1) True 

mais vous ne vous attendez peut-être pas à ce qu’il en soit autrement:

 >>>float(True) 1.0 

Cela peut être un piège si vous convertissez des chaînes en nombres et que vos données ont des valeurs True / False.