156 votes

Constructeur Python et valeur par défaut

D'une manière ou d'une autre, dans la classe Node ci-dessous, la variable wordList et la variable adjacencyList sont partagées entre toutes les instances de Node.

>>> class Node:
...     def __init__(self, wordList = [], adjacencyList = []):
...         self.wordList = wordList
...         self.adjacencyList = adjacencyList
... 
>>> a = Node()
>>> b = Node()
>>> a.wordList.append("hahaha")
>>> b.wordList
['hahaha']
>>> b.adjacencyList.append("hoho")
>>> a.adjacencyList
['hoho']

Existe-t-il un moyen de continuer à utiliser la valeur par défaut (une liste vide dans ce cas) pour les paramètres du constructeur mais de faire en sorte que a et b aient leurs propres variables wordList et adjacencyList ?

Je utilise python 3.1.2.

178voto

Michael J. Barber Points 12837

Les arguments par défaut modifiables ne font généralement pas ce que vous voulez. Essayez plutôt ceci :

class Node:
     def __init__(self, wordList=None, adjacencyList=None):
        if wordList is None:
            self.wordList = []
        else:
             self.wordList = wordList 
        if adjacencyList is None:
            self.adjacencyList = []
        else:
             self.adjacencyList = adjacencyList

40 votes

Ces lignes peuvent aussi être compactes : self.wordList = wordList if wordList is not None else [], ou, légèrement moins sécurisées, self.wordList = wordList or [].

2 votes

Ceci est considéré comme la manière Pythonique, mais je préfère la manière de krousey parce que "les cas spéciaux ne sont pas suffisamment spéciaux".

1 votes

@JoshBleecherSnyder Je ne pouvais pas mettre le doigt dessus, qu'est-ce qui rend ce dernier moins sûr que le premier?

36voto

aaronasterling Points 25749

Illustrons ce qui se passe ici :

Python 3.1.2 (r312:79147, Sep 27 2010, 09:45:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo:
...     def __init__(self, x=[]):
...         x.append(1)
... 
>>> Foo.__init__.__defaults__
([],)
>>> f = Foo()
>>> Foo.__init__.__defaults__
([1],)
>>> f2 = Foo()
>>> Foo.__init__.__defaults__
([1, 1],)

Vous pouvez voir que les arguments par défaut sont stockés dans un tuple qui est un attribut de la fonction en question. Cela n'a en fait rien à voir avec la classe en question et s'applique à n'importe quelle fonction. En python 2, l'attribut sera func.func_defaults.

Comme l'ont souligné d'autres intervenants, vous devriez probablement utiliser None comme valeur sentinelle et donner à chaque instance sa propre liste.

24voto

Shankar Cabus Points 1788
class Noeud:
    def __init__(self, listeMots=None, listeAdjacence=None):
        self.listeMots = listeMots or []
        self.listeAdjacence = listeAdjacence or []

18voto

Kris R. Points 788

Je verrais :

self.wordList = list(wordList)

pour le forcer à faire une copie au lieu de référencer le même objet.

4voto

A--- Points 85

La contribution de Michael J. Barber est correcte - mais aucune explication n'est offerte. La raison de ce comportement est que l'argument par défaut est lié à la définition de la fonction, pas à l'exécution.

Voir "Le moindre étonnement" en Python : quel est la portée de l'argument par défaut mutable ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X