I have a callable class:
class CallMeMaybe:
__name__ = 'maybe'
def __init__(self):
self.n_calls = 0
def __call__(self):
self.n_calls += 1
raise Exception
That seems to work as advertised:
>>> f = CallMeMaybe()
>>> f.n_calls
0
>>> for i in range(7):
... try:
... f()
... except Exception:
... pass
...
>>> f.n_calls
7
I want to decorate it with an exponential backoff:
from backoff import on_exception, expo
dec = on_exception(expo, Exception, max_tries=3, on_backoff=print)
f = CallMeMaybe()
f2 = dec(f)
Now it looks like attribute access stopped working:
>>> 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
My question: who copied n_calls
name into f2
's namespace, and why? Now it holds a stale value - the correct value should be 3:
>>> f2.__wrapped__.n_calls
3
The
backoff
module in its implementation usesfunctools.wraps
which callsfunctools.update_wrapper
and you can see from the source code that by default it updates the__dict__
of the wrapper:Unfortunately it seems impossible to achieve what you want. The
backoff
module could allow optionalassigned
/updated
attributes lists to be passed towraps
to avoid the copy of the attribute. However this would really solve the issue because at that point you would not have access ton_calls
.You probably need to use a mutable object instead of a plain
int
.