为方法跳过“self”的Decorator

2024-04-19 18:37:04 发布

您现在位置:Python中文网/ 问答频道 /正文

假设我们有多个函数都接受一个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)是否不是一个好的解决方案。你知道吗


Tags: 方法函数selfurlreturndefargs装饰
1条回答
网友
1楼 · 发布于 2024-04-19 18:37:04

一种解决方案是以某种方式检测修饰函数是否是类方法——这似乎很难做到(如果不是不可能的话)(据我所知)。inspect模块的ismethod()isfunction()在类定义中使用的装饰器中不起作用。你知道吗

考虑到这一点,这里有一种有点老套的方法来检查修饰的callable的第一个参数是否被命名为"self",这是它在类方法中的编码约定(尽管它不是一个要求,所以caveat emptor,使用风险自负)。你知道吗

下面的代码似乎可以在python2和python3中使用。然而,在python3中,它可能会引发DeprecationWarning,具体取决于所使用的子版本,因此它们在下面的代码部分中被抑制了。你知道吗

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!

相关问题 更多 >