Dois-je donner à mes membres de classe des valeurs par défaut comme ceci:
class Foo:
num = 1
ou comme ça?
class Foo:
def __init__(self):
self.num = 1
Dans cette question, j'ai découvert que dans les deux cas,
bar = Foo()
bar.num += 1
est une opération bien définie.
Je comprends que la première méthode me donnera une variable de classe alors que la seconde ne le fera pas. Cependant, si je n'ai pas besoin d'une variable de classe, mais que je dois uniquement définir une valeur par défaut pour mes variables d'instance, les deux méthodes sont-elles également bonnes? Ou l'un d'eux plus «pythonique» que l'autre?
Une chose que j'ai remarquée est que dans le didacticiel Django, ils utilisent la deuxième méthode pour déclarer des modèles. Personnellement, je pense que la deuxième méthode est plus élégante, mais j'aimerais savoir quelle est la méthode «standard».
self.attr = attr or []
dans la__init__
méthode. Il obtient le même résultat (je pense) et reste clair et lisible.Les deux extraits font des choses différentes, donc ce n'est pas une question de goût mais une question de comportement correct dans votre contexte. La documentation Python explique la différence, mais voici quelques exemples:
Pièce A
class Foo: def __init__(self): self.num = 1
Cela se lie
num
aux instances Foo . La modification de ce champ n'est pas propagée aux autres instances.Donc:
>>> foo1 = Foo() >>> foo2 = Foo() >>> foo1.num = 2 >>> foo2.num 1
Pièce B
class Bar: num = 1
Cela se lie
num
à la classe Bar . Les changements sont propagés!>>> bar1 = Bar() >>> bar2 = Bar() >>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation >>> bar2.num 1 >>> Bar.num = 3 >>> bar2.num 3 >>> bar1.num 2 >>> bar1.__class__.num 3
Réponse réelle
Le code de la pièce B est tout simplement faux pour ceci: pourquoi voudriez-vous lier un attribut de classe (valeur par défaut lors de la création de l'instance) à l'instance unique?
Le code de la pièce A est correct.
Si vous voulez donner des valeurs par défaut pour des variables d'instance dans votre constructeur, je ferais cependant ceci:
class Foo: def __init__(self, num = None): self.num = num if num is not None else 1
...ou même:
class Foo: DEFAULT_NUM = 1 def __init__(self, num = None): self.num = num if num is not None else DEFAULT_NUM
... ou même: (préférable, mais si et seulement si vous avez affaire à des types immuables!)
class Foo: def __init__(self, num = 1): self.num = num
De cette façon, vous pouvez faire:
foo1 = Foo(4) foo2 = Foo() #use default
la source
def __init__(self, num = None)
L'utilisation de membres de classe pour donner des valeurs par défaut fonctionne très bien tant que vous faites attention à ne le faire qu'avec des valeurs immuables. Si vous essayez de le faire avec une liste ou un dict, ce serait assez mortel. Cela fonctionne également lorsque l'attribut d'instance est une référence à une classe tant que la valeur par défaut est None.
J'ai vu cette technique utilisée avec beaucoup de succès dans le repoze qui est un framework qui fonctionne au-dessus de Zope. L'avantage ici n'est pas seulement que lorsque votre classe est conservée dans la base de données, seuls les attributs non par défaut doivent être enregistrés, mais également lorsque vous devez ajouter un nouveau champ dans le schéma, tous les objets existants voient le nouveau champ avec sa valeur par défaut value sans qu'il soit nécessaire de modifier réellement les données stockées.
Je trouve que cela fonctionne aussi bien dans le codage plus général, mais c'est une question de style. Utilisez ce qui vous plaît le plus.
la source
Utiliser des membres de classe pour les valeurs par défaut des variables d'instance n'est pas une bonne idée, et c'est la première fois que je vois cette idée mentionnée du tout. Cela fonctionne dans votre exemple, mais cela peut échouer dans de nombreux cas. Par exemple, si la valeur est mutable, sa mutation sur une instance non modifiée modifiera la valeur par défaut:
>>> class c: ... l = [] ... >>> x = c() >>> y = c() >>> x.l [] >>> y.l [] >>> x.l.append(10) >>> y.l [10] >>> c.l [10]
la source
[]
été cloné là-bas;x.l
ety.l
sont à la fois de mauvais noms et des valeurs immédiates différentes liées au même référent. (Termes de l'avocat de langueVous pouvez également déclarer les variables de classe comme Aucune, ce qui empêchera la propagation. Ceci est utile lorsque vous avez besoin d'une classe bien définie et que vous souhaitez éviter les erreurs d'attribut. Par exemple:
>>> class TestClass(object): ... t = None ... >>> test = TestClass() >>> test.t >>> test2 = TestClass() >>> test.t = 'test' >>> test.t 'test' >>> test2.t >>>
Aussi si vous avez besoin de valeurs par défaut:
>>> class TestClassDefaults(object): ... t = None ... def __init__(self, t=None): ... self.t = t ... >>> test = TestClassDefaults() >>> test.t >>> test2 = TestClassDefaults([]) >>> test2.t [] >>> test.t >>>
Bien sûr, suivez toujours les informations des autres réponses sur l'utilisation des types mutables vs immuables par défaut dans
__init__
.la source
Avec les classes de données , une fonctionnalité ajoutée dans Python 3.7, il existe maintenant un autre moyen (assez pratique) de définir des valeurs par défaut sur les instances de classe. Le décorateur
dataclass
générera automatiquement quelques méthodes sur votre classe, comme le constructeur. Comme le note la documentation liée ci-dessus, "[l] es variables membres à utiliser dans ces méthodes générées sont définies à l'aide d' annotations de type PEP 526 ".En prenant l'exemple d'OP, nous pourrions l'implémenter comme ceci:
from dataclasses import dataclass @dataclass class Foo: num: int = 0
Lors de la construction d'un objet du type de cette classe, nous pourrions éventuellement écraser la valeur.
print('Default val: {}'.format(Foo())) # Default val: Foo(num=0) print('Custom val: {}'.format(Foo(num=5))) # Custom val: Foo(num=5)
la source