在 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]
问题: 如果没有语言支持,我们无法在内置函数或 lambda 上使用此语法,如下所示:
((lambda x: x + 3) >> abs)(2)
问题: 有什么用吗?值得在 python 邮件列表上讨论吗?
Some time ago I looked over Haskell docs and found it's functional composition operator really nice. So I've implemented this tiny decorator:
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]
The problem:
without language support we can't use this syntax on builtin functions or lambdas like this:
((lambda x: x + 3) >> abs)(2)
The question:
is it useful? Does it worth to be discussed on python mail list?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
恕我直言:不,不是。虽然我喜欢 Haskell,但这似乎不适合 Python。您可以执行
compose(f1, f2, f3)
而不是(f1 >>> f2 >> f3)
,这可以解决您的问题 - 您可以使用它可以与任何可调用函数一起使用,而无需任何重载、装饰或更改核心(IIRC 有人已经提出了 functools.compose 至少一次;我现在找不到它)。此外,语言定义现在已经冻结,因此他们可能会拒绝这种更改 - 请参阅 PEP 3003。
IMHO: no, it's not. While I like Haskell, this just doesn't seem to fit in Python. Instead of
(f1 >> f2 >> f3)
you can docompose(f1, f2, f3)
and that solves your problem -- you can use it with any callable without any overloading, decorating or changing the core (IIRC somebody already proposedfunctools.compose
at least once; I can't find it right now).Besides, the language definition is frozen right now, so they will probably reject that kind of change anyway -- see PEP 3003.
函数组合在 Python 中并不是一种超级常见的操作,尤其是在显然需要组合运算符的情况下。如果添加了某些内容,我不确定我是否喜欢选择 Python 的
<<
和>>
,这对我来说并不像看起来那么明显为了你。我怀疑很多人会更喜欢函数
compose
,其顺序没有问题:compose(f, g)(x)
意味着f(g(x))
,与数学中的o
和 Haskell 中的.
的顺序相同。当英文单词可以使用时,Python 会尽量避免使用标点符号,尤其是当特殊字符没有广为人知的含义时。 (对于看起来太有用而不容错过的东西,例如用于装饰器的@(非常犹豫)和用于函数参数的*和**。)如果你确实选择将其发送到python-ideas,你可能会如果你能在 stdlib 或流行的 Python 库中找到一些函数组合可以使代码更加清晰、易于编写、可维护或高效的实例,那么你会赢得更多的人。
Function composition isn't a super-common operation in Python, especially not in a way that a composition operator is clearly needed. If something was added, I am not certain I like the choice of
<<
and>>
for Python, which are not as obvious to me as they seem to be to you.I suspect a lot of people would be more comfortable with a function
compose
, the order of which is not problematic:compose(f, g)(x)
would meanf(g(x))
, the same order aso
in math and.
in Haskell. Python tries to avoid using punctuation when English words will do, especially when the special characters don't have widely-known meaning. (Exceptions are made for things that seem too useful to pass up, such as @ for decorators (with much hesitation) and * and ** for function arguments.)If you do choose to send this to python-ideas, you'll probably win a lot more people if you can find some instances in the stdlib or popular Python libraries that function composition could have made code more clear, easy to write, maintainable, or efficient.
您可以使用
reduce
来完成此操作,尽管调用顺序只是从左到右:You can do it with
reduce
, although the order of calls is left-to-right only:我没有足够的 Python 经验来判断语言的改变是否值得。但我想描述当前语言可用的选项。
为了避免产生意外的行为,函数组合最好遵循标准数学(或 Haskell)运算顺序,即
f ∘ g ∘ h
应该意味着应用h
,然后 <代码>g,然后f
。如果您想使用 Python 中的现有运算符,请说
<<
,正如您提到的,您会遇到 lambda 和内置函数的问题。除了__lshift__
之外,您还可以通过定义反映版本__rlshift__
让您的生活变得更轻松。这样,与可组合对象相邻的 lambda/内置函数就会得到处理。当您确实有两个相邻的 lambda/内置函数时,您需要使用composable
显式转换它们(仅其中之一),如 @si14 建议的那样。请注意,我真正的意思是__rlshift__
,而不是__rshift__
;事实上,我建议完全不要使用 __rshift__ ,因为尽管运算符的形状提供了方向提示,但顺序更改还是令人困惑。但您可能还需要考虑另一种方法。 Ferdinand Jamitzky 有一个很棒的秘诀,用于在 Python 中定义伪中缀运算符,甚至可以工作在内置插件上。有了这个,你可以写
f |o| g
表示函数组合,实际上看起来很合理。I don't have enough experience with Python to have a view on whether a language change would be worthwhile. But I wanted to describe the options available with the current language.
To avoid creating unexpected behavior, functional composition should ideally follow the standard math (or Haskell) order of operations, i.e.,
f ∘ g ∘ h
should mean applyh
, theng
, thenf
.If you want to use an existing operator in Python, say
<<
, as you mention you'd have a problem with lambdas and built-ins. You can make your life easier by defining the reflected version__rlshift__
in addition to__lshift__
. With that, lambda/built-ins adjacent tocomposable
objects would be taken care of. When you do have two adjacent lambda/built-ins, you'll need to explicitly convert (just one of) them withcomposable
, as @si14 suggested. Note I really mean__rlshift__
, not__rshift__
; in fact, I would advise against using__rshift__
at all, since the order change is confusing despite the directional hint provided by the shape of the operator.But there's another approach that you may want to consider. Ferdinand Jamitzky has a great recipe for defining pseudo infix operators in Python that work even on built-ins. With this, you can write
f |o| g
for function composition, which actually looks very reasonable.