Weird interaction with nested functions inside a decorator-producingfunction and closuring of outer


A

Adam Jorgensen

Hi all, I'm experiencing a weird issue with closuring of parameters
and some nested functions I have inside two functions that
return decorators. I think it's best illustrated with the actual code:

# This decorator doesn't work. For some reason python refuses to
closure the *decode_args parameter into the scope of the nested
decorate and decorate_with_rest_wrapper functions
# Renaming *decode_args has no effect
def rest_wrapper(*decode_args, **deco_kwargs):
def decorate(func):
argspec = getfullargspec(func)
decode_args = [argspec.args.index(decode_arg) for decode_arg
in decode_args]
def decorate_with_rest_wrapper(func, *args, **kwargs):
if decode_args:
args = list(args)
for index in decode_args:
args[index] = json.loads(args[index],
cls=deco_kwargs.get('json_decoder'))
return Response.ResponseString(json.dumps(func(*args,
**kwargs), cls=deco_kwargs.get('json_encoder')))
return decorator(decorate_with_rest_wrapper, func)
return decorate

# If I modify rest_wrapper slightly and use the parameters from
rest_wrapper as default value args for decorate it works. Why?
def rest_wrapper(*decode_args, **deco_kwargs):
def decorate(func, decode_args=decode_args, deco_kwargs=deco_kwargs):
argspec = getfullargspec(func)
decode_args = [argspec.args.index(decode_arg) for decode_arg
in decode_args]
def decorate_with_rest_wrapper(func, *args, **kwargs):
if decode_args:
args = list(args)
for index in decode_args:
args[index] = json.loads(args[index],
cls=deco_kwargs.get('json_decoder'))
return Response.ResponseString(json.dumps(func(*args,
**kwargs), cls=deco_kwargs.get('json_encoder')))
return decorator(decorate_with_rest_wrapper, func)
return decorate


# Similarly, this decorator doesn't work. For some reason python
refuses to closure the sa_session_class_lambda parameter into the
scope of the nested decorate and decorate_with_sqlalchemy functions
# Renaming the sa_session_class_lambda parameter does not work and
neither does changing the order of the args list.
def with_sqlalchemy(sa_session_parameter_name='sa_session',
sa_session_class_lambda=None):
def decorate(func):
argspec = getfullargspec(func)
sa_session_parameter_index =
argspec.args.index(sa_session_parameter_name)
if sa_session_class_lambda is None:
if argspec.args[0] == 'self':
sa_session_class_lambda = lambda obj, *args, **kwargs:
obj.get_sa_session_class()
else:
sa_session_class_lambda = lambda *args, **kwargs:
Alchemy.get_sa_session_class()
def decorate_with_alchemy(func, *args, **kwargs):
if args[sa_session_parameter_index]:
raise Exception('%s parameter is not empty!' %
sa_session_parameter_name)
try:
sa_session_class = sa_session_class_lambda(*args, **kwargs)
sa_session = sa_session_class()
args = list(args)
args[sa_session_parameter_index] = sa_session
retval = func(*args, **kwargs)
sa_session.commit()
return retval
except Exception, e:
sa_session.rollback()
raise e
finally:
sa_session.close()
return decorator(decorate_with_alchemy, func)
return decorate

# Again, if I modify decorate and use the parameters from
with_sqlalchemy as default value args in decorate it works fine. What
gives?
def with_sqlalchemy(sa_session_parameter_name='sa_session',
sa_session_class_lambda=None):
def decorate(func,
sa_session_parameter_name=sa_session_parameter_name,
sa_session_class_lambda=sa_session_class_lambda):
argspec = getfullargspec(func)
sa_session_parameter_index =
argspec.args.index(sa_session_parameter_name)
if sa_session_class_lambda is None:
if argspec.args[0] == 'self':
sa_session_class_lambda = lambda obj, *args, **kwargs:
obj.get_sa_session_class()
else:
sa_session_class_lambda = lambda *args, **kwargs:
Alchemy.get_sa_session_class()
def decorate_with_alchemy(func, *args, **kwargs):
if args[sa_session_parameter_index]:
raise Exception('%s parameter is not empty!' %
sa_session_parameter_name)
try:
sa_session_class = sa_session_class_lambda(*args, **kwargs)
sa_session = sa_session_class()
args = list(args)
args[sa_session_parameter_index] = sa_session
retval = func(*args, **kwargs)
sa_session.commit()
return retval
except Exception, e:
sa_session.rollback()
raise e
finally:
sa_session.close()
return decorator(decorate_with_alchemy, func)
return decorate


Does anyone have any idea why the problem parameters are not being captured?

When I try to run the code using the broken versions of the two
decorators python fails claiming that the problem variables
are being referenced before they are defined...

What's more interesting is that PyCharm seems to know this is going to
happen as well because the code insight marks
the problem versions as having unused parameters (Specifically, the
*decode_args and sa_session_class_lambda parameters).

Does anyone know why this is happening and if there is a nicer fix
than the one illustrated above?

Thanks
Adam
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top