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 -- http://mail.python.org/mailman/listinfo/python-list