When is a default argument evaluated in Python?

8

Consider the following class:

class Foo:
    def __init__(self, values = []):
        self.values = values

Note that we are using an instance attribute and not a class attribute, however, see the following code:

a = Foo()
b = Foo()

Two distinct instances, but when changing the attribute of one of the instances:

a.values.append(1)

The attribute of the other instance is changed:

print(b.values) # [1]
  

See working at Ideone .

When checking the id of each attribute, you can verify that both represent the same object, an expected behavior for a class attribute, not an instance:

print(id(a.values)) # 47568867296648
print(id(b.values)) # 47568867296648

Obviously this is the behavior only when the default value of the attribute is a changeable type, but still seems like a strange behavior.

This behavior should be expected and why it occurs?

    
asked by anonymous 26.09.2017 / 16:49

2 answers

3

A function call is expected to create new objects for default values, but this is not the case. Default values are created only once, when the function is defined .

If this object changes, such as the list, in this example, subsequent calls to the function will refer to this changed object.

By definition, immutable objects, such as números , strings , tuplas , and None , are protected against changes. Changes to mutable objects, such as dictionaries, lists, and class instances, can lead to confusion.

Because of this feature, it is recommended not to use mutable objects as default values, instead use None as the default value and within a function or method check with is None and , if it is None , create a new changeable object (like dictionary, list, etc) within the condition ( if ).

In your script the argument value = [] within the method is shared with all instances, since the [] mutable object is created only once to declare the class) and the instances will get the data from it, according to the documentation:

  

link

So I think you can use None and do a Python check:

class Foo:
    def __init__(self, values = None):
        if values is None:
            self.values = [] # cria uma lista vazia
        else:
            self.values = values # cria uma lista vazia

>>> d = Foo()
>>> e = Foo()
>>> d.values.append(1)
>>> d.values.append(2)
>>> e.values
[]
    
26.09.2017 / 17:24
0

Complementing the Guilherme's answer , in fact, what happens is the evaluation of arguments in defining time of the method and how the objects in Python are treated as a reference, in the definition of the method an object is created that represents the default value and in each new instance (or called of the method), the parameter will point to that same instance. This explains why the return of id is the same for different instances and becomes even clearer when parsing the __defaults__ attribute of the method.

According to documentation , the __defaults__ attribute is a tuple that contains the default values of a function or method. In this case, considering the code presented in the question:

class Foo:
    def __init__(self, values = []):
        self.values = values

We can check the value of __defaults__ of the initializer method:

print(Foo.__init__.__defaults__) # ([],)

The first value of this tuple is a changeable object that represents the default value of the values argument, since the class definition, since in Python the class itself is an object - and the method is also an object .

Checking the return of id of this object confirms that it represents the same reference of a.values and b.values :

print(id(Foo.__init__.__defaults__[0])) # 47568867296648

To confirm all that has been said in practice, just check the value in __defaults__ after an instance of Foo that has its modified class attribute:

print(Foo.__init__.__defaults__) # ([],)

a = Foo()
a.values.append(1)

print(Foo.__init__.__defaults__) # ([1],)

That is, when creating an instance and modifying its instance attribute, this attribute being of the type changeable, the object itself, which represents the default value of the argument, stored in __defaults__ , is modified in the same way. p>

  

See working at Ideone .

A similar question was asked in Stack Overflow:

Why are default arguments evaluated at definition time in Python

    
27.09.2017 / 05:22