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?
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. "
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.
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
# ...
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.
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'
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.