简体   繁体   中英

Python: calling a function as a method of a class

Let's start with some code:

def func(*x):
    print('func:', x)


class ABC:
    def __init__(self, f):
        self.f1 = f

    def f2(*x):
        print('f2:', x)

Now we do some tests:

>>> a = ABC(func)
>>> a.f1(10)
func: (10,)
>>> a.f2(10)
f2: (<__main__.ABC object at 0xb75381cc>, 10)
>>> a.f3 = func
>>> a.f3(10)
func: (10,)
>>> a.f1
<function func at 0xb74911ec>
>>> a.f2
<bound method ABC.f2 of <__main__.ABC object at 0xb75381cc>>
>>> a.f3
<function func at 0xb74911ec>

Note that func is a normal function and we are making it a method f1 of the class.

We can see that f2 is getting the class instance as the first argument, but f1 and f3 are not, even though all functions are called as class methods. We can also see that if we call a normal function as a method of a class, Python does not make a bound method from it.

So why is f1 or f3 NOT getting a class instance passed to it even when we are calling it as a method of a class? And also, how does Python know that we are calling an outer function as a method so that it should not pass an instance to it.

-- EDIT --

OK, so basically what I am doing wrong is that I am attaching the functions on the instance and NOT on the class object itself. These functions therefore simply become instance attributes. We can check this with:

>>> ABC.__dict__
... contents...
>>> a.__dict__
{'f1': <function func at 0xb74911ec>, 'f3': <function func at 0xb74911ec>}

Also note that this dict can not be assigned to:

>>> ABC.__dict__['f4'] = func
TypeError: 'dict_proxy' object does not support item assignment

You kind of partially answered your own question inspecting the object. In Python, objects behave like namespaces, so the first attribute points to a function and the second points to a method.

This is how you can add a method dynamically:

from types import MethodType

def func(*x):
    print('func:', x)


class ABC:
    def __init__(self, f):
        self.f1 = MethodType(f, self, self.__class__)

    def f2(*x):
        print('f2:', x)

if __name__ == '__main__':
    a = ABC(func)
    print a.f1(10)
    print a.f2(10)
    a.f3 = MethodType(func, a, ABC)
    print a.f3(10)

Note that it will bind the method to your instance, not to the base class. In order to monkeypatch the ABC class:

>>> ABC.f4 = MethodType(func, None, ABC)
>>> a.f4(1)
('func:', (<__main__.ABC instance at 0x02AA8AD0>, 1))

Monkeypatching is usually frowned upon in the Python circles, despite being popular in other dynamic languages (notably in Ruby when the language was younger).

If you ever resort to this powerful yet dangerous technique, my advice is:

  • never, ever override an existing class method. just don't.

That's because f1 and f3 are not class method they are just references to a global function defined in __main__ :

In [5]: a.f1
Out[5]: <function __main__.func>

In [8]: a.f3
Out[8]: <function __main__.func>

In [9]: a.f2
Out[9]: <bound method ABC.f2 of <__main__.ABC instance at 0x8ac04ac>>

you can do something like this to make a global function a class method:

In [16]: class ABC:
    def __init__(self,f):
        ABC.f1=f
    def f2(*x):    
        print('f2',x)
   ....:         

In [17]: a=ABC(func)

In [18]: a.f1(10)
('func:', (<__main__.ABC instance at 0x8abb7ec>, 10))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM