Finding the instance reference of an object

Joe Strout joe at strout.net
Tue Nov 4 17:06:45 CET 2008


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





More information about the Python-list mailing list