portaldacalheta.pt
  • Principal
  • Rise Of Remote
  • Outils Et Tutoriels
  • Équipes Distribuées
  • Mode De Vie
La Technologie

Buggy Python Code: les 10 erreurs les plus courantes commises par les développeurs Python



À propos de Python

Python est un langage de programmation de haut niveau interprété, orienté objet, avec une sémantique dynamique. Ses structures de données intégrées de haut niveau, associées à un typage dynamique et à une liaison dynamique, le rendent très attractif pour Développement rapide d'applications , ainsi que pour une utilisation en tant que langage de script ou de collage pour connecter des composants ou des services existants. Python prend en charge les modules et les packages, encourageant ainsi la modularité du programme et la réutilisation du code.

À propos de cet article

La syntaxe simple et facile à apprendre de Python peut induire en erreur Développeurs Python - en particulier ceux qui sont plus récents dans la langue - à manquer certaines de ses subtilités et à sous-estimer la puissance du langage Python diversifié .



Dans cet esprit, cet article présente une liste des 10 'top 10' des erreurs quelque peu subtiles et plus difficiles à détecter qui peuvent mordre encore plus développeurs Python avancés à l'arrière.



(Remarque: cet article est destiné à un public plus avancé que Erreurs courantes des programmeurs Python , qui s'adresse davantage à ceux qui sont plus récents dans la langue.)



Erreur courante n ° 1: mauvaise utilisation des expressions par défaut pour les arguments de fonction

Python vous permet de spécifier qu'un argument de fonction est optionnel en fournissant un valeur par défaut pour ça. Bien qu'il s'agisse d'une fonctionnalité intéressante du langage, cela peut prêter à confusion lorsque la valeur par défaut est mutable . Par exemple, considérez cette définition de fonction Python:

internet des objets maison intelligente
>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified ... bar.append('baz') # but this line could be problematic, as we'll see... ... return bar

Une erreur courante est de penser que l'argument optionnel sera défini sur l'expression par défaut spécifiée chaque fois la fonction est appelée sans fournir de valeur pour l'argument optionnel. Dans le code ci-dessus, par exemple, on pourrait s'attendre à ce que l'appel de foo() à plusieurs reprises (c'est-à-dire sans spécifier d'argument bar) retournerait toujours 'baz', puisque l'hypothèse serait que chaque fois foo() est appelé (sans argument bar spécifié) bar est défini sur [] (c'est-à-dire une nouvelle liste vide).



Mais regardons ce qui se passe réellement lorsque vous faites cela:

>>> foo() ['baz'] >>> foo() ['baz', 'baz'] >>> foo() ['baz', 'baz', 'baz']

Hein? Pourquoi a-t-il continué à ajouter la valeur par défaut de 'baz' à un existant liste à chaque fois foo() a été appelé, plutôt que de créer un Nouveau liste à chaque fois?



La réponse de programmation Python la plus avancée est que la valeur par défaut d'un argument de fonction n'est évaluée qu'une seule fois, au moment où la fonction est définie. Ainsi, le bar L'argument est initialisé à sa valeur par défaut (c'est-à-dire une liste vide) uniquement lorsque foo() est d'abord défini, mais appelle ensuite foo() (c'est-à-dire sans un argument bar spécifié) continuera à utiliser la même liste que celle dans laquelle bar a été initialisé à l'origine.

Pour info, une solution de contournement courante pour cela est la suivante:



>>> def foo(bar=None): ... if bar is None: # or if not bar: ... bar = [] ... bar.append('baz') ... return bar ... >>> foo() ['baz'] >>> foo() ['baz'] >>> foo() ['baz']

Erreur courante n ° 2: utilisation incorrecte des variables de classe

Prenons l'exemple suivant:

>>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... pass ... >>> print A.x, B.x, C.x 1 1 1

Logique.



>>> B.x = 2 >>> print A.x, B.x, C.x 1 2 1

Ouais, encore comme prévu.

>>> A.x = 3 >>> print A.x, B.x, C.x 3 2 3

Qu'est-ce que $% #! & ?? Nous avons seulement changé A.x. Pourquoi C.x changer aussi?



En Python, les variables de classe sont gérées en interne comme des dictionnaires et suivent ce que l'on appelle souvent Ordre de résolution de méthode (MRO) . Donc dans le code ci-dessus, puisque l'attribut x n'est pas trouvé dans la classe C, il sera recherché dans ses classes de base (uniquement A dans l'exemple ci-dessus, bien que Python supporte plusieurs héritages). En d'autres termes, C n’a pas le sien x propriété, indépendante de A. Ainsi, les références à C.x sont en fait des références à A.x. Cela pose un problème Python à moins qu'il ne soit géré correctement. En savoir plus sur attributs de classe en Python .

Erreur courante n ° 3: spécification incorrecte des paramètres d'un bloc d'exception

Supposons que vous ayez le code suivant:

>>> try: ... l = ['a', 'b'] ... int(l[2]) ... except ValueError, IndexError: # To catch both exceptions, right? ... pass ... Traceback (most recent call last): File '', line 3, in IndexError: list index out of range

Le problème ici est que le except déclaration fait ne pas prendre une liste d'exceptions spécifiées de cette manière. Au contraire, dans Python 2.x, la syntaxe except Exception, e est utilisé pour lier l'exception au optionnel deuxième paramètre spécifié (dans ce cas e), afin de le rendre disponible pour une inspection plus approfondie. Par conséquent, dans le code ci-dessus, le IndexError l'exception est ne pas être attrapé par le except déclaration; au lieu de cela, l'exception finit par être liée à un paramètre nommé IndexError.

La bonne façon d'intercepter plusieurs exceptions dans un except instruction est de spécifier le premier paramètre comme un tuple contenant toutes les exceptions à intercepter. De plus, pour une portabilité maximale, utilisez le as mot-clé, puisque cette syntaxe est prise en charge à la fois par Python 2 et Python 3:

>>> try: ... l = ['a', 'b'] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>

Erreur commune n ° 4: mauvaise compréhension des règles de portée de Python

La résolution de l'étendue Python est basée sur ce que l'on appelle le LEGB règle, qui est un raccourci pour L ocal, EST nclosing, g lobal, B uilt-in. Cela semble assez simple, non? Eh bien, en fait, il y a quelques subtilités dans la façon dont cela fonctionne en Python, ce qui nous amène au problème de programmation Python plus avancé ci-dessous. Considérer ce qui suit:

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

Quel est le problème?

L'erreur ci-dessus se produit car, lorsque vous faites un affectation à une variable dans une portée, cette variable est automatiquement considérée par Python comme étant locale à cette portée et masque toute variable de nom similaire dans n'importe quelle portée externe.

Beaucoup sont ainsi surpris d'obtenir un UnboundLocalError dans un code de travail antérieur lorsqu'il est modifié en ajoutant une instruction d'affectation quelque part dans le corps d'une fonction. (Vous pouvez en savoir plus à ce sujet Ici .)

Il est particulièrement courant que cela fasse trébucher les développeurs lors de l'utilisation listes . Prenons l'exemple suivant:

>>> lst = [1, 2, 3] >>> def foo1(): ... lst.append(5) # This works ok... ... >>> foo1() >>> lst [1, 2, 3, 5] >>> lst = [1, 2, 3] >>> def foo2(): ... lst += [5] # ... but this bombs! ... >>> foo2() Traceback (most recent call last): File '', line 1, in File '', line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment

Hein? Pourquoi foo2 bombe tandis que foo1 a bien fonctionné?

La réponse est la même que dans l'exemple de problème précédent, mais elle est certes plus subtile. foo1 ne fait pas un affectation à lst, alors que foo2 est. En se souvenant que lst += [5] est en fait un raccourci pour lst = lst + [5], nous voyons que nous essayons de attribuer une valeur à lst (donc présumé par Python comme étant dans la portée locale). Cependant, la valeur que nous cherchons à attribuer à lst est basé sur lst lui-même (encore une fois, maintenant supposé être dans le cadre local), qui n'a pas encore été défini. Boom.

Erreur courante n ° 5: modifier une liste tout en l'itérant

Le problème avec le code suivant devrait être assez évident:

>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i] # BAD: Deleting item from a list while iterating over it ... Traceback (most recent call last): File '', line 2, in IndexError: list index out of range

La suppression d'un élément d'une liste ou d'un tableau lors de son itération est un problème Python bien connu de tout développeur de logiciel expérimenté. Mais si l'exemple ci-dessus peut être assez évident, même les développeurs avancés peuvent être involontairement mordus par cela dans un code beaucoup plus complexe.

Heureusement, Python intègre un certain nombre de paradigmes de programmation élégants qui, lorsqu'ils sont utilisés correctement, peuvent entraîner un code considérablement simplifié et rationalisé. Un autre avantage de ceci est qu'un code plus simple est moins susceptible d'être mordu par le bogue de suppression accidentelle d'un élément de liste lors de son itération. Un de ces paradigmes est celui de compréhension de liste . De plus, les compréhensions de listes sont particulièrement utiles pour éviter ce problème spécifique, comme le montre cette implémentation alternative du code ci-dessus qui fonctionne parfaitement:

>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all >>> numbers [0, 2, 4, 6, 8]

Erreur commune n ° 6: confusion sur la façon dont Python lie les variables dans les fermetures

Considérant l'exemple suivant:

>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...

Vous pouvez vous attendre à la sortie suivante:

0 2 4 6 8

Mais vous obtenez en fait:

comment faire un framboise pi
8 8 8 8 8

Surprise!

Cela est dû à Python liaison tardive comportement qui dit que les valeurs des variables utilisées dans les fermetures sont recherchées au moment où la fonction interne est appelée. Ainsi, dans le code ci-dessus, chaque fois qu'une des fonctions renvoyées est appelée, la valeur de i est recherché dans la portée environnante au moment où il est appelé (et d'ici là, la boucle est terminée, donc i a déjà reçu sa valeur finale de 4).

La solution à ce problème courant de Python est un peu un hack:

>>> def create_multipliers(): ... return [lambda x, i=i : i * x for i in range(5)] ... >>> for multiplier in create_multipliers(): ... print multiplier(2) ... 0 2 4 6 8

Voilà! Nous profitons ici des arguments par défaut pour générer des fonctions anonymes afin d'obtenir le comportement souhaité. Certains appelleraient cela élégant. Certains appelleraient cela subtile. Certains détestent ça. Mais si vous êtes un développeur Python, il est important de comprendre dans tous les cas.

Erreur courante n ° 7: création de dépendances de modules circulaires

Supposons que vous ayez deux fichiers, a.py et b.py, dont chacun importe l'autre, comme suit:

Dans a.py:

import b def f(): return b.x print f()

Et dans b.py:

import a x = 1 def g(): print a.f()

Tout d'abord, essayons d'importer a.py:

>>> import a 1

A bien fonctionné. Cela vous surprend peut-être. Après tout, nous avons ici une importation circulaire qui devrait vraisemblablement poser problème, n'est-ce pas?

La réponse est que le simple présence d'un import circulaire n'est pas en soi un problème en Python. Si un module a déjà été importé, Python est suffisamment intelligent pour ne pas essayer de le réimporter. Cependant, selon le point auquel chaque module tente d'accéder aux fonctions ou aux variables définies dans l'autre, vous pouvez en effet rencontrer des problèmes.

Donc, pour revenir à notre exemple, lorsque nous avons importé a.py, nous n'avons eu aucun problème à importer b.py, puisque b.py ne nécessite rien de a.py à définir au moment de l'importation . La seule référence dans b.py à a est l'appel à a.f(). Mais cet appel est dans g() et rien dans a.py ou b.py invoque g(). Donc la vie est belle.

nommer l'outil de conception qui peut être utilisé pour établir une hiérarchie spatiale du contenu

Mais que se passe-t-il si nous tentons d'importer b.py (sans avoir préalablement importé a.py, c'est-à-dire):

>>> import b Traceback (most recent call last): File '', line 1, in File 'b.py', line 1, in import a File 'a.py', line 6, in print f() File 'a.py', line 4, in f return b.x AttributeError: 'module' object has no attribute 'x'

Oh-oh. Ce n'est pas bon! Le problème ici est que, lors du processus d'importation de b.py, il tente d'importer a.py, qui à son tour appelle f(), qui tente d'accéder à b.x. Mais b.x n'a pas encore été défini. D'où le AttributeError exception.

Au moins une solution à cela est assez triviale. Modifiez simplement b.py importer a.py dans g():

x = 1 def g(): import a # This will be evaluated only when g() is called print a.f()

Non quand on l'importe, tout va bien:

>>> import b >>> b.g() 1 # Printed a first time since module 'a' calls 'print f()' at the end 1 # Printed a second time, this one is our call to 'g'

Erreur commune n ° 8: conflit de noms avec les modules de la bibliothèque standard Python

L'une des beautés de Python est la richesse des modules de bibliothèque qu'il est livré avec «prêt à l'emploi». Mais par conséquent, si vous ne l'évitez pas consciemment, il n'est pas si difficile de rencontrer un conflit de nom entre le nom de l'un de vos modules et un module du même nom dans la bibliothèque standard livrée avec Python (par exemple , vous pourriez avoir un module nommé email.py dans votre code, qui serait en conflit avec le module de bibliothèque standard du même nom).

Cela peut entraîner des problèmes épineux, tels que l'importation d'une autre bibliothèque qui à son tour essaie d'importer la version de la bibliothèque standard Python d'un module mais, comme vous avez un module avec le même nom, l'autre package importe par erreur votre version au lieu de celle dans la bibliothèque standard Python. C'est là que les mauvaises erreurs Python se produisent.

Il faut donc veiller à ne pas utiliser les mêmes noms que ceux des modules de la bibliothèque standard Python. Il est beaucoup plus facile pour vous de modifier le nom d’un module dans votre package que de déposer un fichier Proposition d'amélioration de Python (PEP) pour demander un changement de nom en amont et pour essayer de le faire approuver.

Erreur commune n ° 9: ne pas résoudre les différences entre Python 2 et Python 3

Considérez le fichier suivant foo.py:

import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e) bad()

Sur Python 2, cela fonctionne bien:

$ python foo.py 1 key error 1 $ python foo.py 2 value error 2

Mais maintenant, faisons un tourbillon sur Python 3:

$ python3 foo.py 1 key error Traceback (most recent call last): File 'foo.py', line 19, in bad() File 'foo.py', line 17, in bad print(e) UnboundLocalError: local variable 'e' referenced before assignment

Que vient-il de se passer ici? Le «problème» est que, dans Python 3, l'objet d'exception n'est pas accessible au-delà de la portée de except bloquer. (La raison en est que, sinon, il conserverait un cycle de référence avec le cadre de pile en mémoire jusqu'à ce que le ramasse-miettes s'exécute et purge les références de la mémoire. Plus de détails techniques à ce sujet sont disponibles Ici ).

Une façon d'éviter ce problème consiste à conserver une référence à l'objet d'exception à l'extérieur la portée du except bloquer pour qu'il reste accessible. Voici une version de l'exemple précédent qui utilise cette technique, produisant ainsi un code compatible avec Python 2 et Python 3:

import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def good(): exception = None try: bar(int(sys.argv[1])) except KeyError as e: exception = e print('key error') except ValueError as e: exception = e print('value error') print(exception) good()

Exécuter ceci sur Py3k:

$ python3 foo.py 1 key error 1 $ python3 foo.py 2 value error 2

Hourra!

combien de polices y a-t-il

(Incidemment, notre Guide de recrutement Python traite d'un certain nombre d'autres différences importantes à prendre en compte lors de la migration de code de Python 2 vers Python 3.)

Erreur commune n ° 10: mauvaise utilisation du __del__ méthode

Supposons que vous ayez ceci dans un fichier appelé mod.py:

import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle)

Et vous avez ensuite essayé de le faire à partir de another_mod.py:

import mod mybar = mod.Bar()

Vous auriez un moche AttributeError exception.

Pourquoi? Parce que, comme indiqué Ici , lorsque l’interpréteur s’arrête, les variables globales du module sont toutes définies sur None. En conséquence, dans l'exemple ci-dessus, au point où __del__ est invoquée, le nom foo a déjà été défini sur None.

Une solution à ce problème de programmation Python un peu plus avancé serait d'utiliser atexit.register() au lieu. De cette façon, lorsque votre programme a fini de s'exécuter (en quittant normalement, c'est-à-dire), vos gestionnaires enregistrés sont lancés avant l'interprète est arrêté.

Avec cette compréhension, un correctif pour ce qui précède mod.py le code pourrait alors ressembler à ceci:

import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle)

Cette implémentation fournit un moyen propre et fiable d'appeler toute fonctionnalité de nettoyage nécessaire à l'arrêt normal du programme. De toute évidence, cela dépend de foo.cleanup pour décider quoi faire de l'objet lié au nom self.myhandle, mais vous voyez l'idée.

Emballer

Python est un langage puissant et flexible avec de nombreux mécanismes et paradigmes qui peuvent grandement améliorer la productivité. Comme pour tout outil logiciel ou langage, cependant, avoir une compréhension ou une appréciation limitée de ses capacités peut parfois être plus un obstacle qu'un avantage, laissant une personne dans l'état proverbial de «savoir suffisamment pour être dangereux».

Se familiariser avec les nuances clés de Python, telles que (mais sans s'y limiter) les problèmes de programmation modérément avancés soulevés dans cet article, aidera à optimiser l'utilisation du langage tout en évitant certaines de ses erreurs les plus courantes.

Vous voudrez peut-être également consulter notre Guide d'initiés sur les entretiens Python pour des suggestions sur les questions d'entrevue qui peuvent aider à identifier les experts Python.

Nous espérons que vous avez trouvé les conseils de cet article utiles et apprécions vos commentaires.

Conception pour la lisibilité - Un guide de typographie Web (avec infographie)

Conception De L'interface Utilisateur

Conception pour la lisibilité - Un guide de typographie Web (avec infographie)
Encourager les opportunités et l'action dans un espace de travail distant

Encourager les opportunités et l'action dans un espace de travail distant

Mode De Vie

Articles Populaires
Création d'une API REST Node.js / TypeScript, partie 2: modèles, middleware et services
Création d'une API REST Node.js / TypeScript, partie 2: modèles, middleware et services
Un didacticiel sur la radio définie par logiciel: images de la Station spatiale internationale et écoute de jambons avec un RTL-SDR
Un didacticiel sur la radio définie par logiciel: images de la Station spatiale internationale et écoute de jambons avec un RTL-SDR
Les drones commerciaux révolutionnent les opérations commerciales
Les drones commerciaux révolutionnent les opérations commerciales
Faire des affaires dans l'Union européenne
Faire des affaires dans l'Union européenne
AI vs BI: différences et synergies
AI vs BI: différences et synergies
 
Stratège de contenu produit
Stratège de contenu produit
Risque vs récompense: un guide pour comprendre les conteneurs logiciels
Risque vs récompense: un guide pour comprendre les conteneurs logiciels
Explorer SMACSS: architecture évolutive et modulaire pour CSS
Explorer SMACSS: architecture évolutive et modulaire pour CSS
Si vous n'utilisez pas de données UX, ce n'est pas de la conception UX
Si vous n'utilisez pas de données UX, ce n'est pas de la conception UX
Simplification de l'utilisation des API RESTful et de la persistance des données sur iOS avec Mantle et Realm
Simplification de l'utilisation des API RESTful et de la persistance des données sur iOS avec Mantle et Realm
Articles Populaires
  • comment rédiger un document de conception technique
  • plan d'examen d'architecte de solutions certifié aws
  • comment se connecter en python
  • la règle de la gestalt de pragnanz suggère que le système visuel
  • examen d'associé d'architecte de solution d'aws
Catégories
  • Rise Of Remote
  • Outils Et Tutoriels
  • Équipes Distribuées
  • Mode De Vie
  • © 2022 | Tous Les Droits Sont Réservés

    portaldacalheta.pt