Decorating a class function with the called instance

I am trying to decorate a function by replacing it with an instance of the called 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 the 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 why :

To support method calls, functions include the __get__() method for binding methods while accessing attributes. This means that all functions are non-given descriptors that return related or unrelated methods depending on whether they are called from an object or class.

So, FunctionFaker, which is not a function, does not have the specified handle, so it does not distort the arguments.

How can I implement a called class capable of replacing an instance method?

+5
source share
2 answers

I just realized that I could just implement __get__ and return types.MethodType , but I really don't understand how this still allows calling an empty_function.

This is 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 the CPython API, C does not accurately reflect the Python level difference between __getattribute__ and __getattr__ , so the way it is really implemented is with custom getattro . You can read the details in the source .

MethodType it just become an attribute of the MethodType instance?

Yes, but only dynamically, providing you with an attribute of the underlying caller.

I do not think that they were specifically intended to include class instances that replace functions with method descriptors. But this support is necessary even for simple cases of attaching attributes to methods. For example, using a stand-alone function, you can use the function attribute for, for example, the memoization cache or lazy-initialize-on-first-call. If MethodType did not delegate access to the attribute to the im_func object, moving such a function to the class would violate it, and the developer would not be able to fix it if he did not know how the descriptors work and rewrote the method in an ugly way.

In fact, prior to 2.3, methods did not even have __dict__ ; as you can see from the source , all attributes except C slots were delegated to im_func (by effectively duplicating the usual mechanism for delegating everything to im_func , but wrap errors). There was some debate about this that you could probably find by searching python-dev archives for Christian Tysmer's message in the pre-2.4 period with the corresponding object (this could be this thread , but I haven't read all this ...) . Starting with version 2.4, methods now perform the usual search mechanism (except for the special case __doc__ ) and pass only im_func if it fails.

And is that a reasonable thing?

This is a bit weird, and it would be easier to add the empty_function attribute to the function object rather than wrapping it in a class ... but I don't think this is too unreasonable. (I assume that you are asking about your code, not about how MethodType and descriptors are implemented.)

+2
source

You are on the right way. You want your class to also be a handle :

 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 associated with a class, it behaves like a descriptor (binding the corresponding instance to self in a decorated function), and if it is not associated with the class, it behaves like a normal decorator. Well maintained.

+1
source

Source: https://habr.com/ru/post/1204143/