Pregunta

>>> def a():
...     print "a executed"
...     return []
... 
>>>            
>>> def b(x=a()):
...     x.append(5)
...     print x
... 
a executed
>>> b()
[5]
>>> b()
[5, 5]

x is bound to an empty list object when the function b is first defined. the empty list object gets modified each time b is called because b is bound to the object.

What I don't get is when this happens to immutable objects:

>>> def a():
...     print "a executed"
...     return 0
... 
>>>            
>>> def b(x=a()):
...     x = x + 2
...     print x
... 
a executed
>>> b()
2
>>> b()
2

From my POV, x is bound to the int object 0 when the function b is first defined. Then, x is modified when b() is called. Therefore subsequent calls to b() should re-bind x to 2, 4, 6, and so on. Why doesn't this occur? I am obviously missing something important here!

Thx :)

¿Fue útil?

Solución

When you do x = you're not modifying the object that x references, you're just changing the reference x to point to a different object, in this case, another int. In this case it's event irrelevant whether x points to an immutable object. If you would do x = x + [5] with lists, it would also remain unchanged. Note the difference:

def b1(x = []):
    x = x + [5]
    print(x)

def b2(x = []):
    x.append(5)
    print(x)

print("b1:")
b1()
print("b1:")
b1()

print("b2:")
b2()
print("b2:")
b2()

Gives:

b1:
[5]
b1:
[5]
b2:
[5]
b2:
[5, 5]

When the function is being executed, you're working on a local variable x that either was initialized using the default value, or provided by the caller. So what gets rebound is the local variable x, not the default value for the parameter.

You may want to also read about the difference between formal and actual parameters. It's only slightly related to this problem, but may help you understand this better. An example explanation can be found here.

Otros consejos

Careful, there's a huge difference between:

x.append(5)

and:

x = x + 1

Namely, the first mutates the object referenced by x whereas the second creates a new object which is the result of x + 1 and rebinds it to the name x.

Of course, this is a bit of an over-simplification -- e.g. what if you had used += ... It really falls back on how __add__ and __iadd__ are defined in the first place, but this should get the point across ...


To go a little deeper, you can think of a function as an object or an instance of a class. It has some special attributes which you can even look at if you want to:

>>> def foo(x = lst): pass
... 
>>> foo.func_defaults
([],)
>>> foo.func_defaults[0] is lst
True

When the function is defined, func_defaults1 gets set. Every time the function gets called, python looks at the defaults and the stuff which was present in the call and it figures out which defaults to pass into the function and which ones were provided already. The take away is that this is why, when you append to the list in the first case, the change persists -- You're actually changing the value in func_defaults too. In the second case where you use x = x + 1, you're not actually doing anything to change func_defaults -- You're just creating something new and putting it into the function's namespace.

1the attribute is just __defaults__ in python3.x

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top