为方法跳过“自我”的装饰器
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 中,它可能会引发 DeprecationWarning
s,具体取决于所使用的子版本——因此它们已在下面的代码部分中被抑制.
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!
【讨论】:
以上是关于为方法跳过“自我”的装饰器的主要内容,如果未能解决你的问题,请参考以下文章