Python decorator for method

Class method as a decorator

I have a class where I have multiple methods. I want to use one of the methods as a decorator for other methods. For this I am using following syntax:

@self.action def execute(self,req): 

By the moment, when the function created and decorated, the instance (even the class) does not exist yet, so you can’t do it like that. What could be done instead, depends on what action is supposed to do.

action is a base class method. There are many derived classes and each derived class has a method which performs a specific operation. Before and after each operation, I need to do certain operations which I have written in base class methods. But in every derived class, I have to repeat that code. Thus I have created a method called action in the base class which will act as a decorator.

@gliese581g: Just make it a function next to the Base class, and import it together with Base . Give it a leading underscore name so that from module import * will not include it and any Python developer worth its salt will recognize it as a private utility function.

3 Answers 3

You cannot use a method of the class while defining it; there is no self within the class nor is the class ‘baked’ yet to even access any class.

You can treat methods as functions to use as a decorator:

class SomeClass(): def action(func): # decorate return wrapper @action def execute(self, req): # something 

If action is defined on a base class, then you’d have to refer to the name via the base class:

class Base(): @staticmethod def action(func): # decorate return wrapper class Derived(Base): @Base.action def execute(self, req): # something 

For Python 2, you’d have to make action a static method here, as otherwise you get an unbound method that’ll complain you cannot call it without an instance as the first argument. In Python 3, you can leave off the @staticmethod decorator there, at least for the purposes of the decorator.

But note that action cannot then be used as a method directly; perhaps it should not be part of the class at all at that point. It is not part of the end-user API here, presumably the decorator is not used by consumers of the instances of these classes.

Sidestepping the issue of whether action should be part of the class, action could be defined as a static method ( @staticmethod def action(func). ).

what if we want to call another method of SomeClass in action ? We cannot access methods and properties of an instance of SomeClass using self.

Читайте также:  Theme fonts in html

@ddzzbbwwmm: there is no class yet when running the code inside the class body. You can treat it as a local namespace in a function instead, so reference other functions by their name.

Just beware that both the decorator and the decorated function are unbound methods, so you can only access the self (or cls for classmethods) in the inner scope of the decorator, and must manually bind the decorated method to the instance bound in the inner decorator.

class A: x = 5 y = 6 def decorate(unbound): def _decorator(self): bound = unbound.__get__(self) return bound() * self.x return _decorator @decorate def func(self): return self.y A().func() # 30!! 

Still trying to wrap my head around how decorators could be inherited and overridden.

Beware that for the decorator to work it can’t be bound to an instance. That is: there is no way to make this work

a = A() @a.decorate def func(*args): return 1 

Despite this pattern is much more common than the asked here.

At this point the question raises: is it a method at all or just code that you happen to hide in a class?

The only way to prevent the decorator being wrongfully bound is to declare it as a staticmethod, but then it must be in a previous super class because to be used it must be bound to the static class reference which would not be yet defined, just as the self.

class A: x = 1 @staticmethod def decorate(unbound): def _decorator(self): bound = unbound.__get__(self) return bound() * self.x return _decorator class B(A): @A.decorate def func(self): return 1 class C(): x = 2 @B.decorate def func(self): return 1 a = A() class D(): x = 3 @a.decorate def func(self): return 1 B().func() # 1 C().func() # 2 D().func() # 3 

But as you can see, there is no way for the decorator to use the state of its own class. class A from this last example just happens to be a mixin with a default x variable and an «unrelated» static decorator.

To overcome all of this, you can bind the staticmethod in your same class to an arbitrary type. Namely, the builtin type will do.

class A: x = 1 @staticmethod def decorate(unbound): def _decorator(self): bound = unbound.__get__(self) return bound() * self.x return _decorator @decorate.__get__(type) def func(self): return 1 class B: x = 2 @A.decorate def func(self): return 1 class C: x = 3 @(A().decorate) # Only for Python 3.9+, see PEP-614 def func(self): return 1 A().func() # 1 B().func() # 2 C().func() # 3 

But this features too much magic for my taste. And still not a method for my gut.

Источник

Class method decorator with self arguments?

How do I pass a class field to a decorator on a class method as an argument? What I want to do is something like:

class Client(object): def __init__(self, url): self.url = url @check_authorization("some_attr", self.url) def get(self): do_work() 

It complains that self does not exist for passing self.url to the decorator. Is there a way around this?

Читайте также:  This code is unreachable python перевод

The problem is that self doesn’t exist at function definition time. You need to make it into a partial function.

8 Answers 8

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 

is there a way to pass @staticmethod directly in decorator? (in general). I found that we can not reference Even class in decorator.

@ShivKrishnaJaiswal what exactly do you mean by passing @staticmethod directly in decorator? You can get rid of object reference requirement by using the @staticmethod decorator, however, it won’t solve the OP’s problem. Sure, you can decorate there wrapper within the decorator as @staticmethod and it should work if used correctly (tested on python 3.9), but I see no reason to do it this way. Such a decorator will become unusable on functions without the class. Moreover, you can use @staticmethod even over already decorated method if needed.

A more concise example might be as follows:

#/usr/bin/env python3 from functools import wraps def wrapper(method): @wraps(method) def _impl(self, *method_args, **method_kwargs): method_output = method(self, *method_args, **method_kwargs) return method_output + "!" return _impl class Foo: @wrapper def bar(self, word): return word f = Foo() result = f.bar("kitty") print(result) 

IMO, this is superior to stackoverflow.com/a/11731208/257924. It demonstrates how the internal function _impl can access self to manipulate that self for whatever purpose. I needed to build a simple method decorator that incremented a self.id on a subset of the methods in a class, and only those methods in a class that had the «@» decoration syntax applied to it. That Syntactic Sugar pays it forward to my Future Self, as compared to stackoverflow.com/a/56322968/257924 which abandoned that sugar and requires me to look deep inside the __init__ method.

from re import search from functools import wraps def is_match(_lambda, pattern): def wrapper(f): @wraps(f) def wrapped(self, *f_args, **f_kwargs): if callable(_lambda) and search(pattern, (_lambda(self) or '')): f(self, *f_args, **f_kwargs) return wrapped return wrapper class MyTest(object): def __init__(self): self.name = 'foo' self.surname = 'bar' @is_match(lambda x: x.name, 'foo') @is_match(lambda x: x.surname, 'foo') def my_rule(self): print 'my_rule : ok' @is_match(lambda x: x.name, 'foo') @is_match(lambda x: x.surname, 'bar') def my_rule2(self): print 'my_rule2 : ok' test = MyTest() test.my_rule() test.my_rule2() 

Another option would be to abandon the syntactic sugar and decorate in the __init__ of the class.

def countdown(number): def countdown_decorator(func): def func_wrapper(): for index in reversed(range(1, number+1)): print(index) func() return func_wrapper return countdown_decorator class MySuperClass(): def __init__(self, number): self.number = number self.do_thing = countdown(number)(self.do_thing) def do_thing(self): print('im doing stuff!') myclass = MySuperClass(3) myclass.do_thing() 

This is much more practical. E.g. the top-voted example hardcodes the «url» attribute into the decorator definition.

Читайте также:  Can read method in java

I know this issue is quite old, but the below workaround hasn’t been proposed before. The problem here is that you can’t access self in a class block, but you can in a class method.

Let’s create a dummy decorator to repeat a function some times.

import functools def repeat(num_rep): def decorator_repeat(func): @functools.wraps(func) def wrapper_repeat(*args, **kwargs): for _ in range(num_rep): value = func(*args, **kwargs) return return wrapper_repeat return decorator_repeat 
class A: def __init__(self, times, name): self.times = times self.name = name def get_name(self): @repeat(num_rep=self.times) def _get_name(): print(f'Hi ') _get_name() 

I know this is an old question, but this solution has not been mentioned yet, hopefully it may help someone even today, after 8 years.

So, what about wrapping a wrapper? Let’s assume one cannot change the decorator neither decorate those methods in init (they may be @property decorated or whatever). There is always a possibility to create custom, class-specific decorator that will capture self and subsequently call the original decorator, passing runtime attribute to it.

import functools # imagine this is at some different place and cannot be changed def check_authorization(some_attr, url): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f"checking authorization for ''. ") return func(*args, **kwargs) return wrapper return decorator # another dummy function to make the example work def do_work(): print("work is done. ") ################### # wrapped wrapper # ################### def custom_check_authorization(some_attr): def decorator(func): # assuming this will be used only on this particular class @functools.wraps(func) def wrapper(self, *args, **kwargs): # get url url = self.url # decorate function with original decorator, pass url return check_authorization(some_attr, url)(func)(self, *args, **kwargs) return wrapper return decorator ############################# # original example, updated # ############################# class Client(object): def __init__(self, url): self.url = url @custom_check_authorization("some_attr") def get(self): do_work() # create object client = Client(r"https://stackoverflow.com/questions/11731136/class-method-decorator-with-self-arguments") # call decorated function client.get() 
checking authorisation for 'https://stackoverflow.com/questions/11731136/class-method-decorator-with-self-arguments'. work is done. 

Источник

Оцените статью