为方法跳过“自我”的装饰器

Posted

技术标签:

【中文标题】为方法跳过“自我”的装饰器【英文标题】:Decorator which skips `self` for methods 【发布时间】:2019-02-22 19:03:36 【问题描述】:

假设我们有多个函数,它们都接受一个 URL 作为它们的第一个参数,并且这个 URL 需要被验证。这可以通过装饰器很好地解决

def validate_url(f):
   def validated(url, *args, **kwargs):
       assert len(url.split('.')) == 3 # trivial example
       return f(url, *args, **kwargs)
    return validated

@validate_url
def some_func(url, some_other_arg, *some_args, **some_kwargs):
    pass

这种方法可行,并允许我从许多类似函数的实例中考虑验证行为。但是现在我想写一个类方法,它也需要一个经过验证的 URL。但是,幼稚的方法是行不通的

class SomeClass:
    @validate_url
    def some_method(self, url, some_other_args):
        pass

因为我们最终会尝试验证 self 而不是 url。我的问题是如何编写一个单一的装饰器,它适用于具有最少样板量的函数和方法。

注意 1:我知道为什么会发生这种情况,只是我不知道如何以最优雅的方式解决这个问题。

注意2:URL验证问题只是一个例子,所以检查isinstance(args[0], str)是否不是一个好的解决方案。

【问题讨论】:

不确定this是否可以在这里申请 你能定义一个像def validate_url(url, *args) 这样在some_func 内部运行的@staticmethod 吗?然后你可以装饰validate_url。看起来 hacky 和非 Pythonic,但这是我尝试的第一件事 【参考方案1】:

一种解决方案是以某种方式检测修饰函数是否是类方法——这似乎很困难,甚至不可能(据我所知)干净地做到这一点。 inspect 模块的 ismethod()isfunction() 在类定义中使用的装饰器中不起作用。

鉴于此,这里有一个有点老套的方法,它检查装饰的可调用对象的第一个参数是否被命名为"self",这是类方法中的编码约定(尽管它是 不是要求,所以caveat emptor,使用风险自负)。

以下代码似乎在 Python 2 和 3 中都有效。但是在 Python 3 中,它可能会引发 DeprecationWarnings,具体取决于所使用的子版本——因此它们已在下面的代码部分中被抑制.

from functools import wraps
import inspect
import warnings

def validate_url(f):
    @wraps(f)
    def validated(*args, **kwargs):
        with warnings.catch_warnings():
            # Suppress DeprecationWarnings in this section.
            warnings.simplefilter('ignore', category=DeprecationWarning)

            # If "f"'s first argument is named "self",
            # assume it's a method.
            if inspect.getargspec(f).args[0] == 'self':
                url = args[1]
            else:  # Otherwise assume "f" is a ordinary function.
                url = args[0]
        print('testing url: !r'.format(url))
        assert len(url.split('.')) == 3  # Trivial "validation".
        return f(*args, **kwargs)
    return validated

@validate_url
def some_func(url, some_other_arg, *some_args, **some_kwargs):
    print('some_func() called')


class SomeClass:
    @validate_url
    def some_method(self, url, some_other_args):
        print('some_method() called')


if __name__ == '__main__':
    print('** Testing decorated function **')
    some_func('xxx.yyy.zzz', 'another arg')
    print('  URL OK')
    try:
        some_func('https://bogus_url.com', 'another thing')
    except AssertionError:
        print('  INVALID URL!')

    print('\n** Testing decorated method **')
    instance = SomeClass()
    instance.some_method('aaa.bbb.ccc', 'something else')  # -> AssertionError
    print('  URL OK')
    try:
        instance.some_method('foo.bar', 'arg 2')  # -> AssertionError
    except AssertionError:
        print('  INVALID URL!')

输出:

** Testing decorated function **
testing url: 'xxx.yyy.zzz'
some_func() called
  URL OK
testing url: 'https://bogus_url.com'
  INVALID URL!

** Testing decorated method **
testing url: 'aaa.bbb.ccc'
some_method() called
  URL OK
testing url: 'foo.bar'
  INVALID URL!

【讨论】:

以上是关于为方法跳过“自我”的装饰器的主要内容,如果未能解决你的问题,请参考以下文章

从装饰器访问自我

Python装饰器访问自我[重复]

Python装饰器,自我混淆[重复]

Python装饰器,自我混淆[重复]

Python单元测试--使用装饰器实现测试跳过和预期故障

unittest测试框架_4_装饰器