六行代码实现 Python 管道

2021-01-10 17:14:24 +08:00
 abersheeran

不多说,直接上代码。

from functools import partial

class F(partial):
    def __ror__(self, other):
        if isinstance(other, tuple):
            return self(*other)
        return self(other)

使用样例。

range(10) | F(filter, lambda x: x % 2) | F(sum)

更详细的前因后果介绍,可以看 https://aber.sh/articles/Python-Pipe/ ,实在是懒得复制到 V2EX 调样式了。有兴趣的就看看,没兴趣的就复制代码去用就完事。

小小的得瑟一下,这是最近几天写的最满意、最有用的代码。妙手偶得之~

13656 次点击
所在节点    Python
122 条回复
frostming
2021-01-11 17:19:37 +08:00
@sapocaly 只能魔改 builtins 了,参考我上面的回答
sapocaly
2021-01-11 17:46:39 +08:00
@frostming 不 我发现我之前的纠结没有意义, 如果只是要实现 range(10) | (filter, lambada x:x) |sum 或 range(10).filter(lambda x: x % 2).sum()这样的话,你只需开头用一个自己定一个 class,前者(pipe)照规矩 override ror 就行了,后者 chaining 的话我想用 metaclass 应该不难,不知道有没有更简单的。我觉得这种已经很 hack 了,改 buildin 就太过分了,没必要也不合适。当然如果你非要实现 range(10) | filter(lambada x:x) |sum 这样的,确实可能只能 patch 了,当然我会建议 patch a limited scope 而不是 explicitly override
sapocaly
2021-01-11 17:52:14 +08:00
补充一下,比较容易实现的 chain 的用法会是 Chain.range(10).filter(lambda x:x % 2).sum().end(),稍微难一点的是 Chain.range(10).filter(lambda x:x % 2).sum(),我猜这里我得想下类似 lazy eval 的实现。如果想直接 range(10).filter(lambda x:x % 2).sum(),我可能会用 with patched_buildin(): range(10).filter(lambda x:x % 2).sum()这样的 syntax 当然讨厌 indentation 的话自然也有别的办法
sapocaly
2021-01-11 17:54:13 +08:00
想了想似乎 metaclass 都不需要
abersheeran
2021-01-11 18:03:21 +08:00
@NeezerGu 对的。

@admirez 可以,此管道可以用在任何 Python 代码里。我就是在处理数据的时候想到的这个。

@sapocaly 你说的这个不就是楼上推荐的那个库吗?你喜欢的话可以去试试,上面有链接。我个人喜欢 | F(...) 这么用。没有黑魔法,工作原理简单,对 debug 友好,最重要的优点是每一次管道运算都是真实的运算出了结果,我可以在任何一次运算之后拿去用,而不需要特意标识一个 END 或者其他什么的。
xuboying
2021-01-11 18:04:45 +08:00
楼主不去写 perl6 可惜了,python 的名人名言是只能有一种写法,不遵守人的可以去开发其他语言(逃)
Tumblr
2021-01-11 18:07:05 +08:00
啊!这很 PowerShell !很 pwsh !
abersheeran
2021-01-11 18:10:13 +08:00
@mckelvin 我文章里解释过了。管道的数据处理顺序跟阅读顺序是一样的。利于阅读代码。
你说的这个嵌套用法当然可以,可是阅读顺序和实际的执行顺序是相反的,它先执行的内部函数再执行的外部函数。
NeezerGu
2021-01-11 18:12:21 +08:00
@abersheeran 原来如此,感谢哈。
sapocaly
2021-01-11 18:15:46 +08:00
@abersheeran 额其实我很久没写 python 了,倒不是真要用到,不过觉得你提出的这个问题挺有意思所以想想有啥别的实现。我看了那个库介绍,和我理想中的比较接近,我觉得 END 是可以去掉的,但当然这又要加很多 hack 。 至于哪种 syntax 我比较喜欢,我觉得都还行,不过我觉得我现实中不会去用。简单的逻辑没必要,复杂的逻辑 chain 或者 pipe 的可读性并不会增加,也大概率效率不是最好的。当然,我也很久没用 python 了,效率这个还得具体问题具体分析。
xuboying
2021-01-11 18:31:51 +08:00
为何要特殊处理 tuple,没有 get 到 OP 的意图,有什么特殊情况可以简便写法么?
>>> (1, 2 , 3) | F(filter, lambda x: x % 2) | F(list)
TypeError: filter expected 2 arguments, got 4

>>> [1, 2 , 3] | F(filter, lambda x: x % 2) | F(list)
[1, 3]

>>> list(filter( lambda x: x % 2,(1,2,3)))
[1, 3]
abersheeran
2021-01-11 19:20:39 +08:00
@sapocaly 嗯,关于此的讨论你可以看看楼上我、 @frostming 和 @Wincer 发的。更好、更简单的实现方法目前来看,在 CPython 里是没有的。如果自己实现一个 Python 超集的编译器,那就可以做到了。

@iqxd @xuboying 因为我希望能传递多个参数给被 F 包裹的函数。一般来说,函数返回多值是 return x, y, z 这样,它的实际类型是一个 tuple 。

比如

def fa(...): return a, b, c
def fb(a, b, c, d, e): return y

就可以直接 data | F(fa) | F(fb), 而不是 data | F(fa) | F(lambda args: F(fb(*args))) 这样写。
maddevil
2021-01-11 19:24:49 +08:00
from functools import partial

F = type("F", (partial,), {"__ror__": lambda self, other: self(other)})
range(10) | F(filter, lambda x: x & 1) | F(sum) | F(print)
geebos
2021-01-11 19:44:23 +08:00
个人觉得可以写成装饰器

```python
from functools import partial

class F(partial):
def __init__(self, func):
self.__func = func

def __ror__(self, other):
if isinstance(other, tuple):
return self(*other)
return self(other)

def __call__(self, *args, **kwargs):
return self.__func(*args, **kwargs)

def pipefy(func):
return F(func)

```
zyb201314
2021-01-11 21:07:19 +08:00
这就是牛人技术探讨吗?作为小白的我完全参入不进讨论, 只能给你们鼓掌.
crclz
2021-01-11 21:09:30 +08:00
可以可以,脚本语言就应该有脚本的样子
24bit
2021-01-11 23:35:01 +08:00
赞一个
lithbitren
2021-01-12 01:29:41 +08:00
非常有意思
AlexChing
2021-01-12 09:59:30 +08:00
这个在 python 的数据预处理阶段确实是很实用的。数据预处理需要按照特定的流程来走,自己一个个的写流程确实很难受。
Reficul
2021-01-12 10:12:33 +08:00
差点真被唬住了, 原来是覆盖了位运算的或。。。

秒啊~

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/743574

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX