7

I currently have a system of classes that register callbacks and then call them when certain conditions are met. However I am running into some problems storing the function object.

A:

class Foo(object):
  _callback = None

  @classmethod
  def Register(cls, fn):
    cls._callback = fn

def Bar():
  print "Called"

Foo.Register(Bar)
Foo._callback()

input clear Python 2.7.10 (default, Jul 14 2015, 19:46:27) [GCC 4.8.2] on linux

Traceback (most recent call last):   File "python", line 12, in <module> 
TypeError: unbound method Bar() must be called with Foo instance as first argument (got nothing instead)

I am not sure why it requires a Foo instance when the function is not a member of Foo.

B:

class Foo(object):
  _callback = []

  @classmethod
  def Register(cls, fn):
    cls._callback.append(fn)

def Bar():
  print "Called"

Foo.Register(Bar)
Foo._callback[0]()

Why does the first version not work while the second version does? What functionality differs when adding it to a list instead.

1
  • " when the function is not a member of Foo." Yes, it is. You made it a member of Foo when you do cls._callback = fn Commented Jun 8, 2017 at 18:58

5 Answers 5

5

Whenever you assign a function to a class object, it becomes a method:

In [6]: Foo.baz = lambda self: 'baz'

In [7]: f = Foo()

In [8]: f.baz()
Out[8]: 'baz'

Note, this means you can dynamically add methods to classes, which will be visible on instantiated objects!

In [9]: Foo.anothermethod = lambda self, x: x*2

In [10]: f.anothermethod(4)
Out[10]: 8

From the docs:

If you still don’t understand how methods work, a look at the implementation can perhaps clarify matters. When an instance attribute is referenced that isn’t a data attribute, its class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.

Sign up to request clarification or add additional context in comments.

5 Comments

OP never instantiates his class, calls _callback as strictly a function.
@MadPhysicist not in Python 2, it is an "unbound method". In Python 3, the distinction between "unbound methods" and functions doesn't exist, and accessing the function from the class returns the plain function in Python 3, but in Python 2, an an unbound method object is returned instead. So in a repl sesh: Foo._callback evaluates to <unbound method Foo.Bar>
@MadPhysicist Note, you can access the plain object by using Foo._callback.im_func in Python 3.
@MadPhysicist totally agree.
I find this the easiest to understand without much python knowledge.
4

When you assign Bar to cls._callback, _callback becomes automatically an unbound method of Foo (since it is a function).

Therefore, when called, it is expected to get the instance as its first argument, resulting in failure in cases where it is passed nothing (expects instance) or passed an instance (Bar supports 0 arguments).

However, if you append Bar to a list instead, and then access the list element, you have the normal-old-good Bar ready for use without any bounding conventions hurting it - because it is merely conserved as an object, not a bound method.

Comments

3

.Assigning it is just adding it to the classes __dict__ which stores the references of the bound and unbound methods (ie functions). So, by doing this your just adding another reference to an unbound function which is expecting an instance as its first argument.

Here's an easier example:

class Foo:
    test = lambda x: x

Foo.test("Hello world!")

Error:

unbound method <lambda>(): must be called....

Comments

3

You still have your function object. However, calling the function from the class transforms it into an unbound method each time it is accessed via the class:

Note that the transformation from function object to (unbound or bound) method object happens each time the attribute is retrieved from the class or instance.

(the above is found under user-defined methods of the Python 2 data model documentation)

It is therefore required that the function which is now invoked via a class be passed an instance.

Note that unbound methods only exist in Python 2, so your code is valid and will work in Python 3.

You can retrieve the function object from the unbound method by accessing its im_func attribute:

Foo._callback.im_func()
# Called

The second case stores your function object in a container, it has no direct business with the class and can be retrieved via say indexing and called normally.

1 Comment

I find this to be the clearest answer. Could you please provide a link for the quote?
2

The documentation actually provides an example very similar to yours. In the description of "Method Objects":

When an instance attribute is referenced that isn’t a data attribute, its class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object.

and from "Random Remarks":

It is not necessary that the function definition is textually enclosed in the class definition: assigning a function object to a local variable in the class is also ok.

The latter quote is followed by the example I am referring to.

Basically, the idea is that any function named in a class definition becomes a method of that class. Since you are not instantiating your class, your method never gets bound to an instance object (hence ... unbound method Bar() ...). Methods are always called with a first parameter containing the object on which they are invoked, whether you like it or not.

This is the reason that you need to have a special decorator for @classmethod by the way. The decorator wraps the function you created to give it the required interface, but pass the class object instead of the instance object to the wrapped function that you defined.

Since your second version creates _callback as a data object, it just stores a normal function pointer in a list and calls it as-is.

If you were to try to pass in something like None to Foo._callback(None), you will get rid of the original error you are getting, but of course then run into the problem of passing in a parameter the function was not expecting. You can work around this issue by giving Bar an argument (which you would ignore in this case):

def Bar(self):
    print "Called"

2 Comments

your method never gets bound to an object, so a class is not an object?
@MosesKoledoye. Good catch. Corrected to "instance object"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.