Decorating Class Methods - How to Pass the Instance to the Decorator

Decorating class methods - how to pass the instance to the decorator?

You need to make the decorator into a descriptor -- either by ensuring its (meta)class has a __get__ method, or, way simpler, by using a decorator function instead of a decorator class (since functions are already descriptors). E.g.:

def dec_check(f):
def deco(self):
print 'In deco'
f(self)
return deco

class bar(object):
@dec_check
def foo(self):
print 'in bar.foo'

b = bar()
b.foo()

this prints

In deco
in bar.foo

as desired.

How can I decorate an instance method with a decorator class?

tl;dr

You can fix this problem by making the Timed class a descriptor and returning a partially applied function from __get__ which applies the Test object as one of the arguments, like this

class Timed(object):
def __init__(self, f):
self.func = f

def __call__(self, *args, **kwargs):
print(self)
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret

def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)

The actual problem

Quoting Python documentation for decorator,

The decorator syntax is merely syntactic sugar, the following two function definitions are semantically equivalent:

def f(...):
...
f = staticmethod(f)

@staticmethod
def f(...):
...

So, when you say,

@Timed
def decorated(self, *args, **kwargs):

it is actually

decorated = Timed(decorated)

only the function object is passed to the Timed, the object to which it is actually bound is not passed on along with it. So, when you invoke it like this

ret = self.func(*args, **kwargs)

self.func will refer to the unbound function object and it is invoked with Hello as the first argument. That is why self prints as Hello.


How can I fix this?

Since you have no reference to the Test instance in the Timed, the only way to do this would be to convert Timed as a descriptor class. Quoting the documentation, Invoking descriptors section,

In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol: __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.

The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses.

However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead.

We can make Timed a descriptor, by simply defining a method like this

def __get__(self, instance, owner):
...

Here, self refers to the Timed object itself, instance refers to the actual object on which the attribute lookup is happening and owner refers to the class corresponding to the instance.

Now, when __call__ is invoked on Timed, the __get__ method will be invoked. Now, somehow, we need to pass the first argument as the instance of Test class (even before Hello). So, we create another partially applied function, whose first parameter will be the Test instance, like this

def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)

Now, self.__call__ is a bound method (bound to Timed instance) and the second parameter to partial is the first argument to the self.__call__ call.

So, all these effectively translate like this

t.call_deco()
self.decorated("Hello", world="World")

Now self.decorated is actually Timed(decorated) (this will be referred as TimedObject from now on) object. Whenever we access it, the __get__ method defined in it will be invoked and it returns a partial function. You can confirm that like this

def call_deco(self):
print(self.decorated)
self.decorated("Hello", world="World")

would print

<functools.partial object at 0x7fecbc59ad60>
...

So,

self.decorated("Hello", world="World")

gets translated to

Timed.__get__(TimedObject, <Test obj>, Test.__class__)("Hello", world="World")

Since we return a partial function,

partial(TimedObject.__call__, <Test obj>)("Hello", world="World"))

which is actually

TimedObject.__call__(<Test obj>, 'Hello', world="World")

So, <Test obj> also becomes a part of *args, and when self.func is invoked, the first argument will be the <Test obj>.

Can a decorator of an instance method access the class?

If you are using Python 2.6 or later you could use a class decorator, perhaps something like this (warning: untested code).

def class_decorator(cls):
for name, method in cls.__dict__.iteritems():
if hasattr(method, "use_class"):
# do something with the method and class
print name, cls
return cls

def method_decorator(view):
# mark the method as something that requires view's class
view.use_class = True
return view

@class_decorator
class ModelA(object):
@method_decorator
def a_method(self):
# do some stuff
pass

The method decorator marks the method as one that is of interest by adding a "use_class" attribute - functions and methods are also objects, so you can attach additional metadata to them.

After the class has been created the class decorator then goes through all the methods and does whatever is needed on the methods that have been marked.

If you want all the methods to be affected then you could leave out the method decorator and just use the class decorator.

Python decorators in classes

Would something like this do what you need?

class Test(object):
def _decorator(foo):
def magic( self ) :
print "start magic"
foo( self )
print "end magic"
return magic

@_decorator
def bar( self ) :
print "normal call"

test = Test()

test.bar()

This avoids the call to self to access the decorator and leaves it hidden in the class namespace as a regular method.

>>> import stackoverflow
>>> test = stackoverflow.Test()
>>> test.bar()
start magic
normal call
end magic
>>>

edited to answer question in comments:

How to use the hidden decorator in another class

class Test(object):
def _decorator(foo):
def magic( self ) :
print "start magic"
foo( self )
print "end magic"
return magic

@_decorator
def bar( self ) :
print "normal call"

_decorator = staticmethod( _decorator )

class TestB( Test ):
@Test._decorator
def bar( self ):
print "override bar in"
super( TestB, self ).bar()
print "override bar out"

print "Normal:"
test = Test()
test.bar()
print

print "Inherited:"
b = TestB()
b.bar()
print

Output:

Normal:
start magic
normal call
end magic

Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic

Class decorators for methods in classes

Class decorators accept the function as a subject within the __init__ method (hence the log message), so your decorator code should look like:

class PrintLog(object):

def __init__(self, function):
self.function = function

def __call__(self):
@wraps(self.function)
def wrapped(*args):
print('I am a log')
return self.function(*args)
return wrapped

Sorry if this doesn’t work, I’m answering on my mobile device.

EDIT:

Okay so this is probably not what you want, but this is the way to do it:

from functools import update_wrapper, partial, wraps

class PrintLog(object):

def __init__(self, func):
update_wrapper(self, func)
self.func = func

def __get__(self, obj, objtype):
"""Support instance methods."""
return partial(self.__call__, obj)

def __call__(self, obj, *args, **kwargs):
@wraps(self.func)
def wrapped(*args):
print('I am a log')
return self.func(*args)
return wrapped(obj, *args)

class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs

@PrintLog
def baz(self) -> None:
print('inside baz')

bar = foo('2')
print('running bar.baz()')
bar.baz()

The decorator has to have the __get__ method defined because you're applying the decorator to an instance method. How would a descriptor have the context of the foo instance?

Ref: Decorating Python class methods - how do I pass the instance to the decorator?

Use an instance method as a decorator within another class

You can use a staticmethod to wrap decorator. The inner func_wrap function of decorator contains an additional parameter in its signature: cls. cls can be used to access the ser attribute of the instance of App, and then the desired methods write and read can be called from cls.ser. Also, note that in your declarations, MySerial.write takes no paramters, but is passed the result of the wrapped function. The code below uses *args to prevent the TypeError which would otherwise be raised:

class MySerial():
def __init__(self):
pass # I have to have an __init__
def write(self, *args):
pass # write to buffer
def read(self):
pass # read to buffer
@staticmethod
def decorator(func):
def func_wrap(cls, *args, **kwargs):
cls.ser.write(func(cls, *args, **kwargs))
return cls.ser.read()
return func_wrap

class App():
def __init__(self):
self.ser = MySerial()
@MySerial.decorator
def myfunc(self):
# 'yummy_bytes' is written to the serial buffer via
# MySerial's decorator method
return 'yummy_bytes'

App().myfunc()

Class decorator for methods from other class

Functions are descriptors and that's what allows them to auto-bind self. The easiest way to deal with this is to implement decorators using functions so that this is handled for you. Otherwise you need to explicitly invoke the descriptor. Here's one way:

import functools

class MyDecoratorClass:
def __init__(self, method):
functools.update_wrapper(self, method)
self.method = method

def __get__(self, instance, owner):
return type(self)(self.method.__get__(instance, owner))

def __call__(self, *args, **kwargs):
# do stuff before
retval = self.method(*args, **kwargs)
# do stuff after
return retval

class Foobar:
def __init__(self):
# initialize stuff
pass

@MyDecoratorClass
def foo(self, x, y):
print(f"{[self, x, y]=}")

@MyDecoratorClass
def bar(spam):
print(f"{[spam]=}")

Foobar().foo(1, 2)
bar(3)

Here the __get__ method creates a new instance of MyDecoratorClass with the bound method (previously self.method was just a function since no instance existed yet). Also note that __call__ just calls self.method(*args, **kwargs) - if self.method is now a bound method, the self of FooBar is already implied.

Is it possible for a Python decorator decorating a instance method/classmethod to know the class the function will be bound to?

As jasonharper mentions, the class doesn't exist yet by the time the decorator is called, and so the function it receives is just a regular function (except that its name mentions the class it will be bound to).


For my problem, I ended up doing it attrs-style, using an additional decorator to decorate the class as well.

def include(f: Callable) -> Callable:
"""Add function `f` to SchemaBuilder."""
SchemaBuilder.append(f)
return f

class SchemaBuilder:
records: Dict[Type, Dict[Callable, Any]] = {}
temporary: List[Callable] = []

@classmethod
def append(cls, f: Callable):
"""Temporarily store the method in a list."""
cls.temporary.append(f)

@classmethod
def register(cls, target_cls: Type):
"""Associate all methods stored in the list with `target_cls`.

We rely on the fact that `target_cls` will be instantiated
(and thus this method called) as soon as all of its (immediate)
methods have been created.
"""
cls.records[target_cls] = {k: None for k in cls.temporary}
cls.temporary = []

# In use:

@SchemaBuilder.register # called later
class SomeClass:
@property
@include # called first
def some_property(self): # will be included
pass

@property
def some_other_property(self): # will not be included
pass

decorating class method with arguments

Adapting answer by @madjardi from this post to use arguments.

import functools

class Example:

con = "Connection"

def wrapper(func):
@functools.wraps(func)
def wrap(self, *args, **kwargs):
print("inside wrap")
return func(self, Example.con, *args, **kwargs) # use *args to pass objects down
return wrap

@wrapper
def method(self, con, arg):
print("METHOD {0} {1}".format(con, arg))

wrapper = staticmethod(wrapper)

e = Example()
e.method(1) # METHOD Connection 1

Python: How can i decorate function to change it into class method

What about this approach?

P.S. The code is not structurally optimized for the sake of clarity

from functools import wraps

class A:
pass

def add_class_method(cls):
def decorator(f):
@wraps(f)
def inner(_, *args, **kwargs):
return f(*args, **kwargs)

setattr(cls, inner.__name__, classmethod(inner))

return f

return decorator

def add_instance_method(cls):
def decorator(f):
@wraps(f)
def inner(_, *args, **kwargs):
return f(*args, **kwargs)

setattr(cls, inner.__name__, inner)

return f

return decorator

@add_class_method(A)
def foo():
return "Hello!"

@add_instance_method(A)
def bar():
return "Hello again!"

assert A.foo() == "Hello!"
assert A().bar() == "Hello again!"



Related Topics



Leave a reply



Submit