2

Following are three python codes:

======= No. 1 =======

def foo(x, items=[]):
    items.append(x)
    return items

foo(1)  #return [1]
foo(2)  #return [1,2]
foo(3)  #return [1,2,3]

====== No. 2 ========

def foo(x, items=None):
    if items is None:
        items = []
    items.append(x)
    return items

foo(1)  #return [1]
foo(2)  #return [2]
foo(3)  #return [3]

====== No. 3 =======

def foo(x, items=[]):
    items.append(x)
    return items

foo(1)    # returns [1]
foo(2,[]) # returns [2]
foo(3)    # returns [1,3]

For code No.1, since the value of items is not provided, I think it should take a default value of [] all the time. But the parameter items behaves like a static variable, retaining its value for subsequent use. The code of No.2 executes as I expected: each time foo is invoked, items take the default value None. As for code No.3, I totally have no idea. Why the above three pieces of codes execute so differently? Can you explain? Thank you.

PS: I'm using python 3.3.1

user2384994
  • 1,759
  • 4
  • 24
  • 29
  • For no. 1, does it act that way if foo is just called with no assignment? – Jiminion Aug 22 '13 at 01:16
  • After reading the "“Least Astonishment” in Python: The Mutable Default Argument" and the associate link http://effbot.org/zone/default-values.htm, I think I found the answer. Unfortunately, I think the answer I have found is different from the accepted one in the "“Least Astonishment”" thread and the one in the effbot.org webpage, as well as the answers in this question. So I planned to post an answer of my own. But it seems that I am not allowed to do so perhaps because some people think it is a duplicate post. (continue...) – user2384994 Aug 22 '13 at 04:33
  • I agree that the question is somewhat duplicate but the answer is by no means duplicate. As a result, I will not accept any answer and decided to post my answer here as a comment and also in the “Least Astonishment” post. – user2384994 Aug 22 '13 at 04:34
  • OK, now my answer is as follows: – user2384994 Aug 22 '13 at 04:35
  • First, there are two type of data types in python, one is simple elementary data type, like numbers, and another data type is objects. Second, when passing data to parameters, python pass elementary data type by value, i.e., make a local copy of the value to a local variable, but pass object by reference, i.e., pointers to the object. Admitting the above two points, let's explain what happened to the three pieces of python codes. It's only because of passing by reference for objects, but has nothing to do with mutable/immutable, or def is executed only once when it is defined. – user2384994 Aug 22 '13 at 04:47
  • [] is an object, so for code No.1, python pass the reference of [] to `items`, i.e., `items` is only a pointer to [] which lies in memory as an object. There is only one copy of [] with, however, many references to it. For foo(1), the list [] is changed to [1] by append method. But Note that there is only one copy of the list object and this object now becomes [1]. When running foo(2), what effbot webpage says (`items` is not evaluated any more) is wrong. `items` is evaluated to the list object, although now the content of the object is [1]. This is the effect of passing by reference! – user2384994 Aug 22 '13 at 04:58
  • The result of foo(3) can be easily derived in the same way. So let's analize code No.2. [] is an object, so is None (the former is mutable while the latter is immutable. But the mutability has nothing to do with my question). `None` is somewhere in the space but we know it's there and there is only one copy of None there. So every time foo is invoked, `items` is evaluated (as opposed to some answer that it is only evaluated once) to be None, to be clear, the reference (or the address) of None. Then in the foo, item is changed to [], i.e., points to another object which has a different address. – user2384994 Aug 22 '13 at 05:06
  • Now let me explain No.3. The invocation of foo(1) make `items` point to a list object [] with an address, say, 11111111. the content of the list is changed to [1] in the foo function in the sequel, but the address is not changed, still 11111111. Then foo(2,[]) is coming. Although the [] in foo(2,[]) has the same content as the default parameter [] when calling foo(1), their address are different! Since we provide the parameter explicitly, items has to take the address of this new [], say 2222222, and return it after making some change. – user2384994 Aug 22 '13 at 05:21
  • Now foo(3) is executed. since only x is provided, `items` has to take its default value again. What's the default value? It is set when defining the foo function: the list object located in 11111111. So the items is evaluated to be the address 11111111 having an element 1. The list located at 2222222 also contains one element 2, but it is not pointed by `items` any more. Consequently, An append of 3 will make `items` [1,3]. – user2384994 Aug 22 '13 at 05:22
  • From the above explanations, we can see that the effbot webpage failed to give a relevant answer to my question. What is more, I think a point in the effbot webpage is wrong. I think the code regarding the UI.Button is correct: each button can hold a distinct callback function which will display different value of `i`. I can provide an example to show this (It is inconvenient to embed codes in comment, so I put them in the answer to the "“Least Astonishment”" question). Also, I hope those who marked my question as duplicate can undo it due to my non-duplicate answer. – user2384994 Aug 22 '13 at 05:30

3 Answers3

2

"Least Astonishment" and the Mutable Default Argument

This stackoverflow posts gives answer your question, as Python functions are objects with state, the "item" named argument retains state between multiple calls.

It has its uses:

def calculate(a, b, c, memo={}):
    try:
        value = memo[a, b, c] # return already calculated value
    except KeyError:
        value = heavy_calculation(a, b, c)
        memo[a, b, c] = value # update the memo dictionary
    return value

As explained here http://effbot.org/zone/default-values.htm

Community
  • 1
  • 1
windwarrior
  • 456
  • 2
  • 11
1

The source code [] is not a value. It's an expression that evaluates to a list. If you evaluate it once, you get one list; if you evaluate it multiple times, you get multiple lists. The key thing to realize is that default arguments only evaluate the default expression once and store it; thus, if the default argument is a list, it is always the same list on every call.

user2357112
  • 260,549
  • 28
  • 431
  • 505
0

It appears that the definition of the default list object for items occurs at function definition time. If you pass something in, the items variable uses the object you pass in, but otherwise defaults to that default list object.

Because examples 1 and 3 don't redefine the default list object (while example 2 does set the list to empty, every time), changes you make to it are cumulative. In example 3, passing in a separate, empty list object for use in the items variable means your second execution of the function doesn't have any effect on the default list object you've defined.

Mark R. Wilkins
  • 1,282
  • 7
  • 15