我试图编写一个可以添加到实例方法和非实例方法的装饰器。我已将代码缩减到最低限度的示例,以证明我的观点
def call(fn):
def _impl(*args, **kwargs):
return fn(*args, **kwargs)
fn.call = _impl
return fn
class Foo(object):
@call
def bar(self):
pass
Foo().bar.call()
这给出了漂亮的错误
Traceback (most recent call last):
File "/tmp/511749370/main.py", line 14, in <module>
Foo().bar.call()
File "/tmp/511749370/main.py", line 3, in _impl
return fn(*args, **kwargs)
TypeError: bar() missing 1 required positional argument: 'self'
是否可以在不诉诸
的情况下做这样的事情Foo.bar.call(Foo())
或者这是我唯一的选择吗?
答案 0 :(得分:2)
您必须将装饰器实现为类并实现descriptor protocol。基本上,描述符__get__
函数负责创建绑定方法。通过覆盖此函数,您可以访问self
并可以创建call
函数的绑定副本。
以下实现就是这样做的。 Foo
实例保存在__self__
属性中。装饰器有一个调用装饰函数的__call__
方法,以及一个执行相同操作的call
方法。
import inspect
import functools
from copy import copy
class call:
def __init__(self, func):
self.func = func
self.__self__ = None # "__self__" is also used by bound methods
def __call__(self, *args, **kwargs):
# if bound to on object, pass it as the first argument
if self.__self__ is not None:
args = (self.__self__,) + args
return self.func(*args, **kwargs)
def call(self, *args, **kwargs):
self(*args, **kwargs)
def __get__(self, obj, cls):
if obj is None:
return self
# create a bound copy of the decorator
bound = copy(self)
bound.__self__ = obj
# update __doc__ and similar attributes
functools.wraps(bound.func)(bound)
bound.__signature__ = inspect.signature(bound.func)
# add the bound instance to the object's dict so that
# __get__ won't be called a 2nd time
setattr(obj, self.func.__name__, bound)
return bound
测试:
class Foo(object):
@call
def bar(self):
print('bar')
@call
def foo():
print('foo')
Foo().bar.call() # output: bar
foo() # output: foo