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 witha.__dict__['x']
, thentype(a).__dict__['x']
, and continuing through the base classes oftype(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
Is the Server Bundled with Flask Safe to Use in Production
Underscore _ as Variable Name in Python
What Is _Future_ in Python Used for and How/When to Use It, and How It Works
Writing a Dictionary to a CSV File with One Line for Every 'Key: Value'
When I Catch an Exception, How to Get the Type, File, and Line Number
Seeing Escape Characters When Pressing the Arrow Keys in Python Shell
How to Print Bold Text in Python
Pandas Dataframe Groupby Datetime Month
How to Determine the Language of a Piece of Text
Find Out How Much Memory Is Being Used by an Object in Python
Running Python on Windows for Node.Js Dependencies
Using Break in a List Comprehension
Change User-Agent for Selenium Web-Driver
Character Reading from File in Python
Matching Nested Structures with Regular Expressions in Python