4

Consider the following cases:

Case 1:

def fun(arg):
    
    arg += 1
    
my_var = 1
fun(my_var)
print(my_var)
>> 1

Case 2:

def fun(arg):
    
    arg += [4]
    
my_var = [1,2,3]
fun(my_var)
print(my_var)
>> [1, 2, 3, 4]

Case 3:

def fun(arg):
    
    arg = arg + [4]
    
my_var = [1,2,3]
fun(my_var)
print(my_var)
>> [1, 2, 3]

Case 4:

def fun(arg):
    
    print(arg)

fun("Hi")
print(arg)
>> Hi
Traceback (most recent call last):
  File "<string>", line 8, in <module>
NameError: name 'arg' is not defined

Case 4 demonstrates that the scope of the argument variable lies within the function. Case 1 and 3 support that as changes to the arg variable within the function are not reflected in the parameters globally.

But why does Case 2 happen at all? I noticed the same thing when I used append instead of the +=. Shouldn't the changes that happen to arg not have any effect on the variables the function was called with from a different scope?

Any help appreciated. Thanks in advance.

Nitin
  • 73
  • 7
  • 1
    Assigning a new value to a variable (case 1,3) is different than *mutating* a value (that happens to be "hold" by a variable) (case 2). Or in other words: In case 1 and 3 you are reading from and writing to `arg`. In case 2 you are only reading from arg. – Felix Kling Jan 25 '21 at 13:19
  • 2
    `+=` works a bit weird for lists, because it mutates the left-hand operand. `mylist += X` does a different thing from `mylist = mylist + X`. See [Why does += behave unexpectedly on lists?](https://stackoverflow.com/q/2347265/3890632) – khelwood Jan 25 '21 at 13:21
  • Here's a nice overall explanation on function scopes from Robert Heaton , JFYI https://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick/ – Icebreaker454 Jan 25 '21 at 13:25

4 Answers4

4

The answer is simple: Function arguments are local variables!

Some more observations:

= is the assignment operator and rebinds a variable. This will never mutate objects (ecxeption slice assignment).

+= is its own operator and is implemented as a mutation for mutable types. You can implement its behaviour for your own types by implementing the __iadd__ dunder method. Hence, in general, a = a + b is not equivalent to a += b!

For lists, a += b corresponds to a.extend(b). a + b on the other hand, creates a new list object, and a = a + b rebinds variable a to that new object.

user2390182
  • 72,016
  • 6
  • 67
  • 89
1

Case 2 is the only one where you use a mutating method on an instance. This affects the parameter passed.

The others do nothing or just reassign the argument. Assignment in python only affects the variable being assigned and not other variables that still refer to the previous instance.

Mandatory link to Ned Batchelder

quamrana
  • 37,849
  • 12
  • 53
  • 71
0

Python lets you create variables simply by assigning a value to the variable, without the need to declare the variable upfront. The value assigned to a variable determines the variable type.

if you print the variable type on each function you will see it.

#case 2
def fun2(arg2):
    
    arg2 += [4]
    
my_var2 = [1,2,3]
#fun(my_var2)
print(type(my_var2))
print(type(my_var2[0]))

#case 4 where no type assigned
def fun4(arg44):
    
    print(arg)

#fun(myvar4)
print(arg4)

Watch this code in action with a visual explanation.

Avatazjoe
  • 465
  • 6
  • 13
0

Any variables declared inside a function are local, but lists and dict behave a bit differently. You can append elements to a list even if you dont write global list_name inside the function nor pass the lists as arguments to the functon. Only lists and dict behave this way. However the difference in case 2 and 3 seems to be a bug. It seems that when you write

a = [1,2,3]
def fun(a_list):
    a_list = a_list + [4]
    print(a_list)
fun(a)

>>>[1,2,3,4]

you get [1,2,3,4], so it becomes local variable, but when you write arg += [4] , it behaves as global variable