Sorry for the disturbing title, but I can't put the right words on what I've been looking at for hours now.
I'm using a decorator with optional parameters and i want to alter one of them within the wrapped function so that each call ends up doing a different thing. For context (that I had to remove), i want to create some sort of hash of the original function args and work with that.
from functools import wraps
def deco(s=None):
def _decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(locals())
nonlocal s # Get the deco func arg
if not s:
s = "some_value_depending_on_other_args_i_removed"
# do_smth_smart(s)
# s=None # This solves my issue if uncommented
return None
return wrapper
return _decorate
Here is a small test sample :
@deco()
def test1(self,x):
pass
@deco()
def test2(self,a,b):
pass
test1(1)
test1(2)
test2(3,4)
test2(5,6)
I would expect s
to be "reset" to None
whenever I call the decorated functions.
To my surprise, as it stands, the output is :
{'args': (1,), 'kwargs': {}, 's': None}
{'args': (2,), 'kwargs': {}, 's': 'newname'}
{'args': (3, 4), 'kwargs': {}, 's': None}
{'args': (5, 6), 'kwargs': {}, 's': 'newname'}
Would someone enlighten me please ? Thanks in advance :)
If we add some print
statements:
from functools import wraps
def deco(s=None):
print('deco')
def _decorate(func):
print('_decorate')
@wraps(func)
def wrapper(*args, **kwargs):
print('wrapper')
print(locals())
nonlocal s # Get the deco func arg
if not s:
s = "some_value_depending_on_other_args_i_removed"
# do_smth_smart(s)
# s=None # This solves my issue if uncommented
return None
return wrapper
return _decorate
@deco()
def test1(self,x):
pass
@deco()
def test2(self,a,b):
pass
test1(1)
test1(2)
test2(3,4)
test2(5,6)
We can see that deco
(and _decorate
) is only called when decorating the func
tions test1
and test2
:
deco
_decorate
deco
_decorate
wrapper
{'args': (1,), 'kwargs': {}, 's': None}
wrapper
{'args': (2,), 'kwargs': {}, 's': 'some_value_depending_on_other_args_i_removed'}
wrapper
{'args': (3, 4), 'kwargs': {}, 's': None}
wrapper
{'args': (5, 6), 'kwargs': {}, 's': 'some_value_depending_on_other_args_i_removed'}
Even here only wrapper
is really called every time along with the func
tion it's wrapping, so that's where s
needs to be; to achieve what you're asking we can do something like assigning _s
's __default__
value to be s
in wrapper
's parameter def
inition:
from functools import wraps
def deco(s=None):
print('deco')
def _decorate(func):
print('_decorate')
@wraps(func)
def wrapper(*args, _s=s, **kwargs):
print('wrapper')
print(locals())
nonlocal s # Get the deco func arg
if not _s:
_s = "some_value_depending_on_other_args_i_removed"
print(locals())
# do_smth_smart(s)
# s=None # This solves my issue if uncommented
return None
return wrapper
return _decorate
@deco(1)
def test1(self,x):
pass
@deco()
def test2(self,a,b):
pass
test1(1)
test1(2)
test2(3,4)
test2(5,6)
Outputs:
deco
_decorate
deco
_decorate
wrapper
{'_s': 1, 'args': (1,), 'kwargs': {}, 's': 1}
{'_s': 1, 'args': (1,), 'kwargs': {}, 's': 1}
wrapper
{'_s': 1, 'args': (2,), 'kwargs': {}, 's': 1}
{'_s': 1, 'args': (2,), 'kwargs': {}, 's': 1}
wrapper
{'_s': None, 'args': (3, 4), 'kwargs': {}, 's': None}
{'_s': 'some_value_depending_on_other_args_i_removed', 'args': (3, 4), 'kwargs': {}, 's': None}
wrapper
{'_s': None, 'args': (5, 6), 'kwargs': {}, 's': None}
{'_s': 'some_value_depending_on_other_args_i_removed', 'args': (5, 6), 'kwargs': {}, 's': None}