Является ли хорошей идеей иметь синтаксический сахар для композиции функций в Python? - PullRequest
18 голосов
/ 17 февраля 2010

Некоторое время назад я просмотрел документы на Haskell и обнаружил, что оператор функциональной композиции действительно хорош. Итак, я реализовал этот крошечный декоратор:

from functools import partial

class _compfunc(partial):
    def __lshift__(self, y):
        f = lambda *args, **kwargs: self.func(y(*args, **kwargs)) 
        return _compfunc(f)

    def __rshift__(self, y):
        f = lambda *args, **kwargs: y(self.func(*args, **kwargs)) 
        return _compfunc(f)

def composable(f):
    return _compfunc(f)

@composable    
def f1(x):
    return x * 2

@composable
def f2(x):
    return  x + 3

@composable
def f3(x):
    return (-1) * x

@composable
def f4(a):
    return a + [0]

print (f1 >> f2 >> f3)(3) #-9
print (f4 >> f1)([1, 2]) #[1, 2, 0, 1, 2, 0]
print (f4 << f1)([1, 2]) #[1, 2, 1, 2, 0]

Проблема: без языковой поддержки мы не сможем использовать этот синтаксис для встроенных функций или лямбд, как это:

((lambda x: x + 3) >> abs)(2)

Вопрос: это полезно? Стоит ли обсуждать их в списке рассылки python?

Ответы [ 4 ]

8 голосов
/ 17 февраля 2010

ИМХО: нет, это не так. Хотя мне нравится Haskell, в Python это не совсем подходит. Вместо (f1 >> f2 >> f3) вы можете сделать compose(f1, f2, f3), и это решит вашу проблему - вы можете использовать его с любым вызываемым объектом без какой-либо перегрузки, украшения или изменения ядра (IIRC кто-то уже предлагал functools.compose хотя бы один раз; я не могу найди это прямо сейчас).

Кроме того, определение языка сейчас заморожено, поэтому они, вероятно, в любом случае отклонят такого рода изменения - см. PEP 3003 .

6 голосов
/ 07 октября 2011

Вы можете сделать это с помощью reduce, хотя порядок вызовов только слева направо:

def f1(a):
    return a+1

def f2(a):
    return a+10

def f3(a):
    return a+100

def call(a,f):
    return f(a)


reduce(call, (f1, f2, f3), 5)
# 5 -> f1 -> f2 -> f3 -> 116
reduce(call, ((lambda x: x+3), abs), 2)
# 5
6 голосов
/ 17 февраля 2010

Композиция функций не является супер-распространенной операцией в Python, особенно не так, как явно нужен оператор композиции. Если что-то было добавлено, я не уверен, что мне нравится выбор << и >> для Python, которые для меня не так очевидны, как для вас.

Я подозреваю, что многим людям будет удобнее использовать функцию compose, порядок которой не вызывает проблем: compose(f, g)(x) будет означать f(g(x)), такой же порядок, как o в математике и . в Хаскеле. Python старается избегать использования знаков препинания, когда подойдут английские слова, особенно когда специальные символы не имеют широко известного значения. (Исключения делаются для вещей, которые кажутся слишком полезными, например, @ для декораторов (с большими колебаниями) и * и ** для аргументов функций.)

Если вы решите отправить это python-ideas, вы, вероятно, выиграете гораздо больше людей, если найдете в экземпляре stdlib или в популярных библиотеках Python несколько экземпляров, в которых композиция функций могла бы сделать код более понятным и простым для написания. , ремонтопригодный или эффективный.

1 голос
/ 05 декабря 2012

У меня недостаточно опыта работы с Python, чтобы понять, стоит ли менять язык. Но я хотел описать опции, доступные на текущем языке.

Чтобы избежать непредвиденного поведения, функциональная композиция в идеале должна следовать стандартному математическому (или хаскелевскому) порядку операций, то есть f ∘ g ∘ h должно означать применить h, затем g, затем f.

Если вы хотите использовать существующий оператор в Python, скажите <<, поскольку вы упоминаете, что у вас будут проблемы с лямбдами и встроенными модулями. Вы можете упростить свою жизнь, определив отраженную версию __rlshift__ в дополнение к __lshift__. При этом лямбда / встроенные модули, смежные с composable объектами, будут учтены. Когда у вас есть две смежные лямбда / встроенные команды, вам нужно явно преобразовать (только одну) их с помощью composable, как предложено @ si14. Обратите внимание, я действительно имею в виду __rlshift__, а не __rshift__; на самом деле, я бы не советовал вообще использовать __rshift__, так как изменение порядка сбивает с толку, несмотря на указание направления, предоставленное формой оператора.

Но есть другой подход, который вы можете рассмотреть. У Фердинанда Ямицкого есть отличный рецепт для определения псевдоинфиксных операторов в Python, которые работают даже со встроенными модулями. При этом вы можете написать f |o| g для композиции функций, что на самом деле выглядит очень разумно.

...