Менеджер контекста для проверки данных

Я пытаюсь обдумать хорошее решение этой проблемы, и ничего не приходит в голову. В качестве упражнения я пытаюсь создать диспетчер контекста, который будет обрабатывать проверку данных, например:

validation = lambda x: len(x) <= 10

with validator(validation):
    some_data = input("Please enter a name of 10 characters or less: ")

print(some_data)

# OUTPUT
>> Please enter a name of 10 characters or less: FooBarSpamEggs
>> Please enter a name of 10 characters of less: Adam
Adam

Первоначально я думал сделать это с помощью unittest.mock.patch, но понял, что не могу вызвать исходную функцию, пока она исправлена, например:

def patched(validation, *args):
    while True:
        p = __builtins__.input(args) # Doesn't work
        if validation(p):
            break
    return p

with unittest.mock.patch('builtins.input', patched):
    input("Some prompt here: ")
# fails on recursion error as patched calls itself

Затем я решил написать декоратор для проверки одной строки, но это действительно полезно, только если вы можете сделать что-то вроде:

@validate(lambda x: int(x) == 6)
p = input("How many sides does a d6 have? ")
# can't decorate a function call

Однако я зациклился на этой идее контекстного менеджера. К сожалению, я не знаю, имеет ли менеджер контекста доступ к своему содержимому или он ограничен только своими аргументами. есть идеи?

Кроме того, я знаю, что могу отобразить эту функциональность в функции, например:

def validate_input(prompt, validation, msg_if_fail=None):
    while True:
        p = input(prompt)
        if validation(p):
            break
        if msg_if_fail is not None:
            print(msg_if_fail)
    return p

Но это не так красиво. Это, как я уже сказал, больше упражнение, чем практическая задача.


person Adam Smith    schedule 11.09.2014    source источник


Ответы (2)


Вы можете использовать обычный менеджер контекста, чтобы обернуть unittest.mock.patch и сохранить ссылку на исходную функцию input перед ее исправлением. Затем вы можете передать исходный input вашей функции patched:

import unittest.mock
import contextlib
from functools import partial

def patched(validation, orig_input, *args):
    while True:
        p = orig_input(*args)
        if validation(p):
            break
    return p

@contextlib.contextmanager
def validator(validate_func):
    func = partial(patched, validate_func, input)  # original input reference saved here
    patch = unittest.mock.patch('builtins.input', func)
    patch.start()
    try:
        yield 
    finally:
        patch.stop()

validation = lambda x: len(x) <= 10

Затем вы можете использовать контекстный менеджер следующим образом:

with validator(validation):
    x = input("10 or less: ")

x = input("10 or less (unpatched): ")
print("done")

Пример вывода:

10 or less: abcdefghijklmnop
10 or less: abcdefgdfgdgd
10 or less: abcdef
10 or less (unpatched): abcdefghijklmnop
done
person dano    schedule 11.09.2014
comment
Я бы сказал, что это немного перепроектировано, хотя! (Вы, кажется, тоже знаете об этом, но я решил, что все равно скажу это.) - person dano; 12.09.2014
comment
Нет такой инженерии, как чрезмерная инженерия. - person Adam Smith; 12.09.2014

Декоратор, который принимает параметры, мог бы сделать это довольно хорошо (http://www.artima.com/weblogs/viewpost.jsp?thread=240845):

max10 = lambda x: len(x) <= 10

def validator(test):
    def wrap(func):
        def wrapped(*args, **kwargs):
            result = func(*args, **kwargs)
            if test(result):
                return result
            return None
        return wrapped
    return wrap

Обычный способ использования:

@validator(max10)
def valid_input(prompt="Please enter a name: "):
    return raw_input(prompt)

print valid_input()

Применение декоратора вручную выглядит ближе к тому, что вы просите:

valid_input = validator(max10)(raw_input)
print valid_input("Please enter a name: ")
person systemjack    schedule 12.09.2014