Confusing behaviour while using append() in a function

Hey all. I’m very new to python, learning by myself with a book and amazed by how a useful it is.
I understood that variables in function are local and even if named like variables used in the main script, they are 2 separate objects. I tested that, and I am ok with it.
However, I forgot about it earlier today, and realized that my script was giving me the proper answer when it shouldn’t as my function had no return statement.

Here is a simplified exemple of what happened:

def random_function(a, b):
    a.append('6')
    b = ['R']
    print(f'a in function = {a}')
    print(f'b in function = {b}')


a = ['1']
b = ['E']
print(f'a = {a}')
print(f'b = {b}')
random_function(a, b)
print(f'a = {a}')
print(f'b = {b}')

OUTPUT:

a = ['1']
b = ['E']
a in function = ['1', '6']
b in function = ['R']
a = ['1', '6']
b = ['E']

What confuses me is that, if I change the value of one of the 2 lists, the local and global effect are respected, but when I uses append(), it changes the value everywhere.
Is it normal and what is the explanation for this? I tried to look around for answers, but couldn’t find anything about this specificly.

2 Likes

In Python, things are passed as references. So when you have a function below, and you pass it a list, you are passing a reference to that list. In the case below, we pass what global a is referring to, which is our global list. As we enter the function, global a is referencing the global list, and the local a variable in the function is also referencing the global list. When we call the append of the local a, it appends the value to the global list as they both reference the same list. They are two separate variables referring to the same object.

def function(a):
    a.append(2)
    print(a)

a = []
function(a)
print(a)

OUTPUT

[2]
[2]

In this case, we pass global b which passes the reference to our global list. In the function we also have a local b which, at the time we enter the function, is also referring to the global list. Both, once again are referencing the same list, but then we set the local b to a new local list. Global b and local b are no longer referencing the same list. Now append only affects the locally scoped list in the function:

def function(b):
    b = []
    b.append(2)
    print(b)

b = []
function(b)
print(b)

OUTPUT

[2]
[]

Hopefully that makes things a little clearer.

3 Likes

It is now very clear! Thank you very much @facelessuser!!

1 Like

A question came up regarding this topic. As I learn Python I also try to learn the ‘right’ way to code. My code may be read and used by colleagues someday.

Is it ok to keep using .append() in my function on the global reference to the list I have passed as a parameter (or argument?) in my function?
Or should i make sure to redefine my list in the function, modify the local list, and then return its value when the function ends? I’ll .append() the returned value to my list in the global mode.

1 Like

Your function could either:

  1. copy the list, append to it, and return the new list, or
  2. modify the list in place and not return anything, or
  3. not modify any list at all, just return the new value. Have the caller (your “global mode”) append the new value, and print the result.

If the function just computes one value, and the computation is not trivial, the third option is probably most useful: it could be used even if you don’t want to (or can’t) print the result. (Imagine using the function to program a robot that doesn’t have a text-output screen attached to it.)

If need the list manipulation in the function, either of the first two might be appropriate. Not modifying the argument might be less surprising to those colleagues who’ll call your function. Less surprising behavior is good! But copying the list and keeping both copies around does require more resources (processing time and memory).

Make sure to name the function so that it’s clear what it does: your colleagues shouldn’t be if they call append_and_print(b) and b gets mutated, but it won’t be clear from a function(b) call.
(Something that’s not a toy example could have a better name, of course.)

Late reply from my end, but I wanted to thank you @encukou, for your informations.