Self access in function attribute

I am trying to add a decorator that adds called attributes to functions that return slightly different objects than the return value of the function, but will execute the function at some point.

The problem I am facing is that when the function object is passed to the decorator, it is not bound and does not contain the implicit self argument. When I call the created attribute function (i.e. string() ), I do not have access to self and cannot pass it to the original function.

 def deco(func): """ Add an attribute to the function takes the same arguments as the function but modifies the output. """ def string(*args, **kwargs): return str(func(*args, **kwargs)) func.string = string return func class Test(object): def __init__(self, value): self._value = 1 @deco def plus(self, n): return self._value + n 

When I go to execute the attribute created by the decorator, this is the error I get because args does not contain a self link.

 >>> t = Test(100) >>> t.plus(1) # Gets passed self implicitly 101 >>> t.plus.string(1) # Does not get passed self implicitly ... TypeError: plus() takes exactly 2 arguments (1 given) 

Is there a way to create such a decorator that can get a link to self ? Or is there a way to bind the added attribute function ( string() ) so that it is also called with the implicit argument self ?

+6
source share
2 answers

Here you can use descriptors :

 class deco(object): def __init__(self, func): self.func = func self.parent_obj = None def __get__(self, obj, type=None): self.parent_obj = obj return self def __call__(self, *args, **kwargs): return self.func(self.parent_obj, *args, **kwargs) def string(self, *args, **kwargs): return str(self(*args, **kwargs)) class Test(object): def __init__(self, value): self._value = value @deco def plus(self, n): return self._value + n 

so that:

 >>> test = Test(3) >>> test.plus(1) 4 >>> test.plus.string(1) '4' 

This requires an explanation. deco is a decorator, but it is also a descriptor . A descriptor is an object that defines the alternative behavior that should be invoked when an object is considered as an attribute of its parent. Interestingly, border methods themselves are implemented using the descriptor protocol

This is a sip. Let's see what happens when we run the sample code. First, when we define the plus method, we use the deco decorator. Now we usually see decorator functions, and the return value of the function is the result of decorating. Here we use the class as a decorator. As a result, Test.plus not a function, but rather an instance of the deco type. This instance contains a link to the plus function that we want to wrap.

The deco class has a __call__ method, which allows instances to act as functions. This implementation simply passes the arguments to the plus function referenced. Note that the first argument will be a reference to the Test instance.

The tricky part is implementing test.plus.string(1) . To do this, we need a reference to the Test instance, the plus instance is an attribute. To do this, we use the descriptor protocol. That is, we define the __get__ method, which will be called whenever the deco instance gets access as an attribute of some instance of the parent class. When this happens, it stores the parent object within itself. Then we can simply implement plus.string as a method of the deco class and use the reference to the parent object stored in the deco instance to get the Test instance to which plus belongs.

This is a lot of magic, so here's a disclaimer: although it looks cool, it's probably not a good idea to implement something like this.

+6
source

You need to decorate your function during instance creation (before creating the instance method). You can do this by overriding the __new__ method:

 class Test(object): def __new__(cls, *args_, **kwargs_): def deco(func): def string(*args, **kwargs): return "my_str is :" + str(func(*args, **kwargs)) # *1 func.__func__.string = string return func obj = object.__new__(cls, *args_, **kwargs_) setattr(obj, 'plus', deco(getattr(obj, 'plus'))) return obj def __init__(self, value): self._value = 1 def plus(self, n): return self._value + n 

Demo:

 >>> t = Test(100) >>> t.plus(1) >>> t.plus.string(5) >>> 'my_str is :6' 

<sub> 1. Since python does not allow you to access the attribute of a real instance during installation, you can use the __func__ method to access the real object of the instance method function. Sub>

+4
source

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


All Articles