2016-03-02 99 views
3

我刚刚开始使用Python中的装饰器来玩游戏。我目前无法找出处理这种用例的最佳方法。由于装饰函数不能从装饰器访问局部变量。如何从我的装饰器访问装饰函数中的局部变量

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 

我的问题是:如何访问valid_json从我的装饰功能,能够插入到我的数据库之后。这种情况的最佳做法是什么?

编辑:我正在运行pypy 2.4

+0

是否可以让你想如何你的装饰是使用,即你理想的语法? –

+0

@JaredGoguen我编辑了主帖子 –

+0

装饰者中的“body”是什么?为什么'wrapper'在某个时候不会调用'func'? – Blckknght

回答

1

IIUC你问什么,下面可以这样做。我不能说我为这个解决方案而疯狂(或者关于你在问什么),这对我的品味来说太“神奇”了。为了简单起见,我们假设模式和一切都是整数,并且通过模式验证某些内容仅仅意味着将模式整数添加到它(这仅仅是为了说明)。因此,我们有:

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

现在我们可以写出下面的装饰:

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 

现在我们可以使用它作为这样:

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


>>> bar(1) 
13 

当然,你也可以进一步阐述这一点,采取模式+参数对,处理关键字参数等等。但这是这个答案的原理。我认为这有点有趣,但我不会使用它。

1

你不能直接这样做。被修饰的函数的作用域在被修饰器包装之前被设置,并且它与由修饰器添加的包装器建立的作用域没有连接。

你能做的最好是具备的功能接受该装饰通过额外的参数,例如:

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

与装饰明确添加valid_json=valid_json当它调用包装的函数,例如return func(*args, **kwargs, valid_json=valid_json)

1

我最近也一直在玩装饰者,我的印象是没有一个好的方法来做到这一点。您不能从函数外部访问函数的本地范围。

一个潜在的解决方案,虽然不正是你想要的,使用部分函数应用和函数成员的数据可能是:

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 

compare功能可以显着客户要求做一些有用的东西,但你仍然需要从main_function验证json。我不知道在main_function范围内是否有解决方法,而没有制定内部功能。

也可能“注入”变量到一个函数的全球范围和使用装饰做必要的簿记:

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'} 
+1

聪明,尽管应该指出这不是线程安全的(当'func'正在运行时,对于在同一全局作用域中执行的所有代码,被包装函数的'valid_json'是一些其他值;如果两次调用'func'同时出现在不同的线程中,其中一个会踩踏另一个'valid_json')。 – ShadowRanger