Accessing self in a function attribute
python, python-decorators
Solution
You can use descriptors here:
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 warrants an explanation. `deco` is a decorator, but it is also a descriptor. A descriptor is an object that defines alternative behavior that is to be invoked when the object is looked up as an attribute of its parent. Interestingly, bounds methods are themselves implemented using the descriptor protocol
That's a mouthful. Let's look at what happens when we run the example code. First, when we define the `plus` method, we apply the `deco` decorator. Now normally we see functions as decorators, and the return value of the function is the decorated result. Here we are using a class as a decorator. As a result, `Test.plus` isn't a function, but rather an instance of the `deco` type. This instance contains a reference to the `plus` function that we wish to wrap.
The `deco` class has a `__call__` method that allows instances of it to act like functions. This implementation simply passes the arguments given to the `plus` function it has a reference to. Note that the first argument will be the reference to the `Test` instance.
The tricky part comes in implementing `test.plus.string(1)`. To do this, we need a reference to the `test` instance of which the `plus` instance is an attribute. To accomplish this, we use the descriptor protocol. That is, we define a `__get__` method which will be invoked whenever the `deco` instance is accessed as an attribute of some parent class instance. When this happens, it stores the parent object inside itself. Then we can simply implement `plus.string` as a method on the `deco` class, and use the reference to the parent object stored within the `deco` instance to get at the `test` instance to which `plus` belongs.
This is a lot of magic, so here's a disclaimer: Though this looks cool, it's probably not a great idea to implement something like this.
Problem
I'm trying to add a decorator that adds callable 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'm running into is that when the function object is passed into the decorator, it is unbound and doesn't contain the implicit `self` argument. When I call the created attribute function (ie. `string()`), I don't have access to `self` and can't pass it into 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` doesn't contain the `self` reference. ``` >>> 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 a decorator like this that can get a reference to `self`? Or is there a way to bind the added attribute function (`string()`) so that it also gets called with the implicit `self` argument?