0

I'm getting some inconsistencies with how this work. In the tutorial, the example clearly demonstrates that the list is passed by reference, so any changes to the list within the function is reflected in the original value after the function ends.

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

print fun1(1)
print fun1(2)
print fun1(3)

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

If I try THIS example, however, why does b not change (i.e. if b is stored as "2", then it should return 3)?

def g(a, b=1):
    b=a+b
    return b

>>> g(1)
    2
>>> g(1)
    2

I'm going to guess that it's because the statement b = a+b assigns b to a new b that is only accessible within the function environment, but isn't this inconsistent with how the first example works (in which it's obvious that the variable L was defined BEFORE the function starts, and also can be altered within the function itself).

Mezzoforte
  • 59
  • 1
  • 6
  • 2
    Does this answer your question? ["Least Astonishment" and the Mutable Default Argument](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) – Axe319 Mar 15 '23 at 20:38
  • 2
    `b = a + b` results in the name `b` referring to a new object (not the same object that is the default value of the parameter, which is what `b` referred to before that statement). But `L.append(a)` doesn't change what object the name `L` refers to; rather, it modifies that object itself. – slothrop Mar 15 '23 at 20:39
  • Both default values are handled the same way. The difference is in how the values get *used* in the body when no argument is supplied. – chepner Mar 15 '23 at 20:39
  • Useful background: https://nedbatchelder.com/text/names.html – slothrop Mar 15 '23 at 20:39
  • Assignment is not mutation; mutation is not assignment. See https://nedbatchelder.com/text/names.html. – chepner Mar 15 '23 at 20:40
  • What tutorial is showing you code that uses a mutable default value? – chepner Mar 15 '23 at 20:40
  • Python does not pass arguments by value or by reference; it passes them by *name*, and this is not affected by the type of the argument. – chepner Mar 15 '23 at 20:42
  • 1
    `b = a + b` does *assign to* to the local variable `b`, but it does not *define* `b`; `b` is already defined by the initial assignment to the parameter `b` when `g` is called. – chepner Mar 15 '23 at 20:43
  • @chepner So based on what you are saying, there are 2 b's. One is the local b that I've just assigned a+b to, and the other is the b default argument that I defined prior to the function call. So despite the fact that they are both b's, Python CAN recognize the fact that if the b is on the LHS of the assignment, then it is the new local variable to be assigned to, but if it is on the RHS, then it needs to search through the different scopes for the appropriate value. Am I on the right track? – Mezzoforte Mar 15 '23 at 21:07
  • No, there is only one `b`; it's defined when `g` is called using the argument passed if available, `g.__defaults__[0]` otherwise. `b = b + a` is the *second* assignment, using the existing value on the right-hand side expression before assigning the new value to `b`. – chepner Mar 15 '23 at 21:12
  • @chepner Okay, so if this is the case, then based on what this tutorial is trying to demonstrate (which is that default values are only ever evaluated ONCE), why is the new value not "remembered" for the next time I call g, but instead it evaluates b as 1 AGAIN, instead of the 2 that I've just assigned b to via a+b? – Mezzoforte Mar 15 '23 at 21:20
  • 1
    @Mezzoforte that default parameter 'slot' of `g` is filled once only, with the integer object `1`: just like the default parameter 'slot' of `fun1` is filled once only, with an *initially* empty list object. The difference between them is that the body of `fun1` mutates that list object, while the body of `g` doesn't mutate the integer object. Reassigning the name `b` to point to a different object does not repopulate the default parameter slot of `g` with the new referent of `b` - in fact, if it did, then it wouldn't be true to say that defaults are only evaluated once. – slothrop Mar 15 '23 at 21:30
  • (I'm using the term 'slot' here in a colloquial way - it does have a specific Python meaning, but that's not what I mean here.) – slothrop Mar 15 '23 at 21:30
  • 1
    When you first call `g(1)`, there are now *two* references to the default value, `g.__defaults__[0]` and `b`. Assigning a new value to the name `b` does not affect that value; `g.__defaults__[0]` still refers to the default to be used the next time `g` is called without an argument. – chepner Mar 15 '23 at 22:01
  • (For some reason, `g.__defaults__` itself is not a read-only attribute; you can provide a new set of defaults by assigning a new tuple directly to it. Note that it *must* be a tuple, not just an arbitrary sequence.) – chepner Mar 15 '23 at 22:02
  • @chepner Okay, so what you are saying is that when I run the function definition for the very first time and the program reads the line "b=1", what it does is it set two names to the value 1. A name called b and a named called g. __defaults__[0] (__defaults __ is a sequence object of some sort). And any operations on b will then not affect g.__defaults__[0]. Does that sound closer to what you mean? – Mezzoforte Mar 16 '23 at 00:36

2 Answers2

4

It's because assignments with operator = do not changes the underlying objects, but instead assigns a python object to a new variable name (possibly overwriting or overshadowing it).

You can have the same behaviour with lists if you use the operator = instead of .append():

>>> def g2(a, b=[]):
...   b = b + [a]
...   return b
...
>>> g2(1)
[1]
>>> g2(4)
[4]
>>> g2(5)
[5]

See how b is always a list of length 1? That's because I locally reassign b inside the scope of the function and that assignment will go away when I leave the function, so when I enter it again, it re-uses the global empty list, which was not modified, as default value. When you use append you modify the underlying python object in-place. When I use =, I assign a new local variable to the result of the expression (which in the case of b + [a] is a brand new python object).

Last point, some python objects such as numbers or strings are immutable. That is, unlike list, the allocated python object in memory cannot change. So the only way to manipulate them is to create new object that can be assigned using operator =. Thus, there no real way to modify such a variable the caller passed along in a way that impacts the caller as well.

Lærne
  • 3,010
  • 1
  • 22
  • 33
1

Because b is immutable when you try to assign a new value it doesn't change the existing value, it reassigns the variable to a new number object.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • 1
    It doesn't matter that `b` is immutable; `b = b + a` would assign a new value to `b` even if `b` were a list. Nothing is ever assigned *to* an object; assignments only assign values to *names*. – chepner Mar 15 '23 at 21:13
  • @chepner mutable objects can be modified in place, immutable ones cannot. Python does its best to hide the difference, which is why this kind of confusion arises. Would not `L[0] = 2` be an assignment? – Mark Ransom Mar 15 '23 at 21:30
  • Not really, or at least, it's not an assignment to `L`. It's short for `L.__setitem__(2, 0)`, which is a mutating method call. – chepner Mar 15 '23 at 21:52
  • Mutable objects *can* be modified in place, but the assignment `b = b + a` makes no attempt to do so. – chepner Mar 15 '23 at 21:58
  • @chepner you're missing my point. The syntax that Python uses tries to hide the difference between a mutation and an assignment. Knowing the difference will make you wise in the ways of Python, but it's not a natural thing for people to pick up on - it must be explained. – Mark Ransom Mar 15 '23 at 22:13
  • 1
    You haven't mentioned *that* difference. Your answer makes it sound like `b = b + a` behaves different depending on whether `b` is mutable or not. It *doesn't*. The type of `b` is *absolutely irrelevant* to the semantics of an assignment to a name. `b + a` will always produce a value, and that value will be assigned to the name `b`, whether `b` was immutable or not. – chepner Mar 15 '23 at 22:27
  • @chepner you're absolutely right. I was a bit rushed with this answer and it wasn't up to my usual standards. I already left an upvote for the answer that explained it better than I did. – Mark Ransom Mar 16 '23 at 21:31