python中的功能管道,如R's dplyr中的%>%

can*_*his 63 python functional-programming pipeline

在R(感谢magritrr)中,您现在可以通过更多功能管道语法执行操作%>%.这意味着不是编码:

> as.Date("2014-01-01")
> as.character((sqrt(12)^2)
Run Code Online (Sandbox Code Playgroud)

你也可以这样做:

> "2014-01-01" %>% as.Date 
> 12 %>% sqrt %>% .^2 %>% as.character
Run Code Online (Sandbox Code Playgroud)

对我来说,这更具可读性,这扩展到数据框之外的用例.python语言是否支持类似的东西?

Dun*_*nes 33

一种可能的方法是使用一个名为的模块macropy.Macropy允许您将转换应用于您编写的代码.因此a | b可以转化为b(a).这具有许多优点和缺点.

与Sylvain Leroux提到的解决方案相比,主要优点是您不需要为您感兴趣的函数创建中缀对象 - 只需标记您打算使用转换的代码区域.其次,由于转换是在编译时而不是运行时应用的,因此转换后的代码在运行时不会产生任何开销 - 所有工作都是在首次从源代码生成字节代码时完成的.

主要的缺点是,macropy需要激活某种方式才能使其工作(稍后提到).与更快的运行时相比,源代码的解析在计算上更复杂,因此程序将花费更长的时间来启动.最后,它添加了一种语法风格,这意味着不熟悉macropy的程序员可能会发现您的代码难以理解.

示例代码:

run.py

import macropy.activate 
# Activates macropy, modules using macropy cannot be imported before this statement
# in the program.
import target
# import the module using macropy
Run Code Online (Sandbox Code Playgroud)

target.py

from fpipe import macros, fpipe
from macropy.quick_lambda import macros, f
# The `from module import macros, ...` must be used for macropy to know which 
# macros it should apply to your code.
# Here two macros have been imported `fpipe`, which does what you want
# and `f` which provides a quicker way to write lambdas.

from math import sqrt

# Using the fpipe macro in a single expression.
# The code between the square braces is interpreted as - str(sqrt(12))
print fpipe[12 | sqrt | str] # prints 3.46410161514

# using a decorator
# All code within the function is examined for `x | y` constructs.
x = 1 # global variable
@fpipe
def sum_range_then_square():
    "expected value (1 + 2 + 3)**2 -> 36"
    y = 4 # local variable
    return range(x, y) | sum | f[_**2]
    # `f[_**2]` is macropy syntax for -- `lambda x: x**2`, which would also work here

print sum_range_then_square() # prints 36

# using a with block.
# same as a decorator, but for limited blocks.
with fpipe:
    print range(4) | sum # prints 6
    print 'a b c' | f[_.split()] # prints ['a', 'b', 'c']
Run Code Online (Sandbox Code Playgroud)

最后是努力工作的模块.我将其称为fpipe for function pipe作为其模拟shell语法,用于将输出从一个进程传递到另一个进程.

fpipe.py

from macropy.core.macros import *
from macropy.core.quotes import macros, q, ast

macros = Macros()

@macros.decorator
@macros.block
@macros.expr
def fpipe(tree, **kw):

    @Walker
    def pipe_search(tree, stop, **kw):
        """Search code for bitwise or operators and transform `a | b` to `b(a)`."""
        if isinstance(tree, BinOp) and isinstance(tree.op, BitOr):
            operand = tree.left
            function = tree.right
            newtree = q[ast[function](ast[operand])]
            return newtree

    return pipe_search.recurse(tree)
Run Code Online (Sandbox Code Playgroud)

  • 听起来不错,但我认为它只适用于Python 2.7(而不是Python 3.4). (2认同)
  • 我创建了一个没有依赖项的较小的库,该库的作用与@fpipe装饰器相同,但重新定义了右移(>>)而不是or(|):https://pypi.org/project/pipeop/ (2认同)

sha*_*ker 27

管道是Pandas 0.16.2中的新功能.

例:

import pandas as pd
from sklearn.datasets import load_iris

x = load_iris()
x = pd.DataFrame(x.data, columns=x.feature_names)

def remove_units(df):
    df.columns = pd.Index(map(lambda x: x.replace(" (cm)", ""), df.columns))
    return df

def length_times_width(df):
    df['sepal length*width'] = df['sepal length'] * df['sepal width']
    df['petal length*width'] = df['petal length'] * df['petal width']

x.pipe(remove_units).pipe(length_times_width)
x
Run Code Online (Sandbox Code Playgroud)

注意:Pandas版本保留了Python的引用语义.这就是为什么length_times_width不需要返回值; 它修改x到位.

  • 遗憾的是,这仅适用于数据帧,因此我无法将其指定为正确的答案.但很高兴在这里提到我想到的主要用例是将其应用于数据帧. (4认同)

Syl*_*oux 18

python语言是否支持类似的东西?

"更多功能管道语法"这真的是一个更"功能"的语法吗?我会说它为R添加了一个"中缀"语法.

话虽这么说,Python的语法并没有直接支持标准运算符之外的中缀表示法.


如果你真的需要这样的东西,你应该从Tomer Filiba那里获取代码作为实现你自己的中缀表示法的起点:

代码示例和评论由Tomer Filiba(http://tomerfiliba.com/blog/Infix-Operators/):

from functools import partial

class Infix(object):
    def __init__(self, func):
        self.func = func
    def __or__(self, other):
        return self.func(other)
    def __ror__(self, other):
        return Infix(partial(self.func, other))
    def __call__(self, v1, v2):
        return self.func(v1, v2)
Run Code Online (Sandbox Code Playgroud)

使用这个特殊类的实例,我们现在可以使用一个新的"语法"来调用函数作为中缀运算符:

>>> @Infix
... def add(x, y):
...     return x + y
...
>>> 5 |add| 6
Run Code Online (Sandbox Code Playgroud)


smc*_*mci 17

PyToolz [doc]允许任意组合管道,只是它们没有用管道操作符语法定义.

请按照上面的链接进行快速入门.这是一个视频教程:http: //pyvideo.org/video/2858/functional-programming-in-python-with-pytoolz

In [1]: from toolz import pipe

In [2]: from math import sqrt

In [3]: pipe(12, sqrt, str)
Out[3]: '3.4641016151377544'
Run Code Online (Sandbox Code Playgroud)

  • 他的基本 URL 似乎是: http://http://matthewrocklin.com/blog/ 和 PyToolz http://toolz.readthedocs.io/en/latest/ 。啊,互联网的昙花一现…… (2认同)
  • @Frank:嘿伙计,这个开源软件的作者不会得到你或我的报酬,所以不要说“包 X 很糟糕”,而只是说“包 X 仅限于用例 Y”,和/或建议一个更好的替代包,或者将该功能贡献给包 X,或者自己编写。 (2认同)

sha*_*ker 16

如果您只想将其用于个人脚本,您可能需要考虑使用Coconut而不是Python.

椰子是Python的超集.因此,您可以使用Coconut的管道操作员|>,同时完全忽略椰子语言的其余部分.

例如:

def addone(x):
    x + 1

3 |> addone
Run Code Online (Sandbox Code Playgroud)

编译成

# lots of auto-generated header junk

# Compiled Coconut: -----------------------------------------------------------

def addone(x):
    return x + 1

(addone)(3)
Run Code Online (Sandbox Code Playgroud)

  • @jimbo1qaz 如果您仍然遇到此问题,请尝试 `print(1 |> isinstance$(int))`,或者最好是 `1 |> isinstance$(int) |> print`。 (2认同)

Big*_*ist 13

dfply模块。您可以在以下位置找到更多信息

https://github.com/kieferk/dfply

一些例子是:

from dfply import *
diamonds >> group_by('cut') >> row_slice(5)
diamonds >> distinct(X.color)
diamonds >> filter_by(X.cut == 'Ideal', X.color == 'E', X.table < 55, X.price < 500)
diamonds >> mutate(x_plus_y=X.x + X.y, y_div_z=(X.y / X.z)) >> select(columns_from('x')) >> head(3)
Run Code Online (Sandbox Code Playgroud)


Rob*_*ard 8

我错过了|>Elixir 的管道操作符,所以我创建了一个简单的函数装饰器(大约50行代码),>>在编译时使用ast库和compile/exec 将Python右移操作符重新解释为类似Elixir的管道:

from pipeop import pipes

def add3(a, b, c):
    return a + b + c

def times(a, b):
    return a * b

@pipes
def calc()
    print 1 >> add3(2, 3) >> times(4)  # prints 24
Run Code Online (Sandbox Code Playgroud)

它所做的只是重写a >> b(...)b(a, ...).

https://pypi.org/project/pipeop/

https://github.com/robinhilliard/pipes


mhs*_*vat 8

您可以使用sspipe库。它暴露了两个对象ppx。类似于x %>% f(y,z),您可以写作,x | p(f, y, z)并且类似于x %>% .^2您可以写作x | px**2

from sspipe import p, px
from math import sqrt

12 | p(sqrt) | px ** 2 | p(str)
Run Code Online (Sandbox Code Playgroud)


jra*_*amm 7

不需要第 3 方库或混淆的操作符技巧来实现管道功能 - 您可以很容易地自己掌握基础知识。

让我们首先定义管道函数实际上是什么。从本质上讲,它只是一种以逻辑顺序表达一系列函数调用的方式,而不是标准的“由内而外”的顺序。

例如,让我们看看这些函数:

def one(value):
  return value

def two(value):
  return 2*value

def three(value):
  return 3*value
Run Code Online (Sandbox Code Playgroud)

不是很有趣,但假设有趣的事情正在发生value。我们想按顺序调用它们,将每个的输出传递给下一个。在香草 python 中,这将是:

result = three(two(one(1)))
Run Code Online (Sandbox Code Playgroud)

它不是令人难以置信的可读性,对于更复杂的管道,它会变得更糟。所以,这是一个简单的管道函数,它接受一个初始参数,以及一系列应用它的函数:

def pipe(first, *args):
  for fn in args:
    first = fn(first)
  return first
Run Code Online (Sandbox Code Playgroud)

让我们称之为:

result = pipe(1, one, two, three)
Run Code Online (Sandbox Code Playgroud)

对我来说,这看起来像是非常易读的“管道”语法:)。我看不出它比重载运算符或类似的东西可读性差。事实上,我认为它是更具可读性的python代码

这是解决 OP 示例的简陋管道:

from math import sqrt
from datetime import datetime

def as_date(s):
  return datetime.strptime(s, '%Y-%m-%d')

def as_character(value):
  # Do whatever as.character does
  return value

pipe("2014-01-01", as_date)
pipe(12, sqrt, lambda x: x**2, as_character)
Run Code Online (Sandbox Code Playgroud)


yar*_*le8 6

建筑pipeInfix

正如Sylvain Leroux所暗示,我们可以使用Infix运算符来构造中缀pipe.让我们看看这是如何实现的.

首先,这是Tomer Filiba的代码

代码示例和评论由Tomer Filiba(http://tomerfiliba.com/blog/Infix-Operators/):

from functools import partial

class Infix(object):
    def __init__(self, func):
        self.func = func
    def __or__(self, other):
        return self.func(other)
    def __ror__(self, other):
        return Infix(partial(self.func, other))
    def __call__(self, v1, v2):
        return self.func(v1, v2)
Run Code Online (Sandbox Code Playgroud)

使用这个特殊类的实例,我们现在可以使用一个新的"语法"来调用函数作为中缀运算符:

>>> @Infix
... def add(x, y):
...     return x + y
...
>>> 5 |add| 6
Run Code Online (Sandbox Code Playgroud)

管道运算符将前一个对象作为参数传递给管道后面的对象,因此x %>% f可以转换为f(x).因此,pipe可以使用Infix如下定义运算符:

In [1]: @Infix
   ...: def pipe(x, f):
   ...:     return f(x)
   ...:
   ...:

In [2]: from math import sqrt

In [3]: 12 |pipe| sqrt |pipe| str
Out[3]: '3.4641016151377544'
Run Code Online (Sandbox Code Playgroud)

关于部分申请的说明

%>%从运营商dpylr通过在函数的第一个参数推参数,所以

df %>% 
filter(x >= 2) %>%
mutate(y = 2*x)
Run Code Online (Sandbox Code Playgroud)

对应于

df1 <- filter(df, x >= 2)
df2 <- mutate(df1, y = 2*x)
Run Code Online (Sandbox Code Playgroud)

在Python中实现类似功能的最简单方法是使用currying.该toolz库提供了一个curry装饰器功能,可以轻松构建curry函数.

In [2]: from toolz import curry

In [3]: from datetime import datetime

In [4]: @curry
    def asDate(format, date_string):
        return datetime.strptime(date_string, format)
    ...:
    ...:

In [5]: "2014-01-01" |pipe| asDate("%Y-%m-%d")
Out[5]: datetime.datetime(2014, 1, 1, 0, 0)
Run Code Online (Sandbox Code Playgroud)

请注意,|pipe|将参数推送到最后一个参数位置,即

x |pipe| f(2)
Run Code Online (Sandbox Code Playgroud)

对应于

f(2, x)
Run Code Online (Sandbox Code Playgroud)

在设计curried函数时,应该在参数列表的前面放置静态参数(即可能用于许多示例的参数).

请注意,toolz其中包括许多预先计算的功能,包括operator模块中的各种功能.

In [11]: from toolz.curried import map

In [12]: from toolz.curried.operator import add

In [13]: range(5) |pipe| map(add(2)) |pipe| list
Out[13]: [2, 3, 4, 5, 6]
Run Code Online (Sandbox Code Playgroud)

其大致对应于R中的以下内容

> library(dplyr)
> add2 <- function(x) {x + 2}
> 0:4 %>% sapply(add2)
[1] 2 3 4 5 6
Run Code Online (Sandbox Code Playgroud)

使用其他中缀分隔符

您可以通过覆盖其他Python运算符方法来更改围绕Infix调用的符号.例如,交换__or____ror____mod____rmod__将改变|操作者的mod操作者.

In [5]: 12 %pipe% sqrt %pipe% str
Out[5]: '3.4641016151377544'
Run Code Online (Sandbox Code Playgroud)


Eli*_*igo 5

添加我的2c.我个人使用package fn进行功能样式编程.你的例子转化为

from fn import F, _
from math import sqrt

(F(sqrt) >> _**2 >> str)(12)
Run Code Online (Sandbox Code Playgroud)

F是一个包装类,具有功能风格的语法糖,用于部分应用和组合._是一个用于匿名函数的Scala样式构造函数(类似于Python lambda); 它表示一个变量,因此您可以_在一个表达式中组合多个对象以获得具有更多参数的函数(例如_ + _,相当于lambda a, b: a + b).F(sqrt) >> _**2 >> str导致一个Callable对象可以根据需要多次使用.


Dim*_*min 5

这里有一个非常好的pipe模块https://pypi.org/project/pipe/ 它重载 | 运算符并提供许多管道函数add, first, where, tail等。

>>> [1, 2, 3, 4] | where(lambda x: x % 2 == 0) | add
6

>>> sum([1, [2, 3], 4] | traverse)
10
Run Code Online (Sandbox Code Playgroud)

另外,编写自己的管道函数非常容易

@Pipe
def p_sqrt(x):
    return sqrt(x)

@Pipe
def p_pr(x):
    print(x)

9 | p_sqrt | p_pr
Run Code Online (Sandbox Code Playgroud)