3

I am confused about python's evaluation of default function arguments. As mentioned in the documentation (https://docs.python.org/3.6/tutorial/controlflow.html#more-on-defining-functions)

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))

results in

[1]
[1,2]
[1,2,3]

But

def f(a, L=[]):
    L = L+[a]
    return L 

print(f(1))
print(f(2))
print(f(3))

results in

[1]
[2] 
[3]

If L is evaluated only once, shouldn't both the function return the same result? Can someone explain what am I missing here?

This question is different than "Least Astonishment" and the Mutable Default Argument. That question is questioning the design choice while here I am trying to understand a specific case.

Umang Gupta
  • 15,022
  • 6
  • 48
  • 66
  • 1
    adding 2 list creates a new list, but append adds element to an existing list. Try `L += [a]` – Shashank Mar 11 '18 at 08:45
  • This `L = L+[a]` is not Appending, this is pure reassignment. – Ubdus Samad Mar 11 '18 at 08:46
  • Yes, but what I am unsure about is why would that force to re-evaluate default value? – Umang Gupta Mar 11 '18 at 08:46
  • 1
    its bad practice to use List,Dict as default values in Python, see https://docs.quantifiedcode.com/python-anti-patterns/correctness/mutable_default_value_as_argument.html – Ami Hollander Mar 11 '18 at 08:46
  • 1
    Do not assign to default value mutable objects. – vishes_shell Mar 11 '18 at 08:47
  • I agree it is a bad practise to assign default to mutable object change them in the function. I am not going to do that, however I want to understand what is happening here. – Umang Gupta Mar 11 '18 at 08:48
  • its explained well in the link I shared above – Ami Hollander Mar 11 '18 at 08:49
  • 1
    @UmangGupta because you never mutate `L` - it always remains an empty list. `L = L+[a]` just makes the name `L` bind to the result of what's effectively `[] + [a]` which is a new list.... The original `L` is never affected. – Jon Clements Mar 11 '18 at 08:52
  • 1
    @UmangGupta you might find https://nedbatchelder.com/text/names.html a useful read... – Jon Clements Mar 11 '18 at 08:54
  • I found a decent explanation here. http://effbot.org/zone/default-values.htm, however I am still confused how python distinguished between two variables with same name L & L – Umang Gupta Mar 11 '18 at 08:55
  • 1
    @Umang `L+[a]` - the L there is referring to the closest name in scope - which happens to be your default argument. A new object is created and then the name `L` is bound to *that new object* via `L = `. Your original `L` is never changed and you now can't access it because you don't have a name for it anymore... The name `L` is now your new list... so even if you did do `L.append(5)` - you're mutating the new list - not the default argument one. – Jon Clements Mar 11 '18 at 09:02
  • @JonClements Thanks for the explanation. This explanation I think convinces me enough. Would you mind adding it as an answer so I can accept? – Umang Gupta Mar 11 '18 at 09:05
  • I think heemayl has summed it up succinctly :) – Jon Clements Mar 11 '18 at 09:06

2 Answers2

1

You are not appending but sort of creating a new L here:

>>> def f(a, L=[]):
    L = L+[a] #Here you're making a new list with the default argument and a.
    print(id(L))
    return L
>>> f(1)
140489350175624
[1]
>>> f(1)
140489349715976
[1]
>>> f(2)
140489349718536
[2]

You can see the id of L changes everytime.

But when you use the append it goes as:

>>> def f(a, L=[]):
    L.append(a) #Here you're just appending and NOT making a new list.
    print(id(L))
    return L
>>> f(1)
140489350175624
[1]
>>> f(12)
140489350175624
[1, 12]
>>> f(3)
140489350175624
[1, 12, 3]
>>> 

Read this.

Ubdus Samad
  • 1,218
  • 1
  • 15
  • 27
1

In

L.append(a)

you are appending to the same list object in all function calls, as lists are mutable.

whereas in:

L = L+[a]

you're actually rebinding the name L to the concatenation of L and [a]. Then, the name L becomes local to the function. So, in each function call L becomes different after rebinding.

heemayl
  • 39,294
  • 7
  • 70
  • 76