web-dev-qa-db-fra.com

Comment déclarer les valeurs par défaut des variables d'instance en Python?

Dois-je donner aux membres de ma 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 n'ai besoin que de définir une valeur par défaut pour mes variables d'instance, les deux méthodes sont-elles aussi bonnes? Ou l'un d'eux plus "pythonique" que l'autre?

Une chose que j'ai remarquée est que dans le tutoriel 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».

68
int3

En prolongeant la réponse de bp, je voulais vous montrer ce qu’il entendait par types immuables.

D'abord, ça va.

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

Cependant, cela ne fonctionne que pour les types immuables (non modifiables). Si la valeur par défaut était modifiable (ce qui signifie qu’elle peut être remplacée), ceci se produirait à la place:

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

Notez que a et b ont un attribut partagé. Ceci est souvent indésirable.

C'est le moyen utilisé par Pythonic pour définir les valeurs par défaut des variables d'instance, lorsque le type est modifiable:

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

La raison pour laquelle mon premier extrait de code fonctionne, c'est parce que, avec les types immuables, Python en crée une nouvelle instance à tout moment. Si vous aviez besoin d'ajouter 1 à 1, Python crée un nouveau 2 pour vous, car l'ancien 1 ne peut pas être modifié. La raison est principalement pour le hachage, je crois.

102
Xavier Ho

Les deux extraits font des choses différentes, donc ce n’est pas une question de goût, mais plutôt de savoir quel est le bon comportement 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

Ceci lie num au Foo instances. La modification de ce champ n'est pas propagée à d'autres instances.

Ainsi:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

Pièce B

class Bar:
  num = 1

Ceci lie num à la barre class. 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

Si je n'ai pas besoin d'une variable de classe, mais que je n'ai besoin que de définir une valeur par défaut pour mes variables d'instance, les deux méthodes sont-elles aussi bonnes? Ou l'un d'eux plus "pythonique" que l'autre?

Le code de la pièce à conviction B est tout simplement faux: pourquoi voudriez-vous lier un attribut de classe (valeur par défaut lors de la création d'une instance) à une instance unique?

Le code de la pièce A est correct.

Si vous voulez donner des valeurs par défaut pour les variables d'instance dans votre constructeur, je ferais cependant ceci:

class Foo:
  def __init__(num = None):
    self.num = num if num is not None else 1

...ou même:

class Foo:
  DEFAULT_NUM = 1
  def __init__(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__(num = 1):
    self.num = num

De cette façon, vous pouvez faire:

foo1 = Foo(4)
foo2 = Foo() #use default
45
badp

Utiliser des 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, cela pourrait être 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 repoze, un framework qui s'exécute sur Zope. L'avantage ici n'est pas seulement que, lorsque votre classe est persistée dans la base de données, seuls les attributs autres que ceux par défaut doivent être enregistrés, mais également lorsque vous devez ajouter un nouveau champ au schéma, tous les objets existants voient le nouveau champ avec ses paramètres par défaut valeur sans qu'il soit nécessaire de modifier réellement les données stockées.

Je trouve que cela fonctionne également bien dans le codage plus général, mais c'est une question de style. Utilisez ce que vous êtes le plus heureux.

3
Duncan

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 modifiable, 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]
3
Max Shawabkeh

Vous pouvez également déclarer des 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 empêcher AttributeErrors . 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 dans les autres réponses concernant l’utilisation des types mutable vs immuable comme valeur par défaut dans __init__.

0
Realistic