跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 六行代码实现 Python 管道
未分類
12 1 月 2021

六行代码实现 Python 管道

六行代码实现 Python 管道

資深大佬 : abersheeran 0

不多说,直接上代码。

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 调样式了。有兴趣的就看看,没兴趣的就复制代码去用就完事。

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

大佬有話說 (100)

  • 資深大佬 : msg7086

    (虽然我知道这很煞风景,但是我真的忍不住想要吐槽一句,你这不就是把 Python 写成了 Ruby 吗……)

  • 資深大佬 : msg7086

    顺带提一句,如果让我来写 Python 的话,我可能还是选择把代码写成 list comprehension (可能需要推倒两次)。
    不写 map reduce 是因为怕被 Python 用户吐槽不够 Pythonic 。

  • 主 資深大佬 : abersheeran

    @msg7086 四年 Python 老玩家了,谁敢说我不代码 Pythonic 我就怼死他,我写 C 都 Python 味儿了。

  • 資深大佬 : msg7086

    我之前写任务依赖管理的时候也是把 pipe 拿来用了。
    class Tasks < Array
    def |(op)
    op.call(sources: self)
    end
    end
    类似这么个结构,感觉和你的差不多。
    自己发明 DSL 确实很有成就感。

  • 資深大佬 : Leigg

    有半年没有写 py 了,现在都进化到这个程度了吗?

  • 資深大佬 : Leigg

    我擦,原来是 magic method 。还以为是 shell 语法呢。

  • 資深大佬 : stdout

    很花哨实用。

  • 資深大佬 : python4

    等 python4 发布的时候就作为 feature 吧。

  • 資深大佬 : SjwNo1

    是很优雅,但不够语义化 – . –

  • 資深大佬 : whileFalse

    不错 很优雅!

  • 主 資深大佬 : abersheeran

    @python4 Guido:I don’t like it.

  • 資深大佬 : ferock

    学习

  • 資深大佬 : Jirajine

    不错,可以发布到 pypi

  • 主 資深大佬 : abersheeran

    @Jirajine 请不要把 npm 行为带到 Python 社区。满打满算六行代码,这也需要 PyPi 走一下?复制粘贴它不香吗。我要是真发布了,setup.py 比源码还大。

  • 主 資深大佬 : abersheeran

    @SjwNo1 如果你认为运算符不够语义化,建议开新文,炮轰 Shell 的管道运算符不够语义化。会有人跟你对线。
    如果你认为 F 这个命名不够语义化可以用 Pipe,Python3 里你甚至可以给它一个中文名,管道。够语义化吗?

  • 資深大佬 : Jirajine

    @abersheeran 复制粘贴是可以,但每个模块都复制粘贴一遍也不合适,还是得自己建一个模块再倒入,这和直接从 pypi 导入也没多大区别吧?
    再者一般情况都从 pypi 找包,即使让人直接从 readme 里复制粘贴。不上的话不利于曝光需要的人也很难找到。

  • 資深大佬 : YUX

    `range(10) | F(filter, lambda x: x % 2) | F(sum)` 是奇数和叭

  • 資深大佬 : dorafmon

    @Jirajine 用这个呗, 不是很接近了? https://pypi.org/project/pipetools/

  • 主 資深大佬 : abersheeran

    @YUX 谢谢指正,写错了哈哈哈。好尴尬,待会就改。

  • 資深大佬 : aijam

    又可以安利下了: https://github.com/czheo/syntax_sugar_python

  • 主 資深大佬 : abersheeran

    @Jirajine 有道理。待会就发一个。

  • 主 資深大佬 : abersheeran

    @aijam 各有各的好。我这个一共只有六行代码,工作原理简单,对 Python 原本用法几乎无改变,可以和现在的代码混合使用,使用成本低、收益高。
    你给的那个库,需要从数据源头包一层,返回的也是个它的自定义对象,没办法做到随意的混合使用。当然,它那个只需要在源头和结尾申明一下,可以少输入几个字符,这方面还是有优势的。

  • 資深大佬 : fpure

    不错,很优雅的代码

  • 資深大佬 : alan0liang

    想到了 ECMAScript 的 proposal pipeline op: https://github.com/tc39/proposal-pipeline-operator
    大概长这样:
    64 |> Math.sqrt |> (x => x + 1) |> console.log
    或者:
    64 |> Math.sqrt |> # + 1 |> console.log

  • 資深大佬 : frostming

    我记得很久之前哪里看到过,类似小技巧一类的文章
    不过你这个可以柯里化一下更好:
    range(10) | F(filter)(lambda x: x % 2) | F(sum)

  • 資深大佬 : frostming

    这样我可以:
    Filter = F(filter)
    Sum = F(sum)

  • 主 資深大佬 : abersheeran

    @frostming 嗯? F 从 partial 继承而来,本就可以绑定参数的。

  • 資深大佬 : Ritter

    一直以为 partial 是个函数 原来是个类。。。

  • 資深大佬 : zone10

    我之前看过一个函数式的库, 也很有意思.我现在有个思路是用更高效的语言来实现这个,不知道能不能保证 Python 便利性的同时提高性能

  • 主 資深大佬 : abersheeran

    @zone10 partial 本身就是 C 写的,虽然在标准库里也有一个 C 模块不可用时的 Pure Python 降级代替,但编译正常的 CPython 都会用那个 C 版本。所以这里的性能损耗几乎不可能更低了。

  • 主 資深大佬 : abersheeran

    @Ritter 一般这种类似于一个函数功能的类,都会以小写命名,而不是死板的遵守 pep8 。

  • 資深大佬 : SjwNo1

    还有其他类似的实现吗,学习了

  • 資深大佬 : Wincer

    不错

  • 資深大佬 : dinjufen

    666,花哨的东西学不来

  • 資深大佬 : frostming

    @Wincer C 实现的类要加额外方法要动用 ctypes
    我很久前写了个黑魔法
    https://github.com/frostming/gorella

  • 資深大佬 : wellsc

    666 这个好神奇

  • 資深大佬 : NeezerGu

    @abersheeran 路过,昨天看这个学习了一阵,partial 是 python 代码实现的吧?

  • 主 資深大佬 : abersheeran

    @NeezerGu 自己看标准库源码吧。搜这一行 from _functools import partial

  • 資深大佬 : bruce00

    主博客好看~

  • 資深大佬 : Wincer

    @frostming 沿用了你的代码,成功给 built-in 的 object 加上了 __ror__ 方法,但是还是行不通:range(10) | filter(lambda x: x % 2) 这个表达式被解释器会默认先求两端的值,如果不先求值的话,需要用 partial 包起来,不过这样又和主的实现差不多了。

  • 主 資深大佬 : abersheeran

    @bruce00 谢谢~

  • 主 資深大佬 : abersheeran

    @Wincer 要实现你这个,首先要让这些 callable 支持自动柯里化……不是支持一个管道运算符就行了的

    自动柯里化的工作量就太大了,因为 Py 里存在 *args 和 **kwargs 。我是想不到有什么好办法能实现的。所以我选择让程序员自己柯里化一次(也就是使用 F 包裹一次)。

  • 資深大佬 : Wincer

    @abersheeran 嗯是的,这确实是目前比较合适的方法了

  • 資深大佬 : iqxd

    请问下主 if isinstance(other, tuple): return self(*other) 是为了处理哪种情况呢?

  • 主 資深大佬 : abersheeran

    @frostming
    @Wincer

    就比如说明希想要的那个,可以用
    def fp(func):
    def _(*args, **kwargs):
    return F(func, *args, **kwargs)
    return _

    折中实现一下,强制 double call 才能实际调用。

    而你要做的这个,既要支持 direct call 又要支持
    double call……那就得第一次调用之后延迟计算,判断如果是走管道,调第二次,判断直接赋值的话再去计算。CPython 应该做不到,可以考虑做个超集语言,在编译期做。

  • 資深大佬 : NeezerGu

    @abersheeran
    不是这个吗? https://github.com/python/cpython/blob/master/Lib/functools.py
    哦,这是说, 同时提供了 python 版本和 c 版本? 如果 c 版本的导入失败就用 python 的?

  • 資深大佬 : frostming

    @Wincer 如果只是 patch function 大可直接把 builtin 的 map, filter 换掉,我说的那种可以用来给 list 加 chaining call:

    [1, 2, 3].map(lambda x: x**2)

  • 資深大佬 : mckelvin

    `sum(i for i in range(10) if i % 2)` 比 `range(10) | F(filter, lambda x: x % 2) | F(sum)` 更难读吗?

  • 資深大佬 : yuruizhe

    @Leigg 同+1,我还纳闷 shell 咋能调用 python 函数呢…老实说,我还真没把 python shell 当成过主力 shell,都是 bash…

  • 資深大佬 : muzuiget

    这种用法容易走火入魔。

  • 資深大佬 : Merlini

    之前做文本处理的时候看到网上一个 pipeline 写法感觉也挺不错。
    “`python
    def pipeline(
    value: T,
    function_pipeline: Sequence[Callable[[T], T]],
    ) -> T:
    “””A generic Unix-like pipeline
    :param value: the value you want to pass through a pipeline
    :param function_pipeline: an ordered list of functions that
    comprise your pipeline
    “””
    return reduce(lambda v, f: f(v), function_pipeline, value)
    “`

  • 資深大佬 : zouzou0208

    好玩,原来是 index.py 的作者。好棒。

  • 資深大佬 : chaleaoch

    在这么玩, 会被玩坏的喂~~~~~

  • 資深大佬 : shyling

  • 資深大佬 : Wincer

    @frostming 是的,这样也可以,但是适用性会窄一些,需要预先针对某一种类型 patch

  • 資深大佬 : shyrock

    赞!学习了。

  • 資深大佬 : aldslvda

    学习了

  • 資深大佬 : iintothewind

    你这个管道实现是不是可以再优化一下,
    毕竟每次都得带 F() 包住后面的操作符, 这个看起来有点多余。

  • 資深大佬 : admirez

    pandas 也可以这样搞么?

  • 資深大佬 : sapocaly

    个人觉得 chain funciton call 可能更符合 python 一些至少更接近 django 一些。比如 range(10).filter(lambda x: x % 2).sum()。毕竟每次加个 F 还是有点难受。当然,去掉 F 容易,我是没想到怎么实现 range(10) | filter, lambada x:x |sum 这样的 syntax

  • 資深大佬 : frostming

    @sapocaly 只能魔改 builtins 了,参考我上面的回答

  • 資深大佬 : sapocaly

    @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

    补充一下,比较容易实现的 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

    想了想似乎 metaclass 都不需要

  • 主 資深大佬 : abersheeran

    @NeezerGu 对的。

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

    @sapocaly 你说的这个不就是上推荐的那个库吗?你喜欢的话可以去试试,上面有链接。我个人喜欢 | F(…) 这么用。没有黑魔法,工作原理简单,对 debug 友好,最重要的优点是每一次管道运算都是真实的运算出了结果,我可以在任何一次运算之后拿去用,而不需要特意标识一个 END 或者其他什么的。

  • 資深大佬 : xuboying

    主不去写 perl6 可惜了,python 的名人名言是只能有一种写法,不遵守人的可以去开发其他语言(逃)

  • 資深大佬 : Tumblr

    啊!这很 PowerShell !很 pwsh !

  • 主 資深大佬 : abersheeran

    @mckelvin 我文章里解释过了。管道的数据处理顺序跟阅读顺序是一样的。利于阅读代码。
    你说的这个嵌套用法当然可以,可是阅读顺序和实际的执行顺序是相反的,它先执行的内部函数再执行的外部函数。

  • 資深大佬 : NeezerGu

    @abersheeran 原来如此,感谢哈。

  • 資深大佬 : sapocaly

    @abersheeran 额其实我很久没写 python 了,倒不是真要用到,不过觉得你提出的这个问题挺有意思所以想想有啥别的实现。我看了那个库介绍,和我理想中的比较接近,我觉得 END 是可以去掉的,但当然这又要加很多 hack 。 至于哪种 syntax 我比较喜欢,我觉得都还行,不过我觉得我现实中不会去用。简单的逻辑没必要,复杂的逻辑 chain 或者 pipe 的可读性并不会增加,也大概率效率不是最好的。当然,我也很久没用 python 了,效率这个还得具体问题具体分析。

  • 資深大佬 : xuboying

    为何要特殊处理 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

    @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

    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

    个人觉得可以写成装饰器

    “`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

    这就是牛人技术探讨吗?作为小白的我完全参入不进讨论, 只能给你们鼓掌.

  • 資深大佬 : crclz

    可以可以,脚本语言就应该有脚本的样子

  • 資深大佬 : 24bit

    赞一个

  • 資深大佬 : lithbitren

    非常有意思

  • 資深大佬 : AlexChing

    这个在 python 的数据预处理阶段确实是很实用的。数据预处理需要按照特定的流程来走,自己一个个的写流程确实很难受。

  • 資深大佬 : Reficul

    差点真被唬住了, 原来是覆盖了位运算的或。。。

    秒啊~

  • 資深大佬 : xuboying

    @abersheeran #72 感谢 OP 提供了一个非常好的思路,用在 notebook 里做数据分析非常实用,写项目估计会被大多数 team 打死。。。
    ror 差点被骗了,以为是循环,看了一下文档以后我想再重载一个运算符,这样就不用粗暴的使用 tuple 来判断处理逻辑了,代码如下:

    $ cat foo.py
    from functools import partial

    class F(partial):
    def __ror__(self, rhs):
    return self(rhs)

    def __rrshift__(self, rhs):
    return self(*rhs)

    $ python -i foo.py
    >>> (1, 2 , 3) | F(filter, lambda x: x % 2) | F(list)
    [1, 3]
    >>> (14, ‘#b’) >> F(format)
    ‘0b1110’
    >>>

  • 資深大佬 : xuboying

    @xuboying #81 草率了。。ror 和 rrshift 有优先级关系,一起用不方便。。。

  • 主 資深大佬 : abersheeran

    @xuboying 哈哈,如果你想要拆分成两个字符,不妨考虑 `|` 和 `^` 这两没有优先级问题。

  • 資深大佬 : Leviathann

    @alan0liang 这个 sharp 让我想到了 Mathematica

  • 資深大佬 : no1xsyzy

    @abersheeran | ^ 这两个也有优先级问题, ^ 优先级高
    似乎 | 没有同优先级的……

    @geebos 你的 __init__ 和 __call__ 的功能已经被 partial 实现了……

  • 主 資深大佬 : abersheeran

    @no1xsyzy
    @xuboying

    https://docs.python.org/zh-cn/3.7/reference/expressions.html#operator-precedence

    还真是,没有符号和 | 优先级相同。

  • 資深大佬 : no1xsyzy

    最长的管道实现:发 PEP 要求增加 |> 和 ||> 操作符

  • 資深大佬 : no1xsyzy

    @abersheeran 再想了下,可能比较歪
    a / F(b) :: b(a)
    a // F(b) :: b(*a)
    a @ F(b) :: map(b, a)
    a % F(b) :: filter(b, a)
    a * F(b) :: reduce(b, a)

  • 主 資深大佬 : abersheeran

    @no1xsyzy 提这个 PEP,Guido 会回复:I don’t like it. Closed. 哈哈哈

  • 資深大佬 : woostundy

    或运算有时候还挺常用的。
    要是能自己增加运算符以及定义就好了。

  • 資深大佬 : no1xsyzy

    @abersheeran 大人,时代变了
    Guido 不是也不喜欢 := 么……

  • 主 資深大佬 : abersheeran

    @no1xsyzy 但是能和他对着干的,也就那么几个大佬吧。红姐提交模式匹配的 PR 不还是 Guido 直接 Closed 嘛。

  • 資深大佬 : xuboying

    @no1xsyzy #85 @abersheeran
    花了点摸鱼的时间略微改造了一个符合我个人喜好的版本,以后做数据分析手指就轻松了。

    我发现 partial 的类很特殊,不能简单继承,或者前面有人提到了 Filter=F(filter)似乎也是不行的。

    希望有高人能帮忙简化一下我的写法。

    ···
    $ cat foo.py
    from functools import partial

    class P(partial):
    “””Pipe.”””

    def __ror__(self, rhs):
    return self(rhs)

    class X(partial):
    “””Xargs.”””

    def __ror__(self, rhs):
    return self(*rhs)

    class Filter(P):

    def __new__(cls, *args, **kwargs):
    return super(__class__, cls).__new__(cls, filter, *args, **kwargs)

    class Map(P):

    def __new__(cls, *args, **kwargs):
    return super(__class__, cls).__new__(cls, map, *args, **kwargs)

    $ python -i foo.py
    >>> (1, 2 , 3) | Map(lambda x : x+2 ) | Filter(lambda x: x % 2) | X(divmod) | P (set)
    {0, 3}
    ···

  • 主 資深大佬 : abersheeran

    @xuboying https://github.com/abersheeran/only-pipe/discussions 方便的话可以发到这,留作记录。

  • 資深大佬 : Macv1994

    想问一下题主,怎么样才能像你一样写出这样优雅的代码,可以说一下学习路线吗?

  • 主 資深大佬 : abersheeran

    @Macv1994 学习路线?额……你可以看看我博客,按时间顺序倒着看。那个差不多就能展示我的学习路线了。

  • 資深大佬 : yueyoum

    我为了 回复此贴, 特意登陆了一下

    总结: 哗众取宠,毫无意义

    我已经 抛弃 python 了

    工业应用,各种炫技代码,不是 平铺直述 的代码, 我全部都是 review 不通过的

    如果一个 语言 写了 4 年 还能从 语法上玩出花样, 或者 一个语言 写了 4 年 还没掌握全部的语言特性
    ( c++除外, 特定领域 和 高性能 是别人的卖点)

    那么 在这种语言上 浪费时间是 毫无意义的

    什么是好语言:

    1. 语法 几天 学完
    2. 语言特性 几个月 完全掌握
    3. 有强力的 IDE 支持
    4. 强类型,编译检查错误
    5. 如果运行效率高最好
    6. 如果容易的支持并行最好

    python 一样都没占上

    连最基本的 语言特性, 我敢说 这个帖子里 没有一个人 完全掌握了 python 的特性

    所以, 自己玩玩可以

  • 主 資深大佬 : abersheeran

    @yueyoum 喔。你是 Golang 的粉丝吧。

  • 資深大佬 : lizytalk

    @aijam #20 我觉得这里面的 pipe 的设计比主的好

  • 資深大佬 : xuboying

    @yueyoum 从语法的角度 op 的代码并不符合社区规范,但可能很多人(包括我)是把 op 的设计当成一个最终产品来用的,比如数据分析。op 更像是一个优秀的产品经理而不是单纯的 coder 。

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具