在R语言中(感谢magrittr),您现在可以通过%>%
使用更加函数式的管道语法执行操作。这意味着,您不需要编写以下代码:
> as.Date("2014-01-01")
> as.character((sqrt(12)^2)
你也可以这样做:
> "2014-01-01" %>% as.Date
> 12 %>% sqrt %>% .^2 %>% as.character
对我来说,这种方式更易读,并且适用于数据框之外的用例。 Python语言是否支持类似的功能呢?
在R语言中(感谢magrittr),您现在可以通过%>%
使用更加函数式的管道语法执行操作。这意味着,您不需要编写以下代码:
> as.Date("2014-01-01")
> as.character((sqrt(12)^2)
你也可以这样做:
> "2014-01-01" %>% as.Date
> 12 %>% sqrt %>% .^2 %>% as.character
对我来说,这种方式更易读,并且适用于数据框之外的用例。 Python语言是否支持类似的功能呢?
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
length_times_width
不需要返回值;它直接修改了x
。使用一个名为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
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']
最后是执行重要工作的模块。我将其称为fpipe,代表函数管道,因为它模拟了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)
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'
如果您只是想用于个人脚本编写,您可能需要考虑使用Coconut而不是Python。
Coconut是Python的超集。因此,您可以使用Coconut的管道运算符|>
,同时完全忽略Coconut语言的其他部分。
例如:
def addone(x):
x + 1
3 |> addone
编译成
# lots of auto-generated header junk
# Compiled Coconut: -----------------------------------------------------------
def addone(x):
return x + 1
(addone)(3)
print(1 |> isinstance$(int))
,或者更好的方法是 1 |> isinstance$(int) |> print
。 - Solomon Ucko1 |> print$(2)
调用的是 print(2, 1)
,因为 $ 映射到 Python 的 partials。但我想要 print(1, 2)
,这与 UFCS 和 magrittr 相匹配。动机:1 |> add(2) |> divide(6)
应该是 0.5,而且我不应该需要括号。 - nyanpasu64|>
和 (+)$(?, 2)
都有多么丑陋,我得出结论:编程语言和数学界不希望我使用这种类型的语法,甚至比使用一组括号还要难看。如果它有更好的语法(例如 Dlang 有 UFCS,但我不知道算术函数如何,或者 Python 是否有 ..
管道运算符),我会使用它的。 - nyanpasu64Python语言是否支持类似的功能?
"更具函数式特色的管道语法",这真的是更具"函数式"语法吗?我会说这只是为R添加了一个"infix"语法。
话虽如此,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)
>>> @Infix ... def add(x, y): ... return x + y ... >>> 5 |add| 6
有一个叫做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)
dfply
、dplython
和 plydata
包是 dplyr
包的 Python 移植版,因此它们在语法上非常相似。 - BigDataScientistdfply
是最近唯一被稍微更新的一个:即使在2021年3月,它也没有关闭的问题或提交记录。我联系了该项目,想知道他们是否有任何计划“苏醒”。 - WestCoastProjects3 >> np.sqrt
会出错,但在R中是3 %>% sqrt
。 - Frank%>%
,因此这可能是最接近的 Python 等效语句。 - WestCoastProjectsp
和px
。类似于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)
3 | p( f( x, . ) )
吗?在 R 中,这将是:3 %>% f(x, .)
。 - Frankpx
实现,它类似于 R 中的 .
。但是,你应该注意将 px
传递给 p()
而不是 f()
。例如:3 | p(f, x, px)
。 - mhsekhavat我很想在Python中使用Elixir的管道运算符|>
,所以我创建了一个简单的函数修饰器(约50行代码),它使用ast库和编译/执行将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
它所做的只是将a >> b(...)
重写为b(a, ...)
。
实现管道功能无需第三方库或混淆的操作符技巧-您可以很容易地自己实现基础功能。
首先,让我们定义一下管道函数到底是什么。其核心只是一种以逻辑顺序表达连续函数调用的方式,而不是标准的“从里到外”的顺序。
例如,让我们看看这些函数:
def one(value):
return value
def two(value):
return 2*value
def three(value):
return 3*value
虽然不是很有趣,但假设有一些有趣的事情正在发生在value
上。我们希望依次调用它们,并将每个输出传递给下一个。在普通的Python中,代码如下:
result = three(two(one(1)))
这段代码不太易读,而且在处理更复杂的管道时会变得更加糟糕。因此,这里有一个简单的 pipe 函数,它接受一个初始参数和一系列要应用于该参数的函数:
def pipe(first, *args):
for fn in args:
first = fn(first)
return first
我们称其为:
result = pipe(1, one, two, three)
对我来说,那看起来像是非常易读的“管道”语法 :). 我不认为它比重载运算符或类似的语法不易读。实际上,我认为它比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)
list(list(list(...)))
。这几乎是不可能阅读的。反向阅读也是如此。尝试使用fluentpy
或infixpy
。 - WestCoastProjectslist
?这里的解决方案根本不返回列表。
同样,pipe
函数不会向后读取 - 它是从左到右的。如果你想要从右到左,你需要一个compose
函数。同样容易使用相同的原则实现。 - jrammlist
来实现集合的结果。多个map
、filter
等需要每个都有一个list
,因此会污染代码。pipe
是第三方库,因此您的评论不适用于它。 - WestCoastProjects使用 Infix
构建 pipe
正如Sylvain Leroux所示,我们可以使用Infix
运算符构建中缀pipe
。让我们看看如何实现这一点。
首先,这是来自Tomer Filiba的代码:
管道运算符将前一个对象作为参数传递给后面的对象,因此Code sample and comments by 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)
Using instances of this peculiar class, we can now use a new "syntax" for calling functions as infix operators:
>>> @Infix ... def add(x, y): ... return x + y ... >>> 5 |add| 6
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'
关于部分应用的说明
dplyr
中的 %>%
运算符可以将参数传递到函数的第一个参数中,因此
df %>%
filter(x >= 2) %>%
mutate(y = 2*x)
对应于
df1 <- filter(df, x >= 2)
df2 <- mutate(df1, y = 2*x)
在Python中实现类似的功能最简单的方法是使用柯里化。 toolz
库提供了一个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)
请注意,|pipe|
将参数推入最后一个参数位置,即
x |pipe| f(2)
对应于
f(2, x)
在设计柯里化函数时,静态参数(即可能用于多个示例的参数)应该放在参数列表的前面。
请注意,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]
在R中大体对应如下:
> library(dplyr)
> add2 <- function(x) {x + 2}
> 0:4 %>% sapply(add2)
[1] 2 3 4 5 6
使用其他中缀分隔符
您可以通过覆盖其他Python操作符方法来更改包围中缀调用的符号。例如,将 __or__
和 __ror__
切换为 __mod__
和 __rmod__
将把 |
运算符更改为 mod
运算符。
In [5]: 12 %pipe% sqrt %pipe% str
Out[5]: '3.4641016151377544'
crime_by_state %>% filter(State=="New York", Year==2005) ...
。 - Piotr Migdal