Sreram
Sreram

Reputation: 501

Is it possible to convert a python function into a class?

I am new to Python coming from C++ background and this is the first time I am seeing a language which contains nothing but objects. I have just learned that class and functions are also just objects. So, is there a way to convert the following function to a class?

In [1]: def somefnc(a, b):
...:     return a+b
...: 

I have first tried assigning the __call__ variable to None to take away the "callable nature" from the function. But as you can see, the __call__ was successfully replaced by None but this didn't cause the function to stop adding numbers when called, though, somefnc.__call__(1,3) was working before assigning somefnc.__call__ to None

In [2]: somefnc.__dict__ 
Out[2]: {}

In [3]: somefnc.__call__
Out[3]: <method-wrapper '__call__' of function object at 0x7f282e8ff7b8>

In [4]: somefnc.__call__ = None

In [5]: x = somefnc(1, 2)

In [6]: print(x)
3

In [7]: somefnc.__call__

In [8]: print(somefnc.__call__(1, 2))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
ipython-input-8-407663da97ca     
in <module>()
----> 1 print(somefnc.__call__(1, 2))

TypeError: 'NoneType' object is not callable

In [9]: print (somefnc(1,2))
3

In [10]: 

I am not doing this for developing purpose here, so claiming this to be a bad practice will not make any sense. I am just trying to understand Python very well. Of course, for development purpose, I could rather create a class than to convert a function to one!

After robbing the function off its ability to add two numbers, I am thinking of assigning a valid function to the attribute somefnc.__init__ and some members by modifying somefun.__dict__, to convert it to a class.

Upvotes: 3

Views: 3705

Answers (1)

Eli Korvigo
Eli Korvigo

Reputation: 10513

In Python functions are instances of the function class. So I'll give you a general answer about any class and instance.

In [10]: class Test:
    ...:     def __getitem__(self, i):
    ...:         return i
    ...:     

In [11]: t = Test()

In [12]: t[0]
Out[12]: 0

In [13]: t.__getitem__(0)
Out[13]: 0

In [14]: t.__getitem__ = None

In [15]: t[0]
Out[15]: 0

In [16]: t.__getitem__(0)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-c72f91d2bfbc> in <module>()
----> 1 t.__getitem__(0)

TypeError: 'NoneType' object is not callable

In Python all special methods (the ones with double underscores in the prefix and the postfix) are accessed from the class when triggered via operators, not from the instance.

In [17]: class Test2:
    ...:     def test(self, i):
    ...:         return i
    ...:     

In [18]: t = Test2()

In [19]: t.test(1)
Out[19]: 1

In [20]: t.test = None

In [20]: t.test(1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-22-261b43cb55fe> in <module>()
----> 1 t.test(1)

TypeError: 'NoneType' object is not callable

All methods are accessed via the instance first, when accessed by name. The difference is due to different search mechanics. When you access a method/attribute by name, you invoke __getattribute__ which will first search in the instance's namespace by default. When you trigger a method via operators, __getattribute__ is not invoked. You can see it in the disassembly.

In [22] import dis

In [23]: def test():
    ...:     return Test()[0]
    ...:     

In [24]: dis.dis(test)
  2           0 LOAD_GLOBAL              0 (Test)
              3 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
              6 LOAD_CONST               1 (0)
              9 BINARY_SUBSCR
             10 RETURN_VALUE

In [25]: def test2():
    ...:     return Test().__getitem__(0)
    ...: 

In [26]: dis.dis(test2)
  2           0 LOAD_GLOBAL              0 (Test)
              3 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
              6 LOAD_ATTR                1 (__getitem__)
              9 LOAD_CONST               1 (0)
             12 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             15 RETURN_VALUE

As you can see, there is no LOAD_ATTR in the first case. The [] operator is assembled as a special virtual-machine instruction BINARY_SUBSCR.

Upvotes: 5

Related Questions