20

I am trying to assign a function defined elsewhere to a class variable so I can later call it in one of the methods of the instance, like this:

from module import my_func

class Bar(object):
    func = my_func
    def run(self):
        self.func()  # Runs my function

The problem is that this fails because when doing self.func(), then the instance is passed as the first parameter.

I've come up with a hack but seems ugly to me, anybody has an alternative?

In [1]: class Foo(object):
   ...:     func = lambda *args: args
   ...:     def __init__(self):
   ...:         print(self.func())
   ...:

In [2]: class Foo2(object):
   ...:     funcs = [lambda *args: args]
   ...:     def __init__(self):
   ...:         print(self.funcs[0]())
   ...:

In [3]: f = Foo()
(<__main__.Foo object at 0x00000000044BFB70>,)

In [4]: f2 = Foo2()
()

Edit: The behavior is different with builtin functions!

In [13]: from math import pow

In [14]: def pow_(a, b):
   ....:     return pow(a, b)
   ....:

In [15]: class Foo3(object):
   ....:     func = pow_
   ....:     def __init__(self):
   ....:         print(self.func(2, 3))
   ....:

In [16]: f3 = Foo3()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-c27c8778655e> in <module>()
----> 1 f3 = Foo3()

<ipython-input-15-efeb6adb211c> in __init__(self)
      2     func = pow_
      3     def __init__(self):
----> 4         print(self.func(2, 3))
      5

TypeError: pow_() takes exactly 2 arguments (3 given)

In [17]: class Foo4(object):
   ....:     func = pow
   ....:     def __init__(self):
   ....:         print(self.func(2, 3))
   ....:

In [18]: f4 = Foo4()
8.0
astrojuanlu
  • 6,744
  • 8
  • 45
  • 105

1 Answers1

29

Python functions are descriptor objects, and when attributes on a class accessing them an instance causes them to be bound as methods.

If you want to prevent this, use the staticmethod function to wrap the function in a different descriptor that doesn't bind to the instance:

class Bar(object):
    func = staticmethod(my_func)
    def run(self):
        self.func()

Alternatively, access the unbound function via the __func__ attribute on the method:

def run(self):
    self.func.__func__()

or go directly to the class __dict__ attribute to bypass the descriptor protocol altogether:

def run(self):
    Bar.__dict__['func']()

As for math.pow, that's not a Python function, in that it is written in C code. Most built-in functions are written in C, and most are not descriptors.

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • The last code block is failing (`Bar object has no attribute Bar`) and if I remove `self` then I get `TypeError: unbound method must be called with instance as first argument` – astrojuanlu Jun 29 '16 at 11:23
  • @astrojuanlu: ah, yes, sorry, Python 2. I'll update that option to bypass the descriptor protocol. – Martijn Pieters Jun 29 '16 at 11:27
  • If I may abuse your patience, would you explain why the behaviour with descriptors is different when we use builtin functions? I just tried `math.pow` and the instance is not passed as a first argument! I updated my question with extra information – astrojuanlu Jun 29 '16 at 13:53
  • 2
    @astrojuanlu: `math.pow` is not a plain Python function, it is not a descriptor object. Not all built-in functions (written in C code) are descriptors. – Martijn Pieters Jun 29 '16 at 14:19