【问题标题】:Attribute access on a decorated callable class装饰可调用类的属性访问
【发布时间】:2018-10-06 09:51:29
【问题描述】:

我有一个可调用的类:

class CallMeMaybe:

    __name__ = 'maybe'

    def __init__(self):
        self.n_calls = 0

    def __call__(self):
        self.n_calls += 1
        raise Exception

这似乎像宣传的那样工作:

>>> f = CallMeMaybe()
>>> f.n_calls
0
>>> for i in range(7):
...     try:
...         f()
...     except Exception:
...         pass
...     
>>> f.n_calls
7

我想用指数装饰它backoff

from backoff import on_exception, expo
dec = on_exception(expo, Exception, max_tries=3, on_backoff=print)
f = CallMeMaybe()
f2 = dec(f)

现在看起来属性访问停止工作了:

>>> f2.n_calls
0
>>> f2()
{'target': <__main__.CallMeMaybe object at 0xcafef00d>, 'args': (), 'kwargs': {}, 'tries': 1, 'elapsed': 2.1e-05, 'wait': 0.4843249208229148}
{'target': <__main__.CallMeMaybe object at 0xcafef00d>, 'args': (), 'kwargs': {}, 'tries': 2, 'elapsed': 0.484935, 'wait': 1.6524016553598126}
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
... blah blah blah
>>> f2.n_calls
0

我的问题:谁将n_calls 名称复制到f2 的命名空间中,为什么?现在它拥有一个陈旧的值 - 正确的值应该是 3:

>>> f2.__wrapped__.n_calls
3

【问题讨论】:

    标签: python decorator python-decorators exponential-backoff


    【解决方案1】:

    backoff 模块在其实现 uses functools.wraps 中调用 functools.update_wrapper,您可以看到 from the source code 默认情况下它会更新包装器的 __dict__

    WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                           '__annotations__')
    WRAPPER_UPDATES = ('__dict__',)
    def update_wrapper(wrapper,
                       wrapped,
                       assigned = WRAPPER_ASSIGNMENTS,
                       updated = WRAPPER_UPDATES):
        """Update a wrapper function to look like the wrapped function
    
           wrapper is the function to be updated
           wrapped is the original function
           assigned is a tuple naming the attributes assigned directly
           from the wrapped function to the wrapper function (defaults to
           functools.WRAPPER_ASSIGNMENTS)
           updated is a tuple naming the attributes of the wrapper that
           are updated with the corresponding attribute from the wrapped
           function (defaults to functools.WRAPPER_UPDATES)
        """
        for attr in assigned:
            try:
                value = getattr(wrapped, attr)
            except AttributeError:
                pass
            else:
                setattr(wrapper, attr, value)
        for attr in updated:
            getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
        +
    −# Issue #17482: set __wrapped__ last so we don't inadvertently copy it
        # from the wrapped function when updating __dict__
        wrapper.__wrapped__ = wrapped
        # Return the wrapper so this can be used as a decorator via partial()
        return wrapper
    
    def wraps(wrapped,
              assigned = WRAPPER_ASSIGNMENTS,
              updated = WRAPPER_UPDATES):
        """Decorator factory to apply update_wrapper() to a wrapper function
    
           Returns a decorator that invokes update_wrapper() with the decorated
           function as the wrapper argument and the arguments to wraps() as the
           remaining arguments. Default arguments are as for update_wrapper().
           This is a convenience function to simplify applying partial() to
           update_wrapper().
        """
        return partial(update_wrapper, wrapped=wrapped,
                       assigned=assigned, updated=updated)
    

    不幸的是,似乎不可能实现您想要的。 backoff 模块可以允许将可选的assigned/updated 属性列表传递给wraps,以避免复制属性。但是,这确实可以解决问题,因为那时您将无法访问n_calls

    您可能需要使用可变对象而不是普通的int

    【讨论】:

      猜你喜欢
      • 2015-08-23
      • 2014-02-08
      • 1970-01-01
      • 1970-01-01
      • 2020-07-09
      • 2020-05-14
      • 2018-11-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多