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
  • 76,765
  • 14
  • 60
  • 81
Andrew
  • 67
  • 6
  • I don't see any recursion in your post – OneCricketeer Oct 31 '16 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[:]` – PM 2Ring Oct 31 '16 at 16:55
  • 1
    Well, it's not really about mutable vs immutable either. It's about parameter passing. – Daniel Roseman Oct 31 '16 at 16:55
  • @DanielRoseman Yeah, ok. – PM 2Ring Oct 31 '16 at 16:56
  • Possible duplicate of ["Least Astonishment" and the Mutable Default Argument](http://stackoverflow.com/questions/1132941/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. – Prune Oct 31 '16 at 17:28

3 Answers3

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]
Ziyad Edher
  • 2,150
  • 18
  • 31
  • 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 – Andrew Nov 01 '16 at 03:18
  • 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. – Ziyad Edher Nov 01 '16 at 03:21
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.

Rick
  • 43,029
  • 15
  • 76
  • 119
  • 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 Nov 01 '16 at 03:20
  • @Andrew give [this article a read](http://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick/), too. – Rick Nov 02 '16 at 04:09
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!

Community
  • 1
  • 1
Aaron Morefield
  • 952
  • 10
  • 18
  • 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. – Ziyad Edher Oct 31 '16 at 17:29