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)
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到位.
Syl*_*oux 18
python语言是否支持类似的东西?
"更多功能管道语法"这真的是一个更"功能"的语法吗?我会说它为R添加了一个"中缀"语法.
话虽这么说,Python的语法并没有直接支持标准运算符之外的中缀表示法.
如果你真的需要这样的东西,你应该从Tomer Filiba那里获取代码作为实现你自己的中缀表示法的起点:
代码示例和评论由Tomer Filiba(http://tomerfiliba.com/blog/Infix-Operators/):
Run Code Online (Sandbox Code Playgroud)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
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)
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)
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)
我错过了|>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
您可以使用sspipe库。它暴露了两个对象p和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)
Run Code Online (Sandbox Code Playgroud)
不需要第 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)
建筑pipe用Infix
正如Sylvain Leroux所暗示,我们可以使用Infix运算符来构造中缀pipe.让我们看看这是如何实现的.
首先,这是Tomer Filiba的代码
代码示例和评论由Tomer Filiba(http://tomerfiliba.com/blog/Infix-Operators/):
Run Code Online (Sandbox Code Playgroud)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
管道运算符将前一个对象作为参数传递给管道后面的对象,因此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)
添加我的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对象可以根据需要多次使用.
这里有一个非常好的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)