【问题标题】:Decorator that gets self获得自我的装饰器
【发布时间】:2018-03-02 02:14:18
【问题描述】:

我正在尝试编写一个可以添加到实例方法和非实例方法中的装饰器。我已将我的代码减少到一个最小的例子来证明我的观点

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())

或者这是我唯一的选择?

【问题讨论】:

  • 查看解决方案:stackoverflow.com/questions/7590682/access-self-from-decorator。我知道这不是完全相同的问题,但它可能会有所帮助。
  • @IdanMeyer 我尝试将self 作为参数添加到_impl,但这只是将错误转移到_impl() missing 1 required positional argument: 'self',我尝试使用fn.__self__,这会引发AttributeError

标签: python


【解决方案1】:

您必须将您的装饰器实现为一个类并实现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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-29
    • 2011-11-27
    • 2013-02-06
    • 1970-01-01
    • 2020-06-05
    • 2017-06-23
    相关资源
    最近更新 更多