Это то, что меня беспокоило в течение некоторого времени. Я изучил Haskell до того, как изучил Python, поэтому мне всегда нравилось думать о многих вычислениях как о отображении на список. Это прекрасно выражается пониманием списка (здесь я привожу питоническую версию):
result = [ f(x) for x in list ]
Однако во многих случаях мы хотим выполнить более одного оператора для x, например:
result = [ f(g(h(x))) for x in list ]
Это очень быстро становится неуклюжим и трудным для чтения.
Мое обычное решение - снова развернуть это в цикл for:
result = []
for x in list:
x0 = h(x)
x1 = g(x0)
x2 = f(x1)
result.append(x2)
Одна вещь, которая беспокоит меня без конца, - это необходимость инициализировать пустой список «result». Это мелочь, но меня это огорчает. Мне было интересно, есть ли какие-нибудь альтернативные эквивалентные формы. Одним из способов может быть использование локальной функции (это то, что они называются в Python?)
def operation(x):
x0 = h(x)
x1 = g(x0)
x2 = f(x1)
return x2
result = [ operation(x) for x in list ]
Есть ли какие-либо особые преимущества / недостатки у любой из двух вышеуказанных форм? Или, может быть, есть более элегантный способ?






Да, есть случаи, когда лучше вернуться к циклу for, но чаще я предпочитаю один из следующих подходов:
Для удобства чтения используйте соответствующие разрывы строк и отступы:
result = [blah(blah(blah(x)))
for x in list]
Или извлеките (достаточно) логики в другую функцию, как вы упомянули. Но не обязательно местные; Программисты Python предпочитают плоскую структуру вложенной, если вы видите разумный способ выделить функциональность.
Я пришел к Python из мира функционального программирования и разделяю ваше предубеждение.
Если вас беспокоит только последний результат, ваш последний ответ будет лучшим. Для всех, кто смотрит на это, ясно, что вы делаете.
Я часто беру любой код, который начинает усложняться, и перемещаю его в функцию. Это в основном служит комментарием для этого блока кода. (любой сложный код, вероятно, в любом случае нуждается в переписывании, и, вставив его в функцию, я могу вернуться и поработать над ней позже)
def operation(x):
x0 = h(x)
x1 = g(x0)
x2 = f(x1)
return x2
result = [ operation(x) for x in list]
Выбирайте стиль, который больше всего соответствует вашим вкусам. Я бы не беспокоился о производительности; только в том случае, если вы действительно видите какую-то проблему, вы можете попробовать перейти к другому стилю.
Вот еще несколько возможных предложений в дополнение к вашим предложениям:
result = [f(
g(
h(x)
)
)
for x in list]
Используйте прогрессивные списки:
result = [h(x) for x in list]
result = [g(x) for x in result]
result = [f(x) for x in result]
Опять же, это только вопрос стиля и вкуса. Выберите тот, который вам больше всего нравится, и придерживайтесь его :-)
Я согласен с вами - первый не такой уж и красивый. По большей части это была своего рода пробная имитация функциональных языков в синтаксисе Python.
Я бы заменил постепенное понимание списка конвейером выражений генератора.
А как насчет удобочитаемости? Составление списков следует декларативному шаблону, который довольно легко читать и понимать. Генераторы, хотя и более «элегантны», имеют тенденцию скрывать детали реализации далеко от исходного положения кода.
Вы можете легко выполнять композицию функций в Python.
Здесь демонстрируется способ создания новой функции, которая представляет собой композицию существующих функций.
>>> def comp( a, b ):
def compose( args ):
return a( b( args ) )
return compose
>>> def times2(x): return x*2
>>> def plus1(x): return x+1
>>> comp( times2, plus1 )(32)
66
Вот более полный рецепт функциональная композиция. Это должно сделать его менее неуклюжим.
Если это то, что вы делаете часто и с несколькими разными операторами, вы можете написать что-нибудь вроде
def seriesoffncs(fncs,x):
for f in fncs[::-1]:
x=f(x)
return x
где fncs - список функций. поэтому seriesoffncs ((f, g, h), x) вернет f (g (h (x))). Таким образом, если позже в коде потребуется отработать h (q (g (f (x)))), вы просто выполните seriesoffncs ((h, q, g, f), x), а не создадите новую функцию операций для каждая комбинация функций.
Что, если f () принимает более одного параметра?
Вариант функции dagw.myopenid.com:
def chained_apply(*args):
val = args[-1]
for f in fncs[:-1:-1]:
val=f(val)
return val
Вместо seriesoffncs ((h, q, g, f), x) теперь можно вызывать:
result = chained_apply(foo, bar, baz, x)
Насколько мне известно, в Python нет встроенного / собственного синтаксиса для композиции, но вы можете написать свою собственную функцию для компоновки без особых проблем.
def compose(*f):
return f[0] if len(f) == 1 else lambda *args: f[0](compose(*f[1:])(*args))
def f(x):
return 'o ' + str(x)
def g(x):
return 'hai ' + str(x)
def h(x, y):
return 'there ' + str(x) + str(y) + '\n'
action = compose(f, g, h)
print [action("Test ", item) for item in [1, 2, 3]]
Конечно, сочинять вне понимания не требуется.
print [compose(f, g, h)("Test ", item) for item in [1, 2, 3]]
Этот способ компоновки будет работать для любого количества функций (ну, вплоть до предела рекурсии) с любым количеством параметров для внутренней функции.
Мне нравится ваш второй фрагмент кода, но отступ первого немного жжет мне глаза.