1

Noob question. I'm writing a program with recursive function calls - it's a game so I'm using recursion to allow the computer to 'think ahead' by trying moves. I'm maintaining the game state in a list, and passing this to a function which alters the game state and recursively calls itself 9 or 10 times. When I tried the first version it seemed to treat the list as a global variable. I did some tests and found that variables are always treated as local, but if you alter a list inside the function (typically I am doing something simple like board[i] = "X") it alters the global list rather than act on the local list inside the function. The little example below shows what I mean: the print output is [1,2], whereas if I do the same example but make board an integer rather than a list, the value remains 1 outside the function.

Is there a simple way around this that would make python treat the list as local just inside the function, bearing in mind it needs to do this each time the function is recursively called?

def test(board):
 board[1] = 2
 return 1
board = [1] * 2
print board
Prune
78k14 gold badges63 silver badges83 bronze badges
asked Oct 31, 2016 at 16:49
5
  • I don't see any recursion in your post Commented Oct 31, 2016 at 16:53
  • This isn't about global vs local, it's about mutable vs immutable. If you want a copy of the list in the function then you'll need to create one, eg board = board[:] Commented Oct 31, 2016 at 16:55
  • 1
    Well, it's not really about mutable vs immutable either. It's about parameter passing. Commented Oct 31, 2016 at 16:55
  • @DanielRoseman Yeah, ok. Commented Oct 31, 2016 at 16:56
  • Possible duplicate of "Least Astonishment" and the Mutable Default Argument. I suspect that Ben's answer (at the bottom in my sorting), will be the easiest to read. Commented Oct 31, 2016 at 17:28

3 Answers 3

2

In Python, there are two types of objects, mutable and immutable.

Mutable objects include any user-created objects, and some built-in ones which do include list, while immutable objects are like String, int, and other primitive types.

If you pass in a mutable object into a method, the method gets a reference to that same object, so you can manipulate and mutate it as you like, this is called passing by reference. While if you pass in an immutable object, the object gets passed by assignment, which assigns a new local variable with the same value as in the parameter passed.


In your case, you are passing a list object into your method, which as we discussed above, passes it by reference, hence what you are getting inside the method is a reference to the object in the scope which the method was called from. To be able to create a local list which you can change and mutate without affecting the outer one, you have two choices:

  • Assign the passed parameter to a new variable and change and mutate that one from there.

  • Use full non-mutation assignments (like assigning the entire list not just the value at an index) which will unlink your variable from the reference.


For example:

def mutate (my_list):
 my_list.append(0)
my_list = [1, 2]
print(my_list)
mutate(my_list)
print(my_list)

will output

[1, 2]
[1, 2, 0]

while

def mutate (my_list):
 my_list = my_list + [0]
my_list = [1, 2]
print(my_list)
mutate(my_list)
print(my_list)

will output

[1, 2]
[1, 2]
answered Oct 31, 2016 at 17:11
Sign up to request clarification or add additional context in comments.

2 Comments

Hey, thanks for this super simple explanation. One more thing I've learned about Python. Following your suggestion I fixed my code just by adding board = board[:] at the start of the function and bingo we are in business
Precisely, by setting board = board[:] at the start, you are fully assigning the variable board which in turn unlinks it from the reference. Just a heads up, the reason you do not set board = board or even new_board = board is because you are just creating a new reference to the board variable with the new name; however, when you do board = board[:] you are saying that the new board is equal to all the elements inside board, not just the board itself.
1

Yes: copy the list.

board = board[:] 

The way you described the behavior demonstrates that you have a misunderstanding regarding how python handles object names and namespaces. I'll try to explain.

Say we do this:

# test.py
board = [1,2,3]
def test(board):
 board = board[:]

The name "board" appears 4 times above. If we run test.py directly from commandline, then here is what we have (in order):

  1. board: the global level object name
  2. board: the function-local object name (as part of the function definition)
  3. board: the function-local object name again, but being reassigned to a new object
  4. board[:]: a slice- or copy- of the object referred to by the function-local object name

It is critically important to recognize that you are ONLY passing the OBJECT NAME to your function. The confusion you have stems from the idea that you are passing the object itself. However, you are NOT. One of the basic reasons for this is that python manages memory for you; if you were able to pass around memory addresses and delete actual objects like in other languages, it would be very difficult to keep track of what is stored in memory (so things can be deleted when they're not needed, for example).

When the global object name board is passed to the function, a new function-local object name is created, but it still points to the same object as the global object name. This object happens to be a list, which is mutable.

Since both the global name and the function-local name point to the same mutable object, if you CHANGE that object:

board[1] = 2

...then it doesn't matter whether the change was made via the local name, or the global name; it is the same object being changed either way.

However, when you do this inside the function:

board = board[:]

The function local object name is being reassigned. There is no change to the object it was pointing to! It simply makes the local object name point to a NEW object instead of the object it pointed to before. In this particular case, the new object it is pointing to is a copy of the old object. But we could have just as easily had it point to some other object:

board = "HELLO WORLD!" 

By the way, this will all work the same for any other kind of mutable (set, list, dict) or immutable (int, float, str, tuple) object. The only difference is that since an immutable object cannot be changed, it often appears as if it is a copy of that object that is being passed to the function. But it is not; it is just a function-local name that points to the same object as the global name... the same as for a mutable object.

answered Oct 31, 2016 at 16:55

2 Comments

thanks this is very helpful and I've done exactly that. I've got a lot to learn...it's all got a bit more complex since ZX Spectrum basic which was the last time I did any proper coding :-)
@Andrew give this article a read, too.
0

A few points to make:

  • Python makes all variables which are not declared in the scope of a function to be globals. If you want the function to act on a global variable, you can rewrite your function to make changes to an outside declared variable; rather than directly working with the variable itself and returning it, return the value you wish to update in the global.
  • The object-oriented methods amongst most languages are of the type "Pass by Reference" or "Pass by Value". There is a good discussion here on that exact subject.

Hope this helps!

answered Oct 31, 2016 at 17:03

1 Comment

Your first point is not necessarily correct. You can define two variables inside a class, one global and one not, only the global one can be accessed from outside the class without any references to the class. Variables not in the scope of functions are not automatically global in python.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.