【问题标题】:How to use decorators on overridable class methods如何在可覆盖的类方法上使用装饰器
【发布时间】:2019-06-18 12:48:01
【问题描述】:

我有一个带有多个方法的自定义类,它们都返回一个代码。我想要标准逻辑,根据该方法的可接受代码列表检查返回的代码,如果不是预期的,则会引发错误。

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

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 应用程序的上下文中,我认为 Djangos method_decorator 装饰器可能已经为我解决了这个问题,但我认为我对它的工作原理有一个根本的误解。

【问题讨论】:

  • 您正在明确调用父方法,它会启动异常(根据装饰器)。您在这里的预期结果是什么?

标签: python django python-decorators


【解决方案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

【讨论】:

  • 解决了我的困惑并提供了一个可用的解决方案,谢谢!
猜你喜欢
  • 2022-09-29
  • 2021-10-04
  • 2017-01-31
  • 2012-02-08
  • 1970-01-01
  • 2014-05-09
  • 1970-01-01
  • 2020-04-24
  • 2014-10-08
相关资源
最近更新 更多