163

Somehow, in the Node class below, the wordList and adjacencyList variable is shared between all instances of 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']

Is there any way I can keep using the default value (empty list in this case) for the constructor parameters but to get both a and b to have their own wordList and adjacencyList variables?

I am using python 3.1.2.

Alec
  • 8,529
  • 8
  • 37
  • 63
Hery
  • 7,443
  • 9
  • 36
  • 41

4 Answers4

187

Mutable default arguments don't generally do what you want. Instead, try this:

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 
Michael J. Barber
  • 24,518
  • 9
  • 68
  • 88
  • 40
    These can also be one-liners: `self.wordList = wordList if wordList is not None else []`, or, slightly less safe, `self.wordList = wordList or []`. – Josh Bleecher Snyder Jan 30 '11 at 08:13
  • 2
    This is considered the Pythonic way, but I prefer krousey's way because "special cases aren't special enough". – Karl Knechtel Jan 30 '11 at 09:44
  • 1
    @JoshBleecherSnyder I couldn't put my finger on it, what makes the latter less safe than the former? – markdsievers Oct 01 '12 at 22:26
  • 9
    @markdsievers many things other than `None` evaluate to `False` (e.g. `False`, `0`, `""`, `{}`, `()`, objects that specify `__nonzero__()`). The former is specific: `None` is the only special object that triggers defaulting to `[]`. The latter will replace any false-y object with `[]`. – Josh Bleecher Snyder Oct 02 '12 at 17:37
  • 7
    So, is this a bug? This behavior is quite counterintuitive. – sharshofski Feb 02 '16 at 20:18
  • 9
    Michael J. Barber's contribution is correct - but no explanation is offered. The reason for this behavior is that the default argument is bound at function definition, not runtime. See http://stackoverflow.com/questions/1132941/least-astonishment-in-python-which-scope-is-the-mutable-default-argument-in – A--- May 06 '14 at 06:31
  • 4
    @sharshofski: It's a terrible design decision, and I don't know of any other language that handles defaults like this, but it's not officially considered a bug. – user2357112 Jul 17 '20 at 22:16
38

Let's illustrate what's happening here:

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],)

You can see that the default arguments are stored in a tuple which is an attribute of the function in question. This actually has nothing to do with the class in question and goes for any function. In python 2, the attribute will be func.func_defaults.

As other posters have pointed out, you probably want to use None as a sentinel value and give each instance it's own list.

aaronasterling
  • 68,820
  • 20
  • 127
  • 125
27
class Node:
    def __init__(self, wordList=None adjacencyList=None):
        self.wordList = wordList or []
        self.adjacencyList = adjacencyList or []
Shankar Cabus
  • 9,302
  • 7
  • 33
  • 43
20

I would try:

self.wordList = list(wordList)

to force it to make a copy instead of referencing the same object.

krousey
  • 1,728
  • 15
  • 22