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は、すべての実装関数で同じ機能を記述しているということです。

具体的には、これらの関数はすべて官能的な検証フレームワークで使用するための「検証」関数を返します。したがって、 type のすべての関数は、f()後で によって実行される関数を返しSchema()ます。kは実際には です。つまり、値が OKallow_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))

これら 2 つの関数から返される関数は同等である必要があります。

私が書こうとしたのは、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

これをデバッグしようとしましたが、デバッガーが実際に装飾に入ることができませんでした。この (非常に長い) ブログ投稿シリーズで提起されたような命名の問題が原因で、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引数を取るため、これは理にかなっています。そのため、論理的に括弧が必要になります。それらを置き換えると、元のエラーが発生します。

デコレータは微妙で、明らかに何かが欠けています。これは関数のカリー化に似ていますが、あまり機能していません。これをどのように実装する必要があるかについて、何が欠けていますか?

4

1 に答える 1

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 の観点からは望ましくない場合があります。より洗練されたソリューションはおそらく可能ですが、この答えはやり過ぎです。

于 2015-10-30T01:30:27.253 に答える