Beware (mutable) keyword arguments and remember that...! by markon

Today, I've been working on a small Python script involving keyword-arguments. Being a simple script, it has been quite easy to debug and overcome the problem.

However, if you make an error like this inside a larger application, well, you will have few chances of catching it without a good debugging tool.

Let's see the code:

>>> class MyClass(object):
...     def __init__(self, element, children=[]):
...             self._element = element
...             self._children = children
... 
>>> c1 = MyClass(1)
>>> c1._children.append('foo')
>>> c2 = MyClass(2)
>>> c2._children
['foo']

So, beware mutable keyword-arguments and remember that when you pass a list, you're not passing a copy, but an actual reference to it (ok, you cannot modify it, but you can change its state!).

EDIT: Thank you both semarj (and Daimon) for your answer on reddit.com

EDIT2: Look at effbot.com for more info.

#python

Similar posts

Comments

damilare commented 3 months ago

E8aa8a215bedd8e498a44fefbcde913f?size=52

You should always check if len(children) > 0

>>> class MyClass(object):
    """docstring for MyClass"""
    def __init__(self, element, children=[]):
        super(MyClass, self).__init__()
        self.element = element
        self._children = children if len(children) > 0 else []

>>> c1 = MyClass(1)
>>> c1._children.append('foo')
>>> c2 = MyClass(2)
>>> c2._children
[]

markon commented 3 months ago

89b4a6d6e8544507352cddfb1d2ae76e?size=52

Cool, thank you very much for this suggestion! :D

spikeekips commented 3 months ago

Fedcab3e51787ee09dc8404fb1dadf65?size=52

This is your code to test.

>>> class MyClass(object):
...     def __init__(self, element, children=[]):
...         self._element = element
...         self._children = children

>>> c1 = MyClass(1)
>>> c1._children.append('foo', )
>>> c2 = MyClass(2, )
>>> c2._children
['foo']
>>> id(c1._children) == id(c2._children)
True

The weird thing of python class, after it is instantiated, it's properties is shared with others like this. I think, it's the long time bug in python.

This is another examples of this problems.

if class does not inherit the base object type.

>>> class MyClass () :
...     def __init__ (self, a=list(), ) :
...         self.a = a

>>> a = MyClass()
>>> a.a.append(3, )
>>> b = MyClass()
>>> a.a
[3]
>>> b.a
[3]
>>> id(a.a) != id(b.a)
False

if list() is set to the keyword argument.

>>> class MyClass (object, ) :
...     def __init__ (self, a=list(), ) :
...         self.a = a

>>> a = MyClass()
>>> a.a.append(3, )
>>> b = MyClass()
>>> a.a
[3]
>>> b.a
[3]
>>> id(a.a) != id(b.a)
False

To escape this uncomportable, we can set the default value of keyword argument to None and initialize it every time, when the class is created, like this.

>>> class MyClass (object, ) :
...     def __init__ (self, a=None, ) :
...         self.a = a if isinstance(a, (list, ), ) else list()

>>> a = MyClass()
>>> a.a.append(3, )
>>> b = MyClass()
>>> a.a
[3]
>>> b.a
[]
>>> id(a.a) != id(b.a)
True

markon commented 3 months ago

89b4a6d6e8544507352cddfb1d2ae76e?size=52

Is this a bug?

jpscaletti commented 3 months ago

41a72ac5110595e897793aa032edda5e?size=52

This is not a bug, but the expected behavior. You should never use a mutable object as a default parameter because it is not the real object but a "pointer" to it.

def foobar(a=[]):
    a.append(1)
    print a

>>> foobar()
[1]
>>> foobar()
[1, 1]

Use None instead and initialize to an object inside the function:

def foobar(l=None, dd=None):
    l = l or []
    dd = dd or {}
    ...

markon commented 3 months ago

89b4a6d6e8544507352cddfb1d2ae76e?size=52

If you read semarj's answer on reddit, he states that at compile time you're saying that "you just want that list", so Python keeps a reference to it. However, thank you for your last suggestion :)

spikeekips commented 3 months ago

Fedcab3e51787ee09dc8404fb1dadf65?size=52

@jpscaletti Yeah, right. I saw the same explanations on this /* problem */, but this kind of ambiguity in python always brings complexity or additional code.

damilare
jpscaletti
markon
spikeekips