В R (благодаря magrittr ) теперь вы можете выполнять операции с более функциональным синтаксисом конвейера через %>%
. Это означает, что вместо кодирования этого:
> as.Date("2014-01-01")
> as.character((sqrt(12)^2)
Вы также можете сделать это:
> "2014-01-01" %>% as.Date
> 12 %>% sqrt %>% .^2 %>% as.character
Для меня это более читабельно, и это распространяется на случаи использования за пределами фрейма данных. Поддерживает ли язык Python нечто подобное?
python
functional-programming
pipeline
не могу
источник
источник
crime_by_state %>% filter(State=="New York", Year==2005) ...
с конца Как dplyr заменить мои наиболее распространенные идиомы R .Ответы:
Один из возможных способов сделать это - использовать модуль с именем
macropy
. Macropy позволяет вам применять преобразования к написанному вами коду. Таким образомa | b
может быть преобразован вb(a)
. У этого есть ряд преимуществ и недостатков.По сравнению с решением, упомянутым Сильвеном Леру, главное преимущество состоит в том, что вам не нужно создавать инфиксные объекты для интересующих вас функций - просто отметьте области кода, в которых вы собираетесь использовать преобразование. Во-вторых, поскольку преобразование применяется во время компиляции, а не во время выполнения, преобразованный код не испытывает накладных расходов во время выполнения - вся работа выполняется, когда байтовый код сначала создается из исходного кода.
Основными недостатками являются то, что для работы макропии требуется определенный способ активации (упоминается позже). В отличие от более быстрой среды выполнения, синтаксический анализ исходного кода является более сложным в вычислительном отношении, поэтому запуск программы займет больше времени. Наконец, он добавляет синтаксический стиль, который означает, что программистам, не знакомым с макропией, может быть труднее понять ваш код.
Пример кода:
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 для функционального канала в качестве синтаксиса эмулирующей оболочки для передачи вывода от одного процесса к другому.
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)
источник
Каналы - это новая функция в 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
NB: версия Pandas сохраняет эталонную семантику Python. Вот почему
length_times_width
не требуется возвращаемое значение; он модифицируетсяx
на месте.источник
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'
источник
"более функциональный синтаксис конвейера" действительно ли это более "функциональный" синтаксис? Я бы сказал, что вместо этого он добавляет "инфиксный" синтаксис к R.
При этом грамматика Python не имеет прямой поддержки инфиксной нотации, кроме стандартных операторов.
Если вам действительно нужно что-то подобное, вы должны взять этот код у Томера Филиба в качестве отправной точки для реализации вашей собственной инфиксной нотации:
источник
Если вам это нужно только для личного написания сценариев, вы можете рассмотреть возможность использования 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))
... TypeError: isinstance ожидал 2 аргумента, получил 1print(1 |> isinstance$(int))
, а лучше1 |> isinstance$(int) |> print
.1 |> print$(2)
вызывает,print(2, 1)
так как $ отображается на части Python. но я хочу,print(1, 2)
что соответствует UFCS и magrittr. Мотивация:1 |> add(2) |> divide(6)
должно быть 0,5, скобки не нужны.1 |> isinstance$(?, int) |> print
. Для других ваших примеров:1 |> print$(?, 2)
,1 |> (+)$(?, 2) |> (/)$(?, 6)
. Я не думаю, что вы можете избежать скобок для частичного применения.|>
и(+)$(?, 2)
, я пришел к выводу, что истеблишмент языков программирования и математики не хочет, чтобы я использовал этот тип синтаксиса, и делает его даже более уродливым, чем использование набора круглых скобок. Я бы использовал его, если бы у него был лучший синтаксис (например, у Dlang есть UFCS, но у IDK есть арифметические функции, или если бы у Python был..
оператор канала).Есть
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
являются одними и теми же пакетами. Есть ли между ними разница? @BigDataScientistdfply
,dplython
,plydata
Пакеты питона портыdplyr
пакет , так что они будут очень похожи на синтаксис.Я пропустил
|>
оператор канала из Elixir, поэтому я создал простой декоратор функций (~ 50 строк кода), который переинтерпретирует>>
оператор сдвига вправо Python как очень похожий на Elixir канал во время компиляции с использованием библиотеки ast и compile / exec: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, ...)
.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)
источник
Строительство
pipe
сInfix
Как намекнул Сильвен Леру , мы можем использовать
Infix
оператор для построения инфиксаpipe
. Посмотрим, как этого добиться.Во-первых, вот код от Томера Филиба.
Оператор канала передает предыдущий объект в качестве аргумента объекту, который следует за конвейером, поэтому
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'
Примечание о частичном применении
%>%
Оператор изdpylr
выталкивает аргументов через первый аргумент в функции, так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
Использование других инфиксных разделителей
Вы можете изменить символы, окружающие вызов Infix, переопределив другие методы операторов Python. Например, переключение
__or__
и__ror__
на__mod__
и__rmod__
изменит|
оператора наmod
оператора.In [5]: 12 %pipe% sqrt %pipe% str Out[5]: '3.4641016151377544'
источник
Нет необходимости в сторонних библиотеках или запутанных уловках операторов для реализации функции конвейера - вы можете довольно легко освоить основы самостоятельно.
Начнем с определения того, что на самом деле представляет собой функция канала. По сути, это просто способ выразить последовательность вызовов функций в логическом порядке, а не в стандартном порядке «наизнанку».
Например, давайте посмотрим на эти функции:
def one(value): return value def two(value): return 2*value def three(value): return 3*value
Не очень интересно, но предполагаю, что интересные вещи происходят
value
. Мы хотим вызывать их по порядку, передавая вывод каждого следующему. В ванильном питоне это будет:result = three(two(one(1)))
Это не очень хорошо читается, и для более сложных конвейеров будет хуже. Итак, вот простая функция конвейера, которая принимает начальный аргумент и ряд функций, к которым он применяется:
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)
источник
Добавление моего 2c. Я лично использую пакет fn для программирования в функциональном стиле. Ваш пример переводится как
from fn import F, _ from math import sqrt (F(sqrt) >> _**2 >> str)(12)
F
- это класс-оболочка с синтаксическим сахаром функционального стиля для частичного применения и композиции._
конструктор в стиле Scala для анонимных функций (аналогично Pythonlambda
); он представляет собой переменную, поэтому вы можете объединить несколько_
объектов в одно выражение, чтобы получить функцию с большим количеством аргументов (например_ + _
, эквивалентlambda a, b: a + b
).F(sqrt) >> _**2 >> str
в результате получаетсяCallable
объект, который можно использовать сколько угодно раз.источник
_
это не на 100% гибкое: он не поддерживает все операторы Python. Кроме того, если вы планируете использовать_
в интерактивном сеансе, вы должны импортировать его под другим именем (напримерfrom fn import _ as var
), потому что большинство (если не все) интерактивные оболочки Python используют_
для представления последнего неназначенного возвращаемого значения, тем самым затеняя импортированный объект.Одним из альтернативных решений может быть использование инструмента рабочего процесса dask. Хотя синтаксически это не так весело, как ...
... он по-прежнему позволяет вашей переменной течь вниз по цепочке, а использование dask дает дополнительное преимущество распараллеливания, где это возможно.
Вот как я использую dask для выполнения паттерна конвейерной цепочки:
import dask def a(foo): return foo + 1 def b(foo): return foo / 2 def c(foo,bar): return foo + bar # pattern = 'name_of_behavior': (method_to_call, variables_to_pass_in, variables_can_be_task_names) workflow = {'a_task':(a,1), 'b_task':(b,'a_task',), 'c_task':(c,99,'b_task'),} #dask.visualize(workflow) #visualization available. dask.get(workflow,'c_task') # returns 100
После работы с elixir я захотел использовать паттерн трубопроводов в Python. Это не совсем тот же шаблон, но он похож и, как я уже сказал, дает дополнительные преимущества распараллеливания; если вы скажете dask включить задачу в ваш рабочий процесс, которая не зависит от выполнения другими, они будут выполняться параллельно.
Если вам нужен более простой синтаксис, вы можете обернуть его чем-нибудь, что позаботится об именовании задач за вас. Конечно, в этой ситуации вам нужно, чтобы все функции принимали конвейер в качестве первого аргумента, и вы потеряете все преимущества параллелизации. Но если вас это устраивает, вы можете сделать что-то вроде этого:
def dask_pipe(initial_var, functions_args): ''' call the dask_pipe with an init_var, and a list of functions workflow, last_task = dask_pipe(initial_var, {function_1:[], function_2:[arg1, arg2]}) workflow, last_task = dask_pipe(initial_var, [function_1, function_2]) dask.get(workflow, last_task) ''' workflow = {} if isinstance(functions_args, list): for ix, function in enumerate(functions_args): if ix == 0: workflow['task_' + str(ix)] = (function, initial_var) else: workflow['task_' + str(ix)] = (function, 'task_' + str(ix - 1)) return workflow, 'task_' + str(ix) elif isinstance(functions_args, dict): for ix, (function, args) in enumerate(functions_args.items()): if ix == 0: workflow['task_' + str(ix)] = (function, initial_var) else: workflow['task_' + str(ix)] = (function, 'task_' + str(ix - 1), *args ) return workflow, 'task_' + str(ix) # piped functions def foo(df): return df[['a','b']] def bar(df, s1, s2): return df.columns.tolist() + [s1, s2] def baz(df): return df.columns.tolist() # setup import dask import pandas as pd df = pd.DataFrame({'a':[1,2,3],'b':[1,2,3],'c':[1,2,3]})
Теперь с помощью этой оболочки вы можете создать конвейер, соответствующий любому из этих синтаксических шаблонов:
# wf, lt = dask_pipe(initial_var, [function_1, function_2]) # wf, lt = dask_pipe(initial_var, {function_1:[], function_2:[arg1, arg2]})
как это:
# test 1 - lists for functions only: workflow, last_task = dask_pipe(df, [foo, baz]) print(dask.get(workflow, last_task)) # returns ['a','b'] # test 2 - dictionary for args: workflow, last_task = dask_pipe(df, {foo:[], bar:['string1', 'string2']}) print(dask.get(workflow, last_task)) # returns ['a','b','string1','string2']
источник
Здесь очень хороший
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
Плюс очень легко писать собственные пайп-функции
@Pipe def p_sqrt(x): return sqrt(x) @Pipe def p_pr(x): print(x) 9 | p_sqrt | p_pr
источник
Функциональность канала может быть достигнута путем компоновки методов pandas с точкой. Вот пример ниже.
Загрузите образец фрейма данных:
import seaborn iris = seaborn.load_dataset("iris") type(iris) # <class 'pandas.core.frame.DataFrame'>
Проиллюстрируйте состав методов pandas точкой:
(iris.query("species == 'setosa'") .sort_values("petal_width") .head())
При необходимости вы можете добавить новые методы во фрейм данных panda (например, как это сделано здесь ):
источник