Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function object called via class attribute fails

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.

like image 578
marsh Avatar asked Jun 08 '17 18:06

marsh


3 Answers

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.

like image 76
juanpa.arrivillaga Avatar answered Oct 18 '22 00:10

juanpa.arrivillaga


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.

like image 28
Uriel Avatar answered Oct 17 '22 23:10

Uriel


.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....
like image 3
Pythonista Avatar answered Oct 18 '22 00:10

Pythonista