web-dev-qa-db-fra.com

Est-ce une bonne pratique de déclarer des variables d'instance comme Aucune dans une classe en Python?

Considérez la classe suivante:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

Mes collègues ont tendance à le définir comme ceci:

class Person:
    name = None
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

La raison principale en est que leur éditeur de choix affiche les propriétés d'auto-complétion.

Personnellement, je n'aime pas ce dernier, car cela n'a aucun sens qu'une classe ait ces propriétés définies sur None.

Laquelle serait une meilleure pratique et pour quelles raisons?

74
Remco Haszing

J'appelle cette dernière mauvaise pratique la règle "cela ne fait pas ce que vous pensez".

La position de votre collègue peut être réécrite comme suit: "Je vais créer un tas de variables quasi-globales statiques de classe qui ne sont jamais accessibles, mais qui prennent de la place dans les différentes tables d'espace de noms de la classe (__dict__), juste pour que mon IDE fasse quelque chose. "

72
StarWeaver

1. Rendez votre code facile à comprendre

Le code est lu beaucoup plus souvent qu'écrit. Facilitez la tâche de votre mainteneur de code (il se peut que ce soit vous-même l'année prochaine).

Je ne connais pas de règles strictes, mais je préfère pour que tout futur état d'instance soit clairement déclaré. Plantage avec un AttributeError est déjà assez mauvais. Ne pas voir clairement le cycle de vie d'un attribut d'instance est pire. La quantité de gymnastique mentale requise pour restaurer les séquences d'appels possibles qui conduisent à l'attribution de l'attribut peut facilement devenir non triviale, entraînant des erreurs.

Donc, généralement, je définis non seulement tout dans le constructeur, mais je m'efforce également de garder le nombre d'attributs mutables au minimum.

2. Ne mélangez pas les membres au niveau de la classe et au niveau de l'instance

Tout ce que vous définissez directement dans la déclaration class appartient à la classe et est partagé par toutes les instances de la classe. Par exemple. lorsque vous définissez une fonction à l'intérieur d'une classe, elle devient une méthode qui est la même pour toutes les instances. Il en va de même pour les membres de données. Ceci est totalement différent des attributs d'instance que vous définissez habituellement dans __init__.

Les membres de données au niveau de la classe sont les plus utiles en tant que constantes:

class Missile(object):
  MAX_SPEED = 100  # all missiles accelerate up to this speed
  ACCELERATION = 5  # rate of acceleration per game frame

  def move(self):
    self.speed += self.ACCELERATION
    if self.speed > self.MAX_SPEED:
      self.speed = self.MAX_SPEED
    # ...
27
9000

Personnellement, je définis les membres dans la méthode __ init __ (). Je n'ai jamais pensé à les définir dans la partie classe. Mais ce que je fais toujours: j'initie tous les membres de la méthode __ init__, même ceux qui ne sont pas nécessaires dans la méthode __ init__.

Exemple:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
        self._selected = None

   def setSelected(self, value):
        self._selected = value

Je pense qu'il est important de définir tous les membres au même endroit. Cela rend le code plus lisible. Que ce soit à l'intérieur de __ init __ () ou à l'extérieur, ce n'est pas si important. Mais il est important pour une équipe de s'engager plus ou moins dans le même style de codage.

Oh, et vous remarquerez peut-être que j'ajoute le préfixe "_" aux variables membres.

19
this.myself

C'est une mauvaise pratique. Vous n'avez pas besoin de ces valeurs, elles encombrent le code et peuvent provoquer des erreurs.

Considérer:

>>> class WithNone:
...   x = None
...   y = None
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> class InitOnly:
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> wn = WithNone(1,2)
>>> wn.x
1
>>> WithNone.x #Note that it returns none, no error
>>> io = InitOnly(1,2)
>>> InitOnly.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: class InitOnly has no attribute 'x'
11
Daenyth

J'irai avec "un peu comme docstrings, alors" et je déclarerai cela inoffensif, tant qu'il est toujours None, ou une plage étroite d'autres valeurs, toutes immuables.

Il puait l'atavisme et l'attachement excessif aux langues typées statiquement. Et cela ne sert à rien de code. Mais il a un but mineur qui reste, dans la documentation.

Il documente les noms attendus, donc si je combine du code avec quelqu'un et que l'un de nous a `` nom d'utilisateur '' et l'autre nom d'utilisateur, il y a un indice pour les humains que nous nous sommes séparés et n'utilisons pas les mêmes variables.

Forcer l'initialisation complète en tant que stratégie réalise la même chose d'une manière plus Pythonic, mais s'il y a du code réel dans le __init__, cela fournit un endroit plus clair pour documenter les variables utilisées.

Évidemment, le gros problème ici est qu'il incite les gens à s'initialiser avec des valeurs autres que None, ce qui peut être mauvais:

class X:
    v = {}
x = X()
x.v[1] = 2

laisse une trace globale et ne crée pas d'instance pour x.

Mais c'est plus une bizarrerie dans Python dans son ensemble que dans cette pratique, et nous devrions déjà être paranoïaques à ce sujet.

0
Jon Jay Obermark