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

时间:2019-01-24 17:39:53

标签: python django python-decorators

我有一个带有多个方法的自定义类,这些方法都返回一个代码。我希望使用标准逻辑,以针对该方法的可接受代码列表检查返回的代码,并在不期望的情况下引发错误。

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

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装饰器可能已经为我解决了这个问题,但是我认为我对它的工作原理存在根本的误解。

1 个答案:

答案 0 :(得分:1)

TL; DR

使用__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
相关问题