Есть ли идиома Python для оценки списка функций/выражений с коротким замыканием?

Я написал простой сценарий для решения «логической головоломки», типа головоломки из школы, где вам дается ряд правил, а затем вы должны быть в состоянии найти решение для таких задач, как «Есть пять музыкантов по имени A, B, C , D и E, играющие на концерте, играют друг за другом... если A идет перед B, а D не последним... в каком порядке кто и когда играет?" и т.п.

Чтобы оценить возможные решения, я написал каждое «правило» как отдельную функцию, которая будет оценивать, допустимо ли возможное решение (представленное просто как список строк), например

#Fifth slot must be B or D
def rule1(solution):
    return solution[4] == 'B' or solution[4] == 'D'

#There must be at least two spots between A and B
def rule2(solution):
    returns abs(solution.index('A') - solution.index('B')) >= 2

#etc...

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

Сначала я написал самое простое:

def is_valid(solution):
    return rule1(solution) and rule2(solution) and rule3(solution) and ...

Но это выглядело довольно некрасиво. Я подумал, что, возможно, я мог бы сделать это чтение более элегантным с помощью чего-то вроде понимания списка...

def is_valid(solution)
    rules = [rule1, rule2, rule3, rule4, ... ]
    return all([r(solution) for f in rules])

... но затем я понял, что, поскольку понимание списка генерируется до оценки функции all(), побочным эффектом этого является отсутствие короткого замыкания вообще - каждое правило будет оцениваться, даже если первое возвращает False.

Итак, мой вопрос: есть ли более Pythonic/функциональный способ оценить список выражений True/False с коротким замыканием, без необходимости выписывать длинный список return f1(s) and f2(s) and f3(s) ... ?


person matt b    schedule 04.08.2010    source источник


Ответы (1)


Используйте выражение генератора:

rules = [ rule1, rule2, rule3, rule4, ... ]
rules_generator = ( r( solution ) for r in rules )
return all( rules_generator )

Синтаксический сахар: лишние скобки можно опустить:

rules = [ rule1, rule2, rule3, rule4, ... ]
return all( r( solution ) for r in rules )

Генератор — это (в основном) объект с методом .next(), который возвращает следующий элемент в некоторой итерации. Это означает, что они могут делать полезные вещи, такие как чтение файла по частям, не загружая его целиком в память, или выполнять итерации до огромных целых чисел. Вы можете прозрачно перебирать их с помощью for циклов; Python обрабатывает это за кулисами. Например, range — это генератор в Py3k.

Вы можете свернуть свои собственные выражения генератора, используя оператор yield вместо return в определении функции:

def integers():
    i = 0
    while True:
        yield i

и Python будет обрабатывать сохранение состояния функции и так далее. Они потрясающие!

person Katriel    schedule 04.08.2010
comment
Так является ли основное отличие здесь отсутствием скобок в return all([r(solution) for r in rules]), таким образом, не создавая список всех результатов до того, как будет оценено all()? - person matt b; 04.08.2010
comment
да. В обоих случаях аргумент all оценивается перед передачей, но оценка понимания списка создает весь список в памяти, тогда как оценка выражения генератора создает объект генератора, который загружает элементы по запросу. - person Katriel; 04.08.2010
comment
Я искал подобное, но мне хотелось короткого замыкания, как только что-то было True вместо False. Я изменил all() на any() и все заработало. - person Doug Harris; 29.06.2011