Python: avaliação de função lenta em any () / all ()

Sep 27 2020

Operadores lógicos em Python são preguiçosos. Com a seguinte definição:

def func(s):
    print(s)
    return True

chamando a oroperadora

>>> func('s') or func('t')
's'

avalia apenas a primeira chamada de função, pois orreconhece que a expressão avalia para True, independentemente do valor de retorno da segunda chamada de função. andse comporta de forma análoga.

No entanto, ao usar any()(analogamente:) all()da seguinte maneira:

>>> any([func('s'), func('t')])
's'
't'

todas as chamadas de função são avaliadas, porque a lista interna é construída primeiro, antes de anycomeçar a iterar sobre os valores booleanos de seus itens. O mesmo acontece quando omitimos a construção da lista e apenas escrevemos

>>> any(func('s'), func('t'))
's'
't'

Dessa forma, perdemos o poder de anyestar em curto-circuito , o que significa que ele é interrompido assim que o primeiro elemento do iterável é verdadeiro. Se as chamadas de função forem caras, avaliar todas as funções antecipadamente é uma grande perda e um desperdício dessa capacidade de any. Em certo sentido, pode-se chamar isso de pegadinha do Python, porque pode ser inesperado para os usuários que tentam aproveitar esse recurso do anye porque anygeralmente é considerado apenas outra forma sintática de encadear uma sequência de orinstruções. Mas anyé apenas um curto-circuito , não é preguiçoso , e essa é uma diferença aqui.

anyestá aceitando um iterável . Portanto, deve haver uma maneira de criar um iterador que não avalie seus elementos antecipadamente, mas os transmita sem avaliação anye permita que avaliem anyapenas dentro de , a fim de obter uma avaliação totalmente preguiçosa.

Portanto, a questão é: Como podemos usar anyuma avaliação de função realmente preguiçosa? Isso significa: como podemos fazer um iterador de chamadas de função que anypode consumir, sem avaliar todas as chamadas de função com antecedência?

Respostas

7 jonathan.scholbach Sep 27 2020 at 16:28

Podemos usar uma expressão geradora , passando as funções e seus argumentos separadamente e avaliando apenas no gerador assim:

>>> any(func(arg) for arg in ('s', 't'))
's'

Para funções diferentes com assinaturas diferentes, isso pode ser parecido com o seguinte:

any(
    f(*args)
    for f, args in [(func1, ('s',)), (func2, (1, 't'))]
)

Dessa forma, anyirá parar de chamar o next()elemento no gerador assim que uma chamada de função no gerador for avaliada como True, e isso significa que a avaliação da função é totalmente lenta.

Outra maneira bacana de adiar a avaliação da função foi mencionada por wjandrea em um comentário: Também podemos usar expressões lambda , como:

>>> any(f() for f in [lambda: func('s'), lambda: func('t')]
's'