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?
Python class decorator It is possible to use classes as decorators. For this, we need to implement the __call__ magic function. In the example, we use a class decorator to count the calls of a regular function. We call the update_wrapper function.
Decorating Classes. There are two different ways you can use decorators on classes. The first one is very close to what you have already done with functions: you can decorate the methods of a class.
Decorators are a very powerful and useful tool in Python since it allows programmers to modify the behaviour of function or class.
How to Make an Object Callable. Simply, you make an object callable by overriding the special method __call__() . __call__(self, arg1, .., argn, *args, **kwargs) : This method is like any other normal method in Python. It also can accept positional and arbitrary arguments.
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, e.g., 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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With