简体   繁体   中英

How do I access a local variable in my decorated function from my decorator

I just started playing with decorators in Python. I am currently having trouble figuring out the best possible way to handle this use cases. Since a decorated function cannot access local variables from the decorator.

schema = {
    'lang': {'type': 'string', 'required':True}
}

def validate(schema):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            # Read from request body args[1]
            # Validate the json is in fact correct
            valid_json = json.loads(body.decode('utf-8'))
            # Compare valid_json to the schema using "Cerberus"
            return args, kwargs
        return wrapper
    return my_decorator

@validate(schema)
def main_function(self, req, resp):
    # call database with valid_json  

My question is: How do I access valid_json from my decorated function to be able to insert into my database afterwards. What is the best practice for this situation?

Edit: I am running pypy 2.4

IIUC what you asked, the following could do so. I can't say that I'm crazy about this solution (or about what you're asking, for that matter) - it's too much "magic" for my taste.

For simplicity, let's assume that schemas and everything are integers, and validating something by a schema simply means adding the schema integer to it (this is just for illustration). So we have:

def validate_schema(schema, arg):
    return arg + schema

Now we can write the following decorator:

def validate(schema, arg_pos):
    """
    Decorates a function so that the arg_pos argument will
       be modified to be validated by schema.
    """
    def decorator(method):
        @functools.wraps(method)
        def f(*args, **kwargs):
            # Manipulate args to modify the validated argument:
            args = args[: arg_pos] + (validate_schema(schema, args[arg_pos]), ) + args[arg_pos + 1: ]

            # Call the method with the modified arguments
            return method(*args, **kwargs)
        return f
    return decorator

Now we can use it as so:

# Validate arg #0 with schema 12
@validate(12, 0)
def bar(inp_):
    print inp_


>>> bar(1)
13

Of course, you can elaborate this further, to take pairs of schema+arguments, handle keyword arguments, and so forth. That is the principle of this answer, though. I think it's somewhat amusing, but I wouldn't use it.

You can't do this directly. The scope of the function being decorated is set before it's wrapped by the decorator, and it has no connection to the scopes established by the decorator added wrapper.

The best you can do is have the function accept additional arguments which the decorator passes, eg:

@validate(schema)
def main_function(self, req, resp, valid_json=None):
    # call database with valid_json

with the decorator explicitly adding valid_json=valid_json when it calls the wrapped function, eg return func(*args, **kwargs, valid_json=valid_json) .

I've also been playing with decorators lately and I am under the impression that there's not really a good way to do this. You can't access the local scope of the function from outside the function.

A potential solution, albeit not exactly what you want, using partial function application and function member data might be:

from functools import wraps

schema = { 'lang': {'type': 'string', 'required':True} }

def compare(schema, json): 
    return schema == json

def validate(schema):
    def outer(function):
        @wraps(function)
        def inner(*args, **kwargs):
            return function(*args, **kwargs)
        inner.validate = lambda json: compare(schema, json)
        return inner
    return outer

@validate(schema)
def main_function():
    print main_function.validate({ 'lang': {'type': 'string', 'required':True} })
    print main_function.validate({})

main_function()
# Output:
#     True
#     False

The compare function can obviously be customized to do something useful, but you still need to validate the json from within main_function . I don't know if there is a way around this without making a inner function within main_function .

It is also possible to "inject" a variable into a function's global scope and use a decorator to do the necessary book-keeping:

from functools import wraps

schema = {
    'lang': {'type': 'string', 'required':True}
}

def validate(schema):
    def my_decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            _backup, _check = None, False

            if 'valid_json' in func.func_globals:
                _backup, _check = func.func_globals['valid_json'], True

            valid_json = {'hi': 'mom'} # or whatever

            func.func_globals['valid_json'] = valid_json
            output = func(*args, **kwargs)

            if _check:
                func.func_globals['valid_json'] = _backup
            else:
                del func.func_globals['valid_json']

            return output
        return wrapper
    return my_decorator

@validate(schema)
def main_function():
    print valid_json

main_function() 
# Output:
#     {'hi': 'mom'}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM