7

The intended objective of function foo is to add the number provided as argument to the list and, in case it is 0, reset the list. First I wrote this program:

def foo(n, bar = []):
    if n == 0:
        bar = []
        print("list empty")
    else:
        bar.append(n)
        for y in bar:
            print(y, end=', ')
        print()

foo(5)
foo(3)
foo(0)
foo(6)

output:

5, 
5, 3, 
list empty
5, 3, 6, 

but it looks like bar = [] is ignored. Then I changed bar = [] with bar.clear() and it works as I thought:

def foo(n, bar = []):
    if n == 0:
        bar.clear()
        print("list empty")
    else:
        bar.append(n)
        for y in bar:
            print(y, end=', ')
        print()

foo(5)
foo(3)
foo(0)
foo(6)

output:

5, 
5, 3, 
list empty
6,

I haven't understood why bar.clear() works differntly from bar = [] since clear() should

Remove all elements from the set.

so the same thing bar = [] does.

Edit: I don't think my question is a duplicate of “Least Astonishment” and the Mutable Default Argument, I'm aware that

The default value is evaluated only once.

(from the official tutorial) but what I am asking is, why bar = [] doesn't edit (in this case clear) the list, while append and clear does?

untitled
  • 389
  • 4
  • 13
  • 3
    Possible duplicate of ["Least Astonishment" and the Mutable Default Argument](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) – roganjosh Jun 30 '18 at 17:18
  • 1
    `l.clear()` is the equivalent of `l[:] = []`, not `l = []`. You're mutating an existing list object instead of creating a new empty list. – Patrick Haugh Jun 30 '18 at 17:19
  • 1
    `bar = foo[]` isn't used in your code, as far as I can see, so it's not there to be ignored. – roganjosh Jun 30 '18 at 17:21
  • 1
    @PatrickHaugh: Super pedantic, but `l.clear()` is most directly equivalent to `del l[:]`. `l[:] = []` requires the creation of a temporary `list`, `del l[:]` doesn't. Admittedly, the distinction is fairly irrelevant to observed behavior. – ShadowRanger Jun 30 '18 at 17:26

6 Answers6

7

bar = [] rebinds bar; it creates a brand new empty list, and binds the name bar to it. It doesn't matter what bar was before; it could have been an int, a tuple, or dict, and it's irrelevant now, because it's now bound to a brand new list. The old binding no longer exists in the current method call, but the original list remains in existence as the value of the default argument to foo (note: Mutable defaults are considered surprising and ugly because of this confusion).

bar.clear() calls a method on the existing list, which tells that list to clear itself (and has no effect on what bar refers to; it better be something with a clear method, but other than that, it's not really required that it be a list). Since it's the same bar you started with (referencing the same mutable default value cached in the function defaults), that affects your future calls (because bar continues to refer to the list you provided as the mutable default).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
4

The thing you should really understand is how variables work in Python.

The variables are always just references to the actual content. Nothing is stored in the variable itself it just points to the unnamed content.

Now, when you call foo() what you have is a list in memory and the local bar variable is pointing to that. If you call bar.clear(), that list is actually cleared and that's what you want. However, if you say bar = [] you just bind the local bar variable to a new, empty list and the other list (that already contains 5 and 3) remains intact and it will be used the next time you call foo().

dabadab
  • 388
  • 1
  • 10
3

Here in you code bar is the reference to some list located in memory. When you use bar = [], the variable bar becomes associated with a new (empty) list. When you use bar.clear(), you modify current list.

Speaking generally, this means that lists in Python are passed by reference and not by value. You can read more about it here.

Eugene Primako
  • 2,767
  • 9
  • 26
  • 35
3

When you do a list.clear(), the list gets cleared :-) When you do a list = [], the list gets overwritten by a new empty list.

The difference is that if something was referencing the list before if was overwritten, it will still be referencing the values you think now has been cleared.

This is an excellent way of introducing extremely hard to debug bugs.

chjortlund
  • 3,613
  • 2
  • 31
  • 30
3

Let's say you have a list containing some values, and you assign it to a variable named foo.

foo = [0, 1]

This variable now points to this list in memory.

Let's say you then create a new variable named bar and assign to foo.

bar = foo

These two variables now point to the same list object in memory. If you then change foo and have it equal [], what happens to bar?

foo = []
bar == [0, 1] # True

Now let's do the same with foo.clear().

foo.clear()
bar == [] # True

This is because using the clear method on a list clears that list object, which affects everything that references this object, while assigning a variable to an empty list only changes what that variables, and doesn't change the original list whatsoever.

1

this has desired property:

def foo(n, bar = []):

    if n == 0:
        bar[:] = []
        print("list empty")
    else:
        bar.append(n)
        for y in bar:
            print(y, end=', ')
        print()
H.Bukhari
  • 1,951
  • 3
  • 10
  • 15
  • 4
    This doesn't answer the question, just provide an alternate way of writing the "working" code (which `bar.clear()` already covers adequately). It's fine to provide fixed code, but you need to explain *why* it's better/more correct than the original. – ShadowRanger Jun 30 '18 at 17:30