繁体   English   中英

如何在可覆盖的类方法上使用装饰器

[英]How to use decorators on overridable class methods

我有一个带有多个方法的自定义类,这些方法都返回一个代码。 我想要标准逻辑,根据该方法的可接受代码列表检查返回的代码并在不符合预期的情况下引发错误。

我认为实现这一目标的好方法是使用装饰器:

from functools import wraps

def expected_codes(codes):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            code = f(*args, **kwargs)
            if code not in codes:
                raise Exception(f"{code} not allowed!")
            else:
                return code
        return wrapper
    return decorator

然后我有一个这样的课程:

class MyClass:
    @expected_codes(["200"])
    def return_200_code(self):
        return "200"

    @expected_codes(["300"])
    def return_300_code(self):
        return "301" # Exception: 301 not allowed!

这工作正常,但是如果我覆盖基类:

class MyNewClass:
    @expected_codes(["300", "301"])
    def return_300_code(self):
        return super().return_300_code()  # Exception: 301 not allowed!

由于被覆盖的装饰器,我本来希望上述覆盖的方法正确返回而不是引发异常。

从我通过阅读收集的信息来看,我想要的方法不起作用,因为装饰器正在类定义中进行评估 - 但是我很惊讶没有办法实现我想要的。 这一切都在 Django 应用程序的上下文中,我认为 Django 的method_decorator装饰器可能已经为我解决了这个问题,但我认为我对它的工作原理有一个根本的误解。

TL; 博士

使用__wrapped__属性忽略父装饰器:

class MyNewClass(MyClass):
    @expected_codes(["300", "301"])
    def return_300_code(self):
        return super().return_300_code.__wrapped__(self) # No exception raised

解释

@decorator语法等效于:

def f():
    pass
f = decorator(f)

因此,您可以堆叠装饰器:

def decorator(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        print(f"Calling {f.__name__}")
        f(*args, **kwargs)
    return wrapper

@decorator
def f():
    print("Hi!")

@decorator
def g():
    f()  
g()

#Calling g
#Calling f
#Hi!

但是如果你想避免堆积, __wrapped__属性是你的朋友:

@decorator
def g():
    f.__wrapped__()
g()

#Calling g
#Hi!

简而言之,如果您在子类的装饰方法中调用装饰的父方法之一,装饰器将堆叠,而不是相互覆盖。

因此,当您调用super().return_300_code()您正在调用父类的装饰方法,该方法不接受301作为有效代码,并将引发自己的异常。

如果您想重用原始父级的方法,即直接返回301而不检查的方法,您可以使用__wrapped__属性来访问原始函数(在装饰之前):

class MyNewClass(MyClass):
    @expected_codes(["300", "301"])
    def return_300_code(self):
        return super().return_300_code.__wrapped__(self) # No exception raised

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM