2015-10-30 91 views
2

在核心,我想要做的是采取一些看起来像这样未经修饰的验证功能功能:处理函数参数与装饰

def f(k: bool): 
    def g(n): 
     # check that n is valid 
     return n 
    return g 

,使他们看起来像这样装饰验证功能

@k 
def f(): 
    def g(n): 
     # check that n is valid 
     return n 
    return g 

的想法在这里在于k在所有的执行功能的描述相同的功能。

具体而言,这些函数都返回“验证”功能,以便与voluptuous validation framework一起使用。所以f()类型的所有函数都返回一个函数,稍后由Schema()执行。 k实际上是allow_none,也就是说一个标志确定None的值是否正确。一个非常简单的例子可能是这个示例使用代码

x = "Some input value." 
y = None 
input_validator = Schema(f(allow_none=True)) 
x = input_validator(x) # succeeds, returning x 
y = input_validator(y) # succeeds, returning None 
input_validator_no_none = Schema(f(allow_none=False)) 
x = input_validator(x) # succeeds, returning x 
y = input_validator(y) # raises an Invalid 

而不改变取样使用代码我试图通过改变未修饰的验证功能,装饰验证函数来达到同样的效果。举一个具体的例子,改变这个:

def valid_identifier(allow_none: bool=True): 
    min_range = Range(min=1) 
    validator = Any(All(int, min_range), All(Coerce(int), min_range)) 
    return Any(validator, None) if allow_none else validator 

要这样:

@allow_none(default=True) 
def valid_identifier(): 
    min_range = Range(min=1) 
    return Any(All(int, min_range), All(Coerce(int), min_range)) 

功能来自这两个返回的应该是等价的。

什么我试着写是这样的,利用decorator库:

from decorator import decorator 

@decorator 
def allow_none(default: bool=True): 
    def decorate_validator(wrapped_validator, allow_none: bool=default): 
     @wraps(wrapped_validator) 
     def validator_allowing_none(*args, **kwargs): 
      if allow_none: 
       return Any(None, wrapped_validator) 
      else: 
       return wrapped_validator(*args, **kwargs) 
     return validator_allowing_none 
    return decorate_validator 

而且我有一个unittest.TestCase为了测试这是否如期望的那样

@allow_none() 
def test_wrapped_func(): 
    return Schema(str) 

class TestAllowNone(unittest.TestCase): 

    def test_allow_none__success(self): 
     test_string = "blah" 

     validation_function = test_wrapped_func(allow_none=False) 
     self.assertEqual(test_string, validation_function(test_string)) 
     self.assertEqual(None, validation_function(None)) 

但我的测试返回以下失败:

def validate_callable(path, data): 
     try: 
>   return schema(data) 
E   TypeError: test_wrapped_func() takes 0 positional arguments but 1 was given 

我试过调试这个,但是无法让调试器实际进入装饰。我怀疑由于命名问题(如this (very lengthy) blog post series中提出的问题),test_wrapped_func没有正确设置它的参数列表,所以装饰器从未被执行,但它也可能完全是别的。

我尝试了一些其他的变化。通过从@allow_none删除功能括号:

@allow_none 
def test_wrapped_func(): 
    return Schema(str) 

我得到一个不同的错误:

>  validation_function = test_wrapped_func(allow_none=False) 
E  TypeError: test_wrapped_func() got an unexpected keyword argument 'allow_none' 

跌落@decorator失败:

>  validation_function = test_wrapped_func(allow_none=False) 
E  TypeError: decorate_validator() missing 1 required positional argument: 'wrapped_validator' 

这是有道理的,因为@allow_none需要一个参数,因此逻辑上需要括号。替换它们会导致原始错误。

装饰师是微妙的,我很明显在这里失去了一些东西。这与currying一个函数类似,但它不是很有效。我错过了应该如何实施?

回答

2

我想你会把你的allow_none=default参数放在错误的嵌套层次上。它应该在最内层的函数(包装器)上,而不是装饰器(中层)。

尝试是这样的:

def allow_none(default=True): # this is the decorator factory 
    def decorator(validator): # this is the decorator 
     @wraps(validator) 
     def wrapper(*args, allow_none=default, **kwargs): # this is the wrapper 
      if allow_none: 
       return Any(None, validator) 
      else: 
       return validator(*args, **kwargs) 
     return wrapper 
    return decorator 

如果您不需要默认的是可调节,你可以摆脱嵌套的最外层,只是使默认值在包装功能的常数(或者如果您的呼叫者将总是传递一个值,则省略它)。请注意,正如我在上面所写的,封装器的参数allow_none是一个仅关键字参数。如果您想将其作为位置参数传递,您可以将其移动到*args之前,但这要求它是第一个位置参数,从API的角度来看这可能不是所期望的。更复杂的解决方案可能是可能的,但这个答案矫枉过正。

+0

哇!这工作。我想我甚至明白了为什么这是有效的。谢谢您的帮助! –