在基类和派生类中使用基类的装饰器

Posted

技术标签:

【中文标题】在基类和派生类中使用基类的装饰器【英文标题】:Using decorator from base class both in base class and derived class 【发布时间】:2017-03-24 12:57:48 【问题描述】:

我有一些带有一些方法的 python 对象,我想在开始时做一些检查,根据这个检查,方法的代码会运行,或者会引发一个执行。我不想在每个方法的开头复制“检查”代码,而是希望将装饰器嵌入到类本身中,因为它与它密切相关。所以基本上:

而不是这个

class A(object):

    def a_method(self):
        if self.check_var is True:
            (some_code)
        else:
            raise Exception

我想要这个

class A(object):

    def decorator(function):
        def function_wrapper(self, *args, **kwargs):
            if self.check_var is True:
                return function(self, *args, **kwargs)
            else:
                raise Exception
        return function_wrapper

    @decorator
    def a_method(self):
        (some_code)

我的第一个问题是,我这样做对吗?或者,还有更好的方法。我有很多 A 类的方法需要进行这个检查,所以我不想不必要地复制代码。

我的第二个问题是,如果按照我描述的方式进行,当我想从类 A 派生一个类并执行相同的装饰器检查时会遇到问题。同样,我不想复制代码,所以我想重用基类 A 中的装饰器来在派生类中执行检查。我读到了关于将装饰器变成@classmethod 的文章,但是当我这样做时,我可以在派生类中使用装饰器,但不能在基类中使用了!

所以基本上我想要这样的东西:

class A(object):

    @classmethod #maybe
    def decorator(function):
        def function_wrapper(self, *args, **kwargs):
            if self.check_var is True:
                return function(self, *args, **kwargs)
            else:
                raise Exception
        return function_wrapper

    @decorator
    def a_method(self):
        (some_code)

class B(A):

    @decorator
    def b_method(self):
        (some_code)

有人知道这样做的干净方法吗?

【问题讨论】:

只需在两个类之外和之前定义decorator() 我想把装饰器放在课堂上......装饰器所做的事情与课堂非常相关 把你的基类和装饰器放在一个文件(模块)中,这样很明显它们属于一起,然后按照@martineau所说的去做 另外,您可以跳过装饰器,而使用元类。更多的前期工作,但在子类中没有工作。 除了像@o11c 那样的元类,建议您可以使用 class 装饰器,它适用于基类和子类。这两种技术的一个可能问题是很难有选择地将装饰分配给类的任意一组方法。 【参考方案1】:

我的第一个问题是,我这样做对吗?

正如 martineau 下面所说,好的做法是将经典装饰器放在课堂之外。

def get_decorator(function, argument):
    def function_wrapper(*args, **kwargs):
        if argument is True:
            return function(*args, **kwargs)
        else:
            raise Exception
    return function_wrapper

class A(object):
    def __init__(self):
        self.check_var = True
        self.a_method = get_decorator(self.a_method, self.check_var)
    def a_method(self):
        (whatever)

class B(A):
    def __init__(self):
        super(B, self).__init__()
        self.b_method = get_decorator(self.b_method, self.check_var)
    def b_method(self):
        (whatever)

Classic 装饰器在类创建期间被调用,在创建实例之前很久。 Reference

【讨论】:

我认为你错过了 OP 希望在 A 和子类 B 中使用装饰器的观点。在定义它的类中调用/使用类方法装饰器将导致TypeError: 'classmethod' object is not callable 是的,我知道,我只是考虑多重继承,但这似乎不是一个好的解决方案。 所以你回答的第二部分不能满足 OP 的需求。 感谢您的建议!为第二部分添加一些描述。 您的编辑是一种改进,但我已经想到了解决 TypeError 的替代方法...请参阅我自己刚刚添加的 answer。【参考方案2】:

由于您更愿意将装饰器放在类中(而不是像我在评论中建议的那样放在它们之外),下面显示了一种方法。它使装饰器成为staticmethod 而不是classmethod,并且需要以稍微不寻常的方式使用它,但仅限于类中。

有关使用这样的装饰器的必要性的更多信息,请参阅我的问题Calling class staticmethod within the class body?

class A(object):
    @staticmethod
    def decorator(function):
        def function_wrapper(*args, **kwargs):
            print('in function_wrapper')
            return function(*args, **kwargs)
        return function_wrapper

    @decorator.__func__  #### Note unusual decorator usage inside defining class
    def a_method(self):
        print('in a_method')

class B(A):
    @A.decorator  #### Normal decorator usage outside defining class
    def b_method(self):
        print('in b_method')

避免必须使用 __func__ 并仍将定义保留在第一个类中的一种方法是将其转换为 staticmethod 直到类定义的最后:

class A(object):
    def decorator(function):
        def function_wrapper(*args, **kwargs):
            print('in function_wrapper')
            return function(*args, **kwargs)
        return function_wrapper

    @decorator
    def a_method(self):
        print('in a_method')

    decorator = staticmethod(decorator)  #### convert for use outside this class

class B(A):
    @A.decorator
    def b_method(self):
        print('in b_method')

另一种避免__func__ 的方法是这样的:

class A(object):
    class Check:
        @staticmethod
        def decorator(function):
            def function_wrapper(*args, **kwargs):
                print('in function_wrapper')
                return function(*args, **kwargs)
            return function_wrapper

    @Check.decorator
    def a_method(self):
        print('in a_method')

class B(A):
    Check = A.Check

    @Check.decorator
    def b_method(self):
        print('in b_method')

这具有使装饰器的使用非常统一的额外优势。

【讨论】:

以上是关于在基类和派生类中使用基类的装饰器的主要内容,如果未能解决你的问题,请参考以下文章

在基类和派生类中分离 ODE 和 ODE 求解器

作为基类一部分的 Python 装饰器不能用于装饰继承类中的成员函数

基类 派生类 类的继承与约束

在基类中创建派生类的对象

调用派生类的构造函数在基类的构造函数之前执行

使用基类装饰器扩展组件装饰器