Class method decorator with self arguments?
Yes. Instead of passing in the instance attribute at class definition time, check it at runtime:
def check_authorization(f):
def wrapper(*args):
print args[0].url
return f(*args)
return wrapper
class Client(object):
def __init__(self, url):
self.url = url
@check_authorization
def get(self):
print 'get'
>>> Client('http://www.google.com').get()
http://www.google.com
get
The decorator intercepts the method arguments; the first argument is the instance, so it reads the attribute off of that. You can pass in the attribute name as a string to the decorator and use getattr
if you don't want to hardcode the attribute name:
def check_authorization(attribute):
def _check_authorization(f):
def wrapper(self, *args):
print getattr(self, attribute)
return f(self, *args)
return wrapper
return _check_authorization
Python passing self to the decorator
This is how you do it:
def log_real_decorator(f):
@wraps(f)
def wrapper(self, *args, **kw):
print "I am the decorator, I know that self is", self, "and I can do whatever I want with it!"
print "I also got other args:", args, kw
f(self, *args, **kw)
# ^ pass on self here
return wrapper
Simply pass on self
.
Or
If you want to create a generic decorator that can both be used for class and methods you can simply do this:
def log_real_decorator(f):
@wraps(f)
def wrapper(*args, **kw):
# Do something here
f(*args, **kw)
return wrapper
Also, the decorator that you have created is used when you need to pass some parameters to the decorator(like @log_real_decorator(some_param=1)
. In your case, you don't need that, instead what you should do is create a decorator with two nested functions as I have done above and call it as @log_real_decorator
.
Why is 'self' not in args when using a class as a decorator?
It's a known problem (see here). I actually ran into this same issue when implementing a class decorator in one of my projects a while back.
To fix, I added the below method to my class - which you can also add to your DecoratorClass
, and then it should all work without surprises.
def __get__(self, instance, owner):
"""
Fix: make our decorator class a decorator, so that it also works to
decorate instance methods.
https://stackoverflow.com/a/30105234/10237506
"""
from functools import partial
return partial(self.__call__, instance)
Also, do see the linked SO answer for an example of why this is.
Passing a method of self to decorator as an argument
I think you can just pass A.new_func
. In the wrapper, self
will be one of the *args
, so it will be passed along correctly.
class A:
def __init__(self):
self.x = 1
@refactor_factory(A.new_func)
def old_func(self):
return 2
def new_func(self):
return 2
How to add type annotation to self parameter of the decorator of class method?
Two useful tricks:
- Forward-declare types as string literals, e.g.
self: 'SomeClass'
- Use callable protocols for more flexibility in defining callable
TypeVar
s.
import functools
from typing import cast, Any, Callable, Protocol, TypeVar
class SomeMethod(Protocol):
def __call__(
_self,
self: 'SomeClass',
*args: Any,
**kwargs: Any
) -> Any: ...
_SomeMethod = TypeVar("_SomeMethod", bound=SomeMethod)
def something_todo(f: _SomeMethod) -> _SomeMethod:
@functools.wraps(f)
def wrapper(self: SomeClass, *args, **kwargs):
# do something
return f(self, *args, **kwargs)
return cast(_SomeMethod, wrapper)
class SomeClass:
def __init__(self):
# init here
pass
@something_todo
def some_method(self, *args, **kwargs):
# some process
pass
@something_todo
def foo():
print('error: Value of type variable "_SomeMethod" of "something_todo" cannot be "Callable[[], None]"')
How to pass self into a decorator?
Here's an example of doing it with a class decorator as I tried to describe to you in the comments. I filled-in a few undefined references in your question and used a super-simplified version of your cache_response
function decorator, but hopefully this will convey the idea concretely enough for you to be able adapt it to your real code.
import inspect
import types
class Constructor(object):
def __init__(self, memoize_for_request=True, params=None):
self.memoize_for_request = memoize_for_request
self.params = params
def __call__(self):
def key_func():
print('key_func called with params:')
for k, v in self.params.items():
print(' {}: {!r}'.format(k, v))
key_func()
def cache_response(key_func):
def decorator(fn):
def decorated(*args, **kwargs):
key_func()
fn(*args, **kwargs)
return decorated
return decorator
def example_class_decorator(cls):
key_func = Constructor( # define key_func here using cls.key
memoize_for_request=True,
params={'updated_at': cls.key} # use decorated class's attribute
)
# create and apply cache_response decorator to marked methods
# (in Python 3 use types.FunctionType instead of types.UnboundMethodType)
decorator = cache_response(key_func)
for name, fn in inspect.getmembers(cls):
if isinstance(fn, types.UnboundMethodType) and hasattr(fn, 'marked'):
setattr(cls, name, decorator(fn))
return cls
def decorate_me(fn):
setattr(fn, 'marked', 1)
return fn
class CacheMix(object):
def __init__(self, *args, **kwargs):
super(CacheMix, self).__init__(*args, **kwargs)
@decorate_me
def list(self, *args, **kwargs):
classname = self.__class__.__name__
print('list() method of {} object called'.format(classname))
@example_class_decorator
class ListView(CacheMix):
key = 'test_key'
listview = ListView()
listview.list()
Output:
key_func called with params:
updated_at: 'test_key'
list() method of ListView object called
Access self from decorator
Since you're decorating a method, and self
is a method argument, your decorator has access to self
at runtime. Obviously not at parsetime, because there are no objects yet, just a class.
So you change your decorator to:
def decorator(func):
def _decorator(self, *args, **kwargs):
# access a from TestSample
print 'self is %s' % self
return func(self, *args, **kwargs)
return _decorator
Related Topics
How to Udp Multicast in Python
How to Print a Generator Expression
How to Display Custom Values on a Bar Plot
Add a String Prefix to Each Value in a String Column Using Pandas
Trying to Mock Datetime.Date.Today(), But Not Working
How to Split and Parse a String in Python
Adding Directory to Sys.Path /Pythonpath
Using Python Iterparse for Large Xml Files
How to Solve a Pair of Nonlinear Equations Using Python
Convert Utf-8 with Bom to Utf-8 with No Bom in Python
Calling Filter Returns <Filter Object at ... >
How to Get Numbers After Decimal Point
Speeding Up Pandas.Dataframe.To_SQL with Fast_Executemany of Pyodbc
Serializing Class Instance to JSON
What Does a . in an Import Statement in Python Mean
Reading from a Frequently Updated File
How to Remove All Characters After a Specific Character in Python