I'm trying to decorate a function by replacing it with a instance of a callable class:
class FunctionFaker( object ):
def __init__( self, f ):
self.f= f
def empty_function( self ):
pass
def __call__( self, *args, **kwargs ):
self.f( *args, **kwargs)
def fakefunction( f ):
'''a decorator that transforms a function into a FunctionFaker'''
return FunctionFaker(f)
@fakefunction
def dosomething():
pass
dosomething.empty_function()
dosomething()
This works as expected.
However, as soon as I try to decorate a class method:
class Test( object ):
@fakefunction
def dosomething(self):
pass
t=Test()
t.dosomething.empty_function()
t.dosomething()
I get a TypeError: dosomething() takes exactly 1 argument (0 given)
.
Now, I think I can answer the why :
To support method calls, functions include the
__get__()
method for binding methods during attribute access. This means that all functions are non-data descriptors which return bound or unbound methods depending whether they are invoked from an object or a class.
So, FunctionFaker, not being a function, doesn't have the said descriptor, thus not mangling the arguments.
How can I implement a callable class that is able to replace a instance method?
I just realized I can simply implement
__get__
and return atypes.MethodType
, but I don't really understand how doing so still enables you to call empty_function.
It's because MethodType
has a __getattribute__
method that delegates unknown attributes to its im_func
:
>>> t.dosomething
<bound method Test.? of <__main__.Test object at 0x107639550>>
>>> 'empty_function' in dir(t.dosomething)
False
>>> t.dosomething.__getattribute__
<method-wrapper '__getattribute__' of instancemethod object at 0x109583aa0>
>>> t.dosomething.__getattribute__('empty_function')
<bound method FunctionFaker.empty_function of <__main__.FunctionFaker object at 0x1095f2510>>
Of course in CPython, the C API doesn't exactly mirror the Python-level distinction between __getattribute__
and __getattr__
, so the way it's really implemented is with a custom getattro
slot. You can read the details in the source .
Does it simply become an attribute of the
MethodType
instance?
Yes, but only dynamically, by giving you the attribute of the underlying callable.
I don't think they specifically intended to enable class instances substituting for functions with method descriptors of their own. But this support is needed for even simple cases of attaching attributes to methods. For example, with a standalone function, you can use a function attribute for, eg, a memoization cache, or lazy-initialize-on-first-call setup. If MethodType
didn't delegate attribute access to its im_func
object, moving such a function into a class would break it, and the developer wouldn't be able to fix it unless he knew how descriptors worked and rewrote the method in an ugly way.
In fact, up to 2.3, methods didn't even have a __dict__
; as you can see from the source , all attributes except for the C slots were delegated to im_func
(by effectively duplicating the normal machinery to delegate everything to im_func
but wrap errors). There was some debate about this, which you could probably find by searching the python-dev archives for a post by Christian Tismer in the pre-2.4 period with a relevant-looking subject (it may be this thread , but I didn't read the whole thing…). From 2.4 on, methods now do the normal lookup mechanism (except for the special case of __doc__
), and only delegate to im_func
if it fails.
And is this a sane thing to do?
It's a little strange, and it might be simpler to add the empty_function
attribute to the function object, instead of wrapping it in a class… but I don't think it's too unreasonable. (I assume you're asking about your code, not about how MethodType
and descriptors are implemented.)
You're on the right track. You want your class to also be a descriptor :
import types
class FunctionFaker( object ):
def __init__(self, f):
self.f= f
def empty_function(self):
pass
def __call__(self, *args, **kwargs):
self.empty_function()
self.f(*args, **kwargs)
def __get__(self, instance, cls=None):
# see https://docs.python.org/2/howto/descriptor.html#functions-and-methods
return types.MethodType(self, instance, cls)
@FunctionFaker
def foo(arg1):
print "in foo", arg1
class Bar(object):
@FunctionFaker
def bar(self, arg1):
print "in bar", self, arg1
foo('hello') # in foo hello
Bar().bar('world') # in bar <__main__.Bar object at 0x7fc50b90fb10> world
Bar().bar.empty_function()
Now, if your decorator is bound to a class, it behaves like a descriptor (binding the proper instance to self
in the decorated function), and if it isn't bound to a class, it behaves as a normal decorator. Neat.
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.