On Nov 4, 2008, at 7:42 AM, Craig Allen wrote:

coming from C/C++ Python seemed to me call by reference, for the
pragmatic reason you said, modificaitons to function arguments do
affect the variable in the caller.  The confusing part of this then
comes when immutable objects are passed in.

Yes, you stepped in the wrong direction right away and led yourself into a morass of contradictions.

The correct way to look at it is that a Python reference is like a C++ pointer. C++ pointers are passed by value (unless you add & to explicitly make a parameter by-reference), yet you can still use them to mutate the objects they point to, right? Same thing in Python. Nothing at all mysterious going on here. Compare this:

typedef Spam* SpamPtr;   // (where Spam is some class)
// ...
void foo(SpamPtr spam)
{
        spam->count = 4;
}

When you call foo, it modifies the spam object passed in, even though the parameter is by-value. How? Because (looking more carefully), you didn't actually pass in a Spam object; you passed in a POINTER TO a Spam object. That pointer remained unchanged. You just used the pointer to change some other data living on the heap. This is the case exactly equivalent to Python:

def foo(spam):
        spam.count = 4;

Same thing here; the variable you pass in is a reference to a Spam object, and while that reference remains unchanged by the call, it is used to change some other data that lives on the heap.

Here's a C++ example that has no analog in Python, because it uses call-by-reference:

void throwOut(SpamPtr &spam)
{
        printf("throwing out %s\n", spam->brand);
        delete spam;
        spam = nil;
}

Now here, when you invoke throwOut on a SpamPtr, your own SpamPtr variable (the one that you pass in) actually gets set to nil. That's because the formal parameter here is just an alias of the actual parameter. You can't do that in Python; this attempt:

def throwOut(spam):
        print "throwing out %s\n", spam.brand
        spam = nil

would entirely fail to have any effect whatsoever on the Spam reference you pass in. "spam" here is just a local variable within the throwOut function, which has no connection to the variable passed in other than it gets a copy of its value (i.e., it initially refers to the same object as the actual parameter). This doesn't work, and the C++ throwOut function has no analog in Python, because Python has no call-by-reference.

Here's another C++ example that has no analog in Python, because it passes an object directly on the stack rather than a reference to it:

void bar(Spam spam)
{
        spam.count = 5;
}

This is the one that I know particularly confuses some users, because it LOOKS like what you could do in Python, and has the same behavior on the surface. But it's not analogous at all, because the "spam" local variable here (and presumably the one in the calling context) is an object stored directly on the stack, rather than a reference to an object on the heap. Python can't do that (nor can Java, nor REALbasic, etc.). This example is also call-by-value, but the value in this case is a type that has no analog in Python. Python object variables are references to objects on the heap, just like pointers in C++ to objects created with "new". So this example is a red herring.

I'd be very interested in hearing whether, as a C/C++ user, the above explanation is clear and makes sense to you.

Thanks,
- Joe


--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to