Подтвердить что ты не робот

Хорошо ли иметь синтаксический сахар для работы композиции в Python?

Некоторое время назад я просмотрел документы 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]

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

Вы можете сделать это с помощью 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

Ответ 4

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

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

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

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