Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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):    

where action is other method in my class. But it doesn't work and throws exception as

name 'self' is not defined
like image 496
gliese581g Avatar asked Sep 21 '25 07:09

gliese581g


2 Answers

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.

like image 75
Martijn Pieters Avatar answered Sep 22 '25 20:09

Martijn Pieters


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.

So, again, is it a method?


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.

like image 33
N1ngu Avatar answered Sep 22 '25 20:09

N1ngu