When is a default argument evaluated in Python?

Asked

Viewed 265 times

13

Let us 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, but see the following code:

a = Foo()
b = Foo()

Two distinct instances, but by 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 it working in the Ideone.

When verifying the id of each attribute, it is possible to 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 behavior only when the default value of the attribute is a changeable type, but it still seems to be a strange behavior.

This behavior should be expected and why it occurs?

2 answers

9


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

If that object is changed, like the list, in this example, the subsequent calls to the function will refer to this changed object.

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

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

In your script the argument value = [] within the method is shared with all instances, because the changeable object [] is created only once (at the time of declaring the class) and the instances will get the data from it, according to the documentation:

https://docs.python.org/3/faq/programming.html#Why-are-default-values-Shared-between-Objects

So I guess I can use None and check with Python:

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
[]
  • Float is a changeable or immutable type in Python? This happens with floats as well?

  • Very useful this tip: "it is recommended not to make use of mutable objects as default values in function settings, use None instead and deal within function".

  • Good night dear @Thiagokrempser, numbers refers to the float and the integers, are also immutable. Thanks for the editing, it was a great contribution.

3

Complementing the william’s response, in fact, what occurs is the evaluation of the arguments in time of method definition and how the objects in Python are treated as reference, in the method definition is created an object that represents the default value and in each new instance (or method call)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 analyzing the attribute __defaults__ of the method.

According to the documentation, the attribute __defaults__ is a tuple that contains the default values of the arguments 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 verify the value of __defaults__ of the initialising method:

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

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

When checking the return of id of this object is confirmed 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 which 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 the mutable type, the object itself representing the default value of the argument, stored in __defaults__, is modified equally.

See working on Ideone.

A similar question was asked in Stack Overflow:

Why are default Arguments evaluated at Definition time in Python

Browser other questions tagged

You are not signed in. Login or sign up in order to post.