由于 pandas.DataFrame
已经实现了按位或运算,因此使用按位或运算符来实现这一点很困难。如果您不介意用 >>
替换 |
,可以尝试以下方法:
import pandas as pd
def select(df, *args):
cols = [x for x in args]
return df[cols]
def rename(df, **kwargs):
for name, value in kwargs.items():
df = df.rename(columns={'%s' % name: '%s' % value})
return df
class SinkInto(object):
def __init__(self, function, *args, **kwargs):
self.args = args
self.kwargs = kwargs
self.function = function
def __rrshift__(self, other):
return self.function(other, *self.args, **self.kwargs)
def __repr__(self):
return "<SinkInto {} args={} kwargs={}>".format(
self.function,
self.args,
self.kwargs
)
df = pd.DataFrame({'one' : [1., 2., 3., 4., 4.],
'two' : [4., 3., 2., 1., 3.]})
然后你可以执行:
>>> df
one two
0 1 4
1 2 3
2 3 2
3 4 1
4 4 3
>>> df = df >> SinkInto(select, 'one') \
>> SinkInto(rename, one='new_one')
>>> df
new_one
0 1
1 2
2 3
3 4
4 4
在Python 3中,您可以滥用Unicode:
>>> print('\u01c1')
ǁ
>>> ǁ = SinkInto
>>> df >> ǁ(select, 'one') >> ǁ(rename, one='new_one')
new_one
0 1
1 2
2 3
3 4
4 4
[更新]
感谢您的回复。是否可以为每个函数创建一个单独的类(例如 SinkInto),以避免将函数作为参数传递?
那装饰器怎么样?
def pipe(original):
class PipeInto(object):
data = {'function': original}
def __init__(self, *args, **kwargs):
self.data['args'] = args
self.data['kwargs'] = kwargs
def __rrshift__(self, other):
return self.data['function'](
other,
*self.data['args'],
**self.data['kwargs']
)
return PipeInto
@pipe
def select(df, *args):
cols = [x for x in args]
return df[cols]
@pipe
def rename(df, **kwargs):
for name, value in kwargs.items():
df = df.rename(columns={'%s' % name: '%s' % value})
return df
现在您可以装饰任何以 DataFrame
作为第一个参数的函数:
>>> df >> select('one') >> rename(one='first')
first
0 1
1 2
2 3
3 4
4 4
Python 太棒了!
我知道像 Ruby 这样的语言是“如此表达式”,它鼓励人们将每个程序都编写成新的 DSL,但在 Python 中,这种做法有点不受欢迎。许多 Python 爱好者认为,将运算符重载用于不同目的是一种罪孽深重的亵渎。
[更新]
用户 OHLÁLÁ 并不满意:
使用这种解决方案的问题在于当你试图调用函数而不是管道时。-OHLÁLÁ
您可以实现 dunder-call 方法:
def __call__(self, df):
return df >> self
然后:
>>> select('one')(df)
one
0 1.0
1 2.0
2 3.0
3 4.0
4 4.0
看起来满足OHLÁLÁ不容易:
在这种情况下,您需要显式调用对象:
select('one')(df)
有什么方法可以避免这种情况吗?- OHLÁLÁ
好的,我能想到一个解决办法,但是有一个注意事项:您的原始函数不能有第二个位置参数,它必须是一个pandas数据帧(关键字参数是可以的)。让我们在装饰器中为我们的PipeInto
类添加一个__new__
方法,以检测第一个参数是否为数据帧,如果是,则只需使用参数调用原始函数:
def __new__(cls, *args, **kwargs):
if args and isinstance(args[0], pd.DataFrame):
return cls.data['function'](*args, **kwargs)
return super().__new__(cls)
看起来它能够工作,但可能有一些我没有注意到的缺陷。
>>> select(df, 'one')
one
0 1.0
1 2.0
2 3.0
3 4.0
4 4.0
>>> df >> select('one')
one
0 1.0
1 2.0
2 3.0
3 4.0
4 4.0
__call__
方法应该很容易修复。 - Paulo Scardine