【问题标题】:Is it a good idea to have a syntax sugar to function composition in Python?在 Python 中为函数组合提供语法糖是个好主意吗?
【发布时间】:2010-02-17 15:00:19
【问题描述】:

前段时间我查看了 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 邮件列表上讨论吗?

【问题讨论】:

  • ...但仍然可以这样写: print (composable(lambda x: x + 3) >> composable(abs))(-4)
  • 对于数学函数,通常使用 * 表示合成,使用 + 表示函数求和,例如 (f * g + h)(x) = f(g(x)) + h(x)。并且还可以使用幂运算符:f**2(x) = f(f(x)) 等...

标签: python function-composition


【解决方案1】:

恕我直言:不,不是。虽然我喜欢 Haskell,但这似乎不适合 Python。而不是(f1 &gt;&gt; f2 &gt;&gt; f3),你可以做compose(f1, f2, f3),这可以解决你的问题——你可以将它与任何可调用的对象一起使用,而不会出现任何重载、装饰或更改核心(IIRC 有人已经提议functools.compose 至少一次;我不能马上找到)。

此外,语言定义现在被冻结了,所以他们可能无论如何都会拒绝这种改变——见PEP 3003

【讨论】:

  • compose() 不太清楚。构图的方向是什么?您必须查看文档才能回答这个问题。
  • @si14:不知道 Haskell 我找不到 >> 和
  • @Nikow:我也不知道,但是>>和> f2 >> f3意思是“把f1的结果放到f2,然后f2 到 f3 的结果”。另一种选择是 f1(f2(f3(x))) 带有很多凌乱的大括号。
  • @si14:我看到了其中的逻辑,但仍然认为这主要是习惯问题。至少有一种函数式语言过度使用括号;-) 对我来说,这听起来类似于(Java-)人们抱怨 len(a) 不是面向对象的,应该是 a.len()。
  • &gt;&gt; 在 Haskell 中意味着一些重要的不同。这是&gt;&gt;= 的一个特例,它包括一种函数组合,但也从Monad 中解包。 @si14:事实上我假设你的意思是 f1>>f2 是 lambda x: f2(f1(x))。我现在明白你为什么要这样解释它了,但这与数学上熟悉的组合运算符相反。
【解决方案2】:

函数组合在 Python 中并不是一个超级常见的操作,尤其是在显然需要组合运算符的情况下。如果添加了某些内容,我不确定我是否喜欢为 Python 选择 &lt;&lt;&gt;&gt;,这对我来说并不像对您而言那么明显。

我怀疑很多人会更喜欢函数compose,它的顺序没有问题:compose(f, g)(x) 表示f(g(x)),与数学中的o. 的顺序相同在哈斯克尔。 Python 尽量避免在英语单词可以使用的情况下使用标点符号,尤其是当特殊字符没有广为人知的含义时。 (对于看起来太有用而不能忽略的东西,例如 @ 用于装饰器(犹豫不决)和 * 和 ** 用于函数参数。)

如果您确实选择将其发送到 python-ideas,如果您可以在 stdlib 或流行的 Python 库中找到一些函数组合可以使代码更清晰、更易于编写的实例,那么您可能会赢得更多的人、可维护或高效。

【讨论】:

  • 您能否解释一下为什么&gt;&gt;&lt;&lt; 不明显?我不是不同意,我只是想看看程序员之间是否有共识。
  • @max,我不知道人们如何描述为什么某事不明显。根本没有理由我会看着&lt;&lt;&gt;&gt; 说:“啊,它们应该用于这个!”
  • @max 一次尝试:它不太明显,因为没有这种用法的传统。另外不太明显,因为在不同的意义上 &gt;&gt; 用于扩展打印语句('print chevron')。同样不明显shifting operationsspecial functions所有 callables 的组合联系起来:不清楚这会取消哪些类型的对象 .
  • @max: 还有:Explicit is better than implicit。这在多大程度上是明确的:(f1 &gt;&gt; f2 &lt;&lt; f3 &gt;&gt; f4)(3)?
  • @naxa,这并不明显,但我不知道它隐含了什么。
【解决方案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

【讨论】:

  • 对,但那不是中缀(即# 5 -&gt; f1 -&gt; f2 -&gt; f3 -&gt; 116
【解决方案4】:

我没有足够的 Python 经验来判断语言更改是否值得。但我想描述当前语言的可用选项。

为避免产生意外行为,理想情况下,函数组合应遵循标准数学(或 Haskell)操作顺序,即,f ∘ g ∘ h 应意味着应用 h,然后是 g,然后是 f

如果你想在 Python 中使用现有的运算符,比如 &lt;&lt;,正如你提到的,你会遇到 lambdas 和内置函数的问题。除了__lshift__ 之外,您还可以通过定义反射版本__rlshift__ 让您的生活更轻松。这样,与 composable 对象相邻的 lambda/built-ins 将得到处理。当您确实有两个相邻的 lambda/内置插件时,您需要使用 composable 显式转换(仅其中一个),正如 @si14 建议的那样。注意我的意思是__rlshift__,而不是__rshift__;事实上,我完全不建议使用__rshift__,因为尽管操作符的形状提供了方向提示,但顺序变化令人困惑。

但您可能需要考虑另一种方法。 Ferdinand Jamitzky 有一个 great recipe 用于在 Python 中定义伪中缀运算符,即使在内置函数上也可以使用。有了这个,你可以写f |o| g进行函数组合,实际上看起来很合理。

【讨论】:

  • 澄清一下,在引用的框架中,每个函数都必须定义为 Infix 运算符,而不是像 Mathematica(和其他)这样的语言,其中函数可以在不重新定义的情况下组合。跨度>
猜你喜欢
  • 2018-09-02
  • 2010-09-23
  • 2011-05-14
  • 2015-06-29
  • 2019-03-11
  • 1970-01-01
  • 2010-12-28
  • 2015-09-04
  • 1970-01-01
相关资源
最近更新 更多