Python question: Default Pass by reference, or value?

Started by
17 comments, last by Zahlman 17 years, 9 months ago
This is just a brute-force depth-first search with some limit on allowed cost, AFAICT.

The problem is the statements like:

	upData = currentData


This does not clone the list, but instead aliases it. Again, Java object model.

However, Python is friendlier here in that it offers a general-purpose copy module. Here you could solve the problem by invoking the module like:

# At the top, or just inside findPathimport copy# ...upData = copy.copy(currentData)# Use upData


When the structure is complex (i.e. contains nested lists), there is also copy.deepcopy which applies recursively to the contained things.

However, there is a much simpler and idiomatic solution: instead of "copying" the list and appending to it (the reason you get the garbage results is that all the changes get applied to the passed-in currentData via the upData etc. aliases), do the append in a way that implicitly makes a copy:

upData = currentData + ['U']


That said, lots of cleanup is possible here. The directions are all treated in similar ways, so there's code duplication there that we can clean up. Also, we can represent currentSquare not as a list of two ints (as it currently appears to be) but instead represent points with complex numbers, and then use plain old addition to generate the new points.

def findPath(self, currentData, currentSquare, destinationSquare, remaining):    # I renamed 'movementLeft' because the 'left' is confusing ;)    # Similarly, change the getTile interface to accept the coordinate directly.    tileInfo = self.getTile(currentSquare)    currentTileMovementCosts = self.mapKey.getMovementCost(tileInfo)    # Guard clause: Can we no longer get there?    if movementLeft < 0:        currentData[0] = -1        return currentData    # Guard clause: Did we just get there?    if currentSquare == destinationSquare:        return currentData    # Otherwise, recurse over four neighbouring directions.    currentData[0] += currentTileMovementCosts    remaining -= currentTileMovementCosts    # set the default ("failed") result, and improve it with the recursion     # results if possible.    bestResult = [-1] + currentData[1:]    for direction in ([['L'], complex(-1, 0),                       ['U'], complex(0, -1),                       ['D'], complex(0, 1),                       ['R'], complex(1, 0)]):        tempData = currentData + direction[0] # append direction code        tempSquare = currentSquare + direction[1] # add direction vector	result = self.findPath(tempData, tempSquare, destinationSquare, remaining)        # If that's better than what we have, set it.        if result[0] != -1 and (bestResult[0] == -1 or result[0] < bestResult[0]):            bestResult = result # don't need to copy; we won't modify it,            # but just possibly replace it :)    return bestResult
Advertisement
There is no passing by value in Python. EVERYTHING is passed by reference. This differs from Java, as even integers, floats, and booleans are passed by reference.

However, integers, floats, and booleans, (and tuples as well) are all immutable. That means that you cannot change their value at all, and thus, you cannot propogate changes back to the caller.

Consider mulling over these examples:
def increment(x):    x += 1    print x myvar = 4increment(myvar) # prints 5print myvar # prints 4



def increment_list(mylist):    mylist[0] += 1    print mylist[0]myvar = [4]increment_list(myvar) # prints 5print myvar[0] # prints 5 as well


If you want to pass by value, you can simply copy the data once you get to the function.

def increment_list_by_value(mylist):    mylist = mylist[:] # [:] is a shorthand to copy lists and tuples, dictionaries also support the copy() method.    mylist[0] += 1    print mylist[0]myvar = [4]increment_list_by_value(myvar) # prints 5print myvar[0] # prints 4, because changes were only made to the copy
Quote:Original post by Tac-Tics
However, integers, floats, and booleans, (and tuples as well) are all immutable.

Whoa, nelly!

Tuples and strings are immutable. Integers, floats and booleans are not immutable in the sense your examples imply.
s = "Oluseyi"          # we would traditionally think of s as a strings = "Olu, the Evil"    # s was reassigned, not modifieds[2] = 'y'             # TypeError: object does not support item assignments += " Man"            # OK. Again, s was reassigned to the result of the concatenationi = 1                  # we would traditionally think of i as an integeri = 5i += 2                 # both OK; i is being reassigned

Given that there is no way to modify an integer without reassigning it, the notion that it is immutable is imprecise. It's the same for floats. Mutability is only relevant, in this context, to aggregate or sequence types - lists, tuples, strings, dictionaries. Of those four, only strings and tuples are immutable (you can not add new elements to a tuple, nor can you modify an element already in a tuple).

Your increment example doesn't prove the immutability of integers, for instance; it proves that Python argument lists are populated with references to copies of the values of the supplied parameters (because all objects are accessed by reference in Python).
Quote:
This differs from Java, as even integers, floats, and booleans are passed by reference.


No. You don't pass "integers,floats and booleans". Not by value, not by reference, not by anything. You don't pass objects in general. You pass references *by value*. It doesn't matter if it's a reference to an integer,a string, a list or a user-defined object. It's all the same to Python. All the examples you gave are explained by that simple notion: If you pass copies of references, any reassignment of the reference isn't visible outside of the scope of the function, but of course any operation done to the referenced object is.
Quote:Original post by mikeman
Quote:Original post by Kylotan
You're blurring the 2 concepts beyond the point of usefulness however. It's exactly the same in C or C++, or Java - the called function ends up with a copy of the reference or the pointer. At the low level it's copying a value which represents a memory location, but in language terms it's still "pass by reference" because changes made to an argument in the called function are reflected in the corresponding object in the calling function.


Not at all. Consider this simple Python function that gets an angle and returns both the sine and the cosine, ie it needs to return 2 values:

*** Source Snippet Removed ***

Claiming that Python uses pass-by-reference would be a big mistake, as the programmer would naturally expect the above code to work just as it works with C++(where references are regularly used to for multiple return values).


But that is not because the objects are not passed by reference, but because assignment works differently to the way it does in C++. The references themselves are handled in the function call exactly as they would be in C++. Only the assignment operation differs.
Quote:Original post by Kylotan
Quote:Original post by mikeman
Quote:Original post by Kylotan
You're blurring the 2 concepts beyond the point of usefulness however. It's exactly the same in C or C++, or Java - the called function ends up with a copy of the reference or the pointer. At the low level it's copying a value which represents a memory location, but in language terms it's still "pass by reference" because changes made to an argument in the called function are reflected in the corresponding object in the calling function.


Not at all. Consider this simple Python function that gets an angle and returns both the sine and the cosine, ie it needs to return 2 values:

*** Source Snippet Removed ***

Claiming that Python uses pass-by-reference would be a big mistake, as the programmer would naturally expect the above code to work just as it works with C++(where references are regularly used to for multiple return values).


But that is not because the objects are not passed by reference, but because assignment works differently to the way it does in C++. The references themselves are handled in the function call exactly as they would be in C++. Only the assignment operation differs.


Assignment just copies the value from one variable to another. There's nothing special about that. For example:

-----------------
a=str("Hello");
b=a
-----------------

1)a=str("Hello"):
str() creates a string object and returns a reference to it. Then, the assignment operator copies the value of the reference to "a".

2)b=a
The value of "a" is copied to "b". Now a and b reference the same object.

I don't understand why you need to think in terms of "overwriting and not rebinding" as you mentioned earlier when things are pretty clear. Variables in python are "objects" that hold a reference. If you want an equivalent in C++, you can observe the behaviour of Boost::Python. For instance:

void foo(boost::python::object a){   a=str("World");}boost::python::object myvar;myvar=str("Hello");foo(myvar);//myvar is still "Hello"



boost::python::object represents a Python variable(integer,string,list,callable,anything) and is passed by value, exactly like Python does. Now:


void foo(boost::python::object& a){  a=str("World");}boost::python::object myvar;myvar=str("Hello");foo(myvar);//myvar is "World"


That is passing by reference. The thing is, Python doesn't have that option.

[Edited by - mikeman on July 8, 2006 3:21:24 PM]
Er, I think you're both right, based on my memory of all the previous times I thought about this; it's just different ways of looking at it. Assignment does work differently - rebinding rather than changing all state - and thus it's *as if* it were pass by reference but with a different assignment operator (that - well - didn't actually assign back to the reference, which makes it rather strange to call it pass by reference after all), whereas it's more clearly thought of as pass-reference-by-pointer (especially if you're writing an implementation).
Quote:Original post by Zahlman
Er, I think you're both right, based on my memory of all the previous times I thought about this; it's just different ways of looking at it. Assignment does work differently - rebinding rather than changing all state - and thus it's *as if* it were pass by reference but with a different assignment operator (that - well - didn't actually assign back to the reference, which makes it rather strange to call it pass by reference after all), whereas it's more clearly thought of as pass-reference-by-pointer (especially if you're writing an implementation).


I just don't understand this "rebinding" thing. When I do 'a=str("mikeman")' it's not like the left side of the assignment is a reference and the right side is an actual object, so the assignment operator "binds" a to "mikeman". Both sides are references, so in my mind the assignment works pretty straightforward, all you have to do is keep in mind that everything you manipulate in Python is a reference. Kind of like a smart pointer in C++, except that it's untyped(it can reference any type of object).
*sigh*

The "rebinding" process is that of cloning a remote control and putting it into a coffee cup (replacing whatever remote control was there before). The "assignment" process, if Java (or Python) provided it, would be that of overwriting all the contents of the Cat object with those of some other Cat object (possibly making adjustments for the fact that its location is different). That's what happens when you invoke a C++ operator= via a reference.

The difference being, Python only has objects and no primitives, so you don't need as complicated of an explanation.

We all know how it works here; I'm just trying to translate the different explanations to each other's proponents :s

This topic is closed to new replies.

Advertisement