Class Method Decorator with Self Arguments

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:

  1. Forward-declare types as string literals, e.g. self: 'SomeClass'
  2. Use callable protocols for more flexibility in defining callable TypeVars.
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



Leave a reply



Submit