4

I would like to do something like the following:

class Foo(object):
    def __init__(self):
        self.member = 10
        pass

def factory(foo):
    foo = Foo()

aTestFoo = None
factory(aTestFoo)

print aTestFoo.member

However it crashes with AttributeError: 'NoneType' object has no attribute 'member': the object aTestFoo has not been modified inside the call of the function factory.

What is the pythonic way of performing that ? Is it a pattern to avoid ? If it is a current mistake, how is it called ?

In C++, in the function prototype, I would have added a reference to the pointer to be created in the factory... but maybe this is not the kind of things I should think about in Python.

In C#, there's the key word ref that allows to modify the reference itself, really close to the C++ way. I don't know in Java... and I do wonder in Python.

Stephane Rolland
  • 38,876
  • 35
  • 121
  • 169
  • What are you actually trying to do here? Also, why do you need a factory function? – stranac Oct 09 '12 at 10:18
  • The code in question is relatively simple to answer about my concern. Of course a return in the factory function with a direct assignment would work. But as I have stumbled accross this, I try to undertand. **This problem** precisely – Stephane Rolland Oct 09 '12 at 10:22
  • Ok, so your problem is python doesn't pass objects by reference. – stranac Oct 09 '12 at 10:28
  • 2
    Yep... as the answers of delnan and rahul explains in details. – Stephane Rolland Oct 09 '12 at 10:31
  • 1
    take a look http://stackoverflow.com/questions/291978/short-description-of-python-scoping-rules, the answer is concise and to the particulate – Rahul Gautam Oct 09 '12 at 10:44

4 Answers4

12

Python does not have pass by reference. One of the few things it shares with Java, by the way. Some people describe argument passing in Python as call by value (and define the values as references, where reference means not what it means in C++), some people describe it as pass by reference with reasoning I find quite questionable (they re-define it to use to what Python calls "reference", and end up with something which has nothing to do with what has been known as pass by reference for decades), others go for terms which are not as widely used and abused (popular examples are "{pass,call} by {object,sharing}"). See Call By Object on effbot.org for a rather extensive discussion on the defintions of the various terms, on history, and on the flaws in some of the arguments for the terms pass by reference and pass by value.

The short story, without naming it, goes like this:

  • Every variable, object attribute, collection item, etc. refers to an object.
  • Assignment, argument passing, etc. create another variable, object attribute, collection item, etc. which refers to the same object but has no knowledge which other variables, object attributes, collection items, etc. refer to that object.
  • Any variable, object attribute, collection item, etc. can be used to modify an object, and any other variable, object attribute, collection item, etc. can be used to observe that modification.
  • No variable, object attribute, collection item, etc. refers to another variable, object attribute, collection items, etc. and thus you can't emulate pass by reference (in the C++ sense) except by treating a mutable object/collection as your "namespace". This is excessively ugly, so don't use it when there's a much easier alternative (such as a return value, or exceptions, or multiple return values via iterable unpacking).

You may consider this like using pointers, but not pointers to pointers (but sometimes pointers to structures containing pointers) in C. And then passing those pointers by value. But don't read too much into this simile. Python's data model is significantly different from C's.

9

You are making a mistake here because in Python

"We call the argument passing technique _call by sharing_,
because the argument objects are shared between the
caller and the called routine.  This technique does not
correspond to most traditional argument passing techniques
(it is similar to argument passing in LISP).  In particular it
is not call by value because mutations of arguments per-
formed by the called routine will be visible to the caller.
And it is not call by reference because access is not given
to the variables of the caller, but merely to certain objects."

in Python, the variables in the formal argument list are bound to the actual argument objects. the objects are shared between caller and callee; there are no "fresh locations" or extra "stores" involved.

(which, of course, is why the CLU folks called this mechanism "call- by-sharing".)

and btw, Python functions doesn't run in an extended environment, either. function bodies have very limited access to the surrounding environment.

Rahul Gautam
  • 4,749
  • 2
  • 21
  • 30
3

The Assignment Statements section of the Python docs might be interesting.

The = statement in Python acts differently depending on the situation, but in the case you present, it just binds the new object to a new local variable:

def factory(foo):
    # This makes a new instance of Foo,
    # and binds it to a local variable `foo`,
    foo = Foo()

# This binds `None` to a top-level variable `aTestFoo`
aTestFoo = None

# Call `factory` with first argument of `None`
factory(aTestFoo)

print aTestFoo.member

Although it can potentially be more confusing than helpful, the dis module can show you the byte-code representation of a function, which can reveal how Python works internally. Here is the disassembly of `factory:

>>> dis.dis(factory)
  4           0 LOAD_GLOBAL              0 (Foo)
              3 CALL_FUNCTION            0
              6 STORE_FAST               0 (foo)
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE        

What that says is, Python loads the global Foo class by name (0), and calls it (3, instantiation and calling are very similar), then stores the result in a local variable (6, see STORE_FAST). Then it loads the default return value None (9) and returns it (12)

What is the pythonic way of performing that ? Is it a pattern to avoid ? If it is a current mistake, how is it called ?

Factory functions are rarely necessary in Python. In the occasional case where they are necessary, you would just return the new instance from your factory (instead of trying to assign it to a passed-in variable):

class Foo(object):
    def __init__(self):
        self.member = 10
        pass

def factory():
    return Foo()

aTestFoo = factory()

print aTestFoo.member
dbr
  • 165,801
  • 69
  • 278
  • 343
1

Your factory method doesn't return anything - and by default it will have a return value of None. You assign aTestFoo to None, but never re-assign it - which is where your actual error is coming from.

Fixing these issues:

class Foo(object):
    def __init__(self):
        self.member = 10
        pass

def factory(obj):
    return obj()

aTestFoo = factory(Foo)
print aTestFoo.member

This should do what I think you are after, although such patterns are not that typical in Python (ie, factory methods).

Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284
  • I never said its a bug? But I have removed that - either way, I'm not sure how/what you expected your original code to do since you didn't return anything and simply printed a member of `None`. It would be the same result if had just did `print None.foo` – Burhan Khalid Oct 09 '12 at 10:26