Weird interaction with nested functions inside a decorator-producing function and closuring of outer data...

Adam Jorgensen adam.jorgensen.za at gmail.com
Wed Aug 24 08:21:18 EDT 2011


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



More information about the Python-list mailing list