1

I'm writing a python module in C and I need to have it call a Python function with one of the arguments passed by "reference". The end result should be that what the Python function does to the argument gets saved into the original C variable.

int      *resume;      // this int is actually passed in to this body of code
PyObject *resumeInt;   // which was originally a C callback func for libnids, but
PyObject *ret;         // I removed/rewrote most of this code for clarity

resumeInt = Py_BuildValue("i",-1);
ret = PyObject_CallFunction(tcpResumeFunc, "(O)", resumeInt);    

*resume = PyInt_AsLong(resumeInt);
Py_DECREF(ret);
Py_DECREF(resumeInt);

To test, I had the Python function that tcpResumeFunc represents modify the passed-in integer to = 5. When I print out *resume at the end of this code, however, it retains it's initial -1 value. I know I am misunderstanding something about how the API works. Any suggestions?

Amro
  • 123,847
  • 25
  • 243
  • 454
Caleb Hearon
  • 256
  • 4
  • 14

1 Answers1

4

I think you're misunderstanding something about how variables work in Python. Variables in Python do not contain values; they refer to them - like in Java, except there are no "primitives" (not even int). Assignment to a variable doesn't overwrite the old value; it simply causes the variable to refer to the new value and cease referring to the old one (which may then be garbage-collected if nothing else refers to it).

Why not just actually return (and use) a value (since Python functions always return something anyway, even if it's just the implicit return of None)?


Edit: Working through an example, because it's too long for a comment reply.

From C, we call PyObject_CallFunction, which hands a PyObject* over to the Python function. In Python, the function parameter (we'll call it spam) now refers to the pointed-at PyObject.

When we write spam += 6 in the function, that is the same as spam = spam + 6. The bytecode interpreter gets the PyObject that represents the value 6, runs a special bytecode that inspects the value represented by the two objects, adds them, and creates a new PyObject (or fetches one from a cache) that represents the sum.

Then spam is rebound to the new object; but the spam variable-reference is on the Python side of the memory fence, and is not the same thing as the PyObject* on the C side of the fence.

Thus, resumeInt does not get pointed at the newly created/fetched PyObject.

It is actually behaving the exact same way as it would calling the Python function from Python. Try it:

def fry(spam):
  spam += 1

eggs = 3
fry(eggs)
eggs # still 3!

This happens because rebinding spam does not affect eggs, which is a separate variable. We are not passing by reference; we are passing a reference by value. Just like in Java.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • I do understand that, (I think) I assumed that since *resumeInt referred to the same object, after passing it to PyObject_CallFunction I would be left with the new *resumeInt as modified by the Python function called. Then I just convert it back to a C variable with PyInt_AsLong. Is there something wrong with that idea? Your suggestion does sound a bit more elegant and easier, but I'd like to understand this. – Caleb Hearon Dec 03 '10 at 05:01
  • Ah, after your edit it makes more sense. So what I'm trying to do would probably be impossible. I will modify the code to have it return the value. I'd vote up but I don't have enough reputation; great answer though, thanks a lot! – Caleb Hearon Dec 03 '10 at 05:41