Ответ 1
Трудно реализовать это с помощью побитового оператора or
, потому что 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. Многие питоники считают перегрузку оператора разной целью как греховное богохульство.
[обновление]
Пользователь 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 dataframe (аргументы ключевого слова в порядке). Давайте добавим метод __new__
к нашему классу PipeInto
внутри docorator, который проверяет, является ли первый аргумент фреймворком данных, и если это так, мы просто вызываем исходную функцию с аргументами:
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