Andrew Andrew - 1 month ago 14
Python Question

Python: confused about global and local variables

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

Answer

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.