2015-10-30 125 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

哇!這工作。我想我甚至明白了爲什麼這是有效的。謝謝您的幫助! –