Как я могу гарантировать, что список произвольных вызовов функций не будет с готовностью оценен после точки короткого замыкания при обработке в Python? - PullRequest
3 голосов
/ 31 октября 2019

Например, при

def expensive_call(x):
    print(x)
    if x == "d":
        return x
def expensive_call_2(x, y):
    print(x)
    print(y)
    return x + y

a = [expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")]
next((e for e in a if e is not None), 'All are Nones')

вывод равен

a
b
c
d
Out[22]: 'bc'

Поскольку expensive_call("d") оценивается с нетерпением, обратите внимание, что «d» выводится даже при вызове nextкороткие замыкания при втором вызове с выводом «bc».

Я жестко кодирую вызовы в списке a, и a не обязательно должна быть структурой данных списка.

Одно из возможных решений заключается в следующем:

a = ['expensive_call("a")', 'expensive_call_2("b", "c")', 'expensive_call("d")']
def generator():
    for e in a:
        r = eval(e)
        if r is not None:
            yield r
next(generator(), 'All are Nones')

Выходное значение равно

a
b
c
Out[23]: 'bc'

по желанию. Однако мне не очень нравится использовать eval. Я также предпочел бы не использовать какое-либо решение, которое изначально хранит указатель на функцию и аргументы отдельно, как (expensive_call, ("a")). В идеале я хотел бы иметь что-то вроде

a = lazy_magic([expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")])
next((e for e in a if e is not None), 'All are Nones')

Обратите внимание, что https://stackoverflow.com/a/3405828/2750819 - аналогичный вопрос, но он применяется только в том случае, если функции имеют одинаковую сигнатуру метода.

Ответы [ 2 ]

5 голосов
/ 31 октября 2019

Вы можете поместить их все в функцию и получить результаты:

def gen():
    yield expensive_call("a")
    yield expensive_call_2("b", "c")
    yield expensive_call("d")


result = next(
    (value for value in gen() if value is not None),
    'All are Nones')

Другое решение заключается в использовании partial приложение:

from functools import partial

calls = [partial(expensive_call, 'a'),
         partial(expensive_call_2, 'b', 'c'),
         partial(expensive_call, 'd')]

Затем оцените:

next((result for call in calls
      for result in [call()]
      if result is not None),
     'All results None')
3 голосов
/ 31 октября 2019

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

def lazy_fn(fn):
    return lambda *args: lambda: fn(*args)

(также можно выразить как lazy_fn = lambda fn: lambda *args: lambda: fn(*args) , если вам нравятся лямбды.)

Используйте его следующим образом:

@lazy_fn
def expensive_call(x):
    print(x)
    if x == "d":
        return x

@lazy_fn
def expensive_call_2(x, y):
    print(x)
    print(y)
    return x + y

a = [expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")]
print(next((e for e in map(lambda i: i(), a) if e is not None), 'All are Nones'))

Выходы:

a
b
c
bc

Обратите внимание, что вместо использования for e in a необходимо использовать for e in map(lambda i: i(), a).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...