W R (dzięki magrittr ) możesz teraz wykonywać operacje z bardziej funkcjonalną składnią potoków za pośrednictwem %>%
. Oznacza to, że zamiast kodować to:
> as.Date("2014-01-01")
> as.character((sqrt(12)^2)
Możesz też to zrobić:
> "2014-01-01" %>% as.Date
> 12 %>% sqrt %>% .^2 %>% as.character
Dla mnie jest to bardziej czytelne i obejmuje przypadki użycia poza ramką danych. Czy język Python obsługuje coś podobnego?
python
functional-programming
pipeline
cantdutchthis
źródło
źródło
crime_by_state %>% filter(State=="New York", Year==2005) ...
od końca Jak dplyr zastąpić moje najczęstszych idiomów R .Odpowiedzi:
Jednym z możliwych sposobów jest użycie modułu o nazwie
macropy
. Makropia umożliwia zastosowanie transformacji do napisanego kodu. W ten sposóba | b
można przekształcić wb(a)
. Ma to wiele zalet i wad.W porównaniu do rozwiązania, o którym wspomniał Sylvain Leroux, Główną zaletą jest to, że nie musisz tworzyć obiektów wrostkowych dla funkcji, którymi jesteś zainteresowany - wystarczy zaznaczyć obszary kodu, które zamierzasz wykorzystać w transformacji. Po drugie, ponieważ transformacja jest stosowana w czasie kompilacji, a nie w czasie wykonywania, transformowany kod nie ponosi żadnych kosztów w czasie wykonywania - cała praca jest wykonywana, gdy kod bajtowy jest najpierw tworzony z kodu źródłowego.
Główną wadą jest to, że makropia wymaga określonego sposobu aktywacji, aby mogła działać (o czym będzie mowa później). W przeciwieństwie do szybszego środowiska uruchomieniowego, analizowanie kodu źródłowego jest bardziej złożone obliczeniowo, więc uruchomienie programu zajmie więcej czasu. Wreszcie, dodaje styl składniowy, co oznacza, że programiści, którzy nie są zaznajomieni z makropią, mogą uznać twój kod za trudniejszy do zrozumienia.
Przykładowy kod:
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']
I wreszcie moduł, który wykonuje ciężką pracę. Nazwałem go fpipe dla potoku funkcjonalnego jako emulująca składnia powłoki do przekazywania danych wyjściowych z jednego procesu do drugiego.
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)
źródło
Rury to nowa funkcja w Pandas 0.16.2 .
Przykład:
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
Uwaga: wersja Pandas zachowuje semantykę referencyjną Pythona. Dlatego
length_times_width
nie potrzebuje wartości zwracanej; zmienia sięx
w miejscu.źródło
PyToolz [doc] pozwala na dowolne komponowanie potoków, ale nie są one zdefiniowane za pomocą tej składni operatora potoku.
Skorzystaj z powyższego linku, aby uzyskać szybki start. A oto samouczek wideo: 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ódło
„bardziej funkcjonalna składnia potoków” czy naprawdę jest to bardziej „funkcjonalna” składnia? Powiedziałbym, że zamiast tego dodaje składnię „wrostek” do R.
Mimo to gramatyka języka Python nie obsługuje bezpośrednio notacji wrostków poza standardowymi operatorami.
Jeśli naprawdę potrzebujesz czegoś takiego, powinieneś wziąć ten kod od Tomera Filiby jako punkt wyjścia do zaimplementowania własnej notacji wrostkowej:
źródło
Jeśli chcesz tego tylko do osobistego pisania skryptów, możesz rozważyć użycie Coconut zamiast Pythona.
Coconut to nadzbiór Pythona. Możesz zatem użyć operatora potoku Coconut
|>
, całkowicie ignorując resztę języka Coconut.Na przykład:
def addone(x): x + 1 3 |> addone
kompiluje się do
# lots of auto-generated header junk # Compiled Coconut: ----------------------------------------------------------- def addone(x): return x + 1 (addone)(3)
źródło
print(1 |> isinstance(int))
... TypeError: isinstance spodziewa się 2 argumentów, otrzymano 1print(1 |> isinstance$(int))
lub najlepiej1 |> isinstance$(int) |> print
.1 |> print$(2)
wywołania,print(2, 1)
ponieważ $ mapuje na części składowe Pythona. ale chcę,print(1, 2)
który pasuje do UFCS i magrittr. Motywacja:1 |> add(2) |> divide(6)
powinna wynosić 0,5 i nie potrzebuję nawiasów.1 |> isinstance$(?, int) |> print
. Inne przykłady:1 |> print$(?, 2)
,1 |> (+)$(?, 2) |> (/)$(?, 6)
. Nie sądzę, aby można było uniknąć nawiasów przy częściowym zastosowaniu.|>
i drugie(+)$(?, 2)
, doszedłem do wniosku, że język programowania i matematyka nie chcą, żebym używał tego typu składni, i sprawia, że jest to jeszcze brzydsze niż uciekanie się do zestawu nawiasów. Użyłbym go, gdyby miał lepszą składnię (np. Dlang ma UFCS, ale IDK o funkcjach arytmetycznych lub gdyby Python miał..
operator potoku).Jest
dfply
moduł. Więcej informacji można znaleźć pod adresemhttps://github.com/kieferk/dfply
Oto kilka przykładów:
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)
źródło
dfply
idplython
są tymi samymi pakietami. Czy jest między nimi jakaś różnica? @BigDataScientistdfply
,dplython
,plydata
Pakiety są porty python zdplyr
pakietu są więc będzie dość podobny w składni.Brakowało mi
|>
operatora potoku z Elixir, więc stworzyłem prosty dekorator funkcji (~ 50 linii kodu), który reinterpretuje>>
Pythona w prawo jako potok podobny do potoku Elixir w czasie kompilacji przy użyciu biblioteki ast i kompilacji / 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
Wszystko, co robi, to przepisywanie
a >> b(...)
jakob(a, ...)
.https://pypi.org/project/pipeop/
https://github.com/robinhilliard/pipes
źródło
Możesz użyć biblioteki sspipe . Odsłania dwa obiekty
p
ipx
. Podobniex %>% f(y,z)
jak możesz pisaćx | p(f, y, z)
i podobnie jakx %>% .^2
potrafisz pisaćx | px**2
.from sspipe import p, px from math import sqrt 12 | p(sqrt) | px ** 2 | p(str)
źródło
Budynek
pipe
zInfix
Jak zasugerował Sylvain Leroux , możemy użyć
Infix
operatora do skonstruowania wrostkapipe
. Zobaczmy, jak to się robi.Po pierwsze, oto kod od Tomera Filiby
Operator potoku przekazuje poprzedni obiekt jako argument do obiektu, który następuje po potoku, dzięki czemu
x %>% f
można go przekształcić wf(x)
. W konsekwencjipipe
operator można zdefiniowaćInfix
w następujący sposób: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'
Uwaga dotycząca częściowego zastosowania
%>%
Operatora zdpylr
popycha argumenty przez pierwszy argument funkcji, takdf %>% filter(x >= 2) %>% mutate(y = 2*x)
koresponduje z
df1 <- filter(df, x >= 2) df2 <- mutate(df1, y = 2*x)
Najłatwiejszym sposobem osiągnięcia czegoś podobnego w Pythonie jest użycie curry .
toolz
Biblioteka udostępniacurry
funkcję dekoratora sprawia, że konstrukcja funkcji curried łatwe.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)
Zauważ, że
|pipe|
wypycha argumenty na ostatnią pozycję argumentu , to znaczyx |pipe| f(2)
koresponduje z
f(2, x)
Projektując funkcje curried, argumenty statyczne (tj. Argumenty, które mogą być użyte w wielu przykładach) należy umieścić wcześniej na liście parametrów.
Należy pamiętać, że
toolz
zawiera wiele wstępnie ustawionych funkcji, w tym różne funkcjeoperator
modułu.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]
co mniej więcej odpowiada następującemu w R
> library(dplyr) > add2 <- function(x) {x + 2} > 0:4 %>% sapply(add2) [1] 2 3 4 5 6
Używanie innych ograniczników wrostków
Możesz zmienić symbole otaczające wywołanie Infix, zastępując inne metody operatora Pythona. Na przykład przełączenie
__or__
i__ror__
do__mod__
i__rmod__
zmieni|
operatora namod
operatora.In [5]: 12 %pipe% sqrt %pipe% str Out[5]: '3.4641016151377544'
źródło
Do zaimplementowania funkcji potoku nie są potrzebne żadne biblioteki innych firm ani skomplikowane sztuczki operatorów - możesz łatwo uzyskać podstawy.
Zacznijmy od zdefiniowania, czym właściwie jest funkcja potoku. W istocie jest to po prostu sposób na wyrażenie serii wywołań funkcji w logicznej kolejności, a nie w standardowej kolejności „na lewą stronę”.
Na przykład spójrzmy na te funkcje:
def one(value): return value def two(value): return 2*value def three(value): return 3*value
Niezbyt interesujące, ale zakładaj, że dzieją się ciekawe rzeczy
value
. Chcemy wywołać je po kolei, przekazując wynik każdego z nich do następnego. W waniliowym pytonie byłoby to:result = three(two(one(1)))
Nie jest niesamowicie czytelny, a dla bardziej złożonych rurociągów będzie gorzej. Oto prosta funkcja potoku, która pobiera argument początkowy i szereg funkcji, do których można go zastosować:
def pipe(first, *args): for fn in args: first = fn(first) return first
Nazwijmy to:
result = pipe(1, one, two, three)
Dla mnie wygląda to na bardzo czytelną składnię potoku :). Nie widzę, żeby było to mniej czytelne niż przeciążanie operatorów lub coś w tym rodzaju. W rzeczywistości twierdziłbym, że jest to bardziej czytelny kod Pythona
Oto skromna rura rozwiązująca przykłady 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)
źródło
Dodanie mojego 2c. Osobiście używam pakietu fn do programowania w stylu funkcjonalnym. Twój przykład przekłada się na
from fn import F, _ from math import sqrt (F(sqrt) >> _**2 >> str)(12)
F
to klasa opakowań z funkcjonalnym cukrem syntaktycznym do częściowego zastosowania i kompozycji._
jest konstruktorem w stylu Scala dla funkcji anonimowych (podobnie jak w Pythonielambda
); reprezentuje zmienną, dlatego można połączyć kilka_
obiektów w jednym wyrażeniu, aby uzyskać funkcję z większą liczbą argumentów (np._ + _
jest równoważnalambda a, b: a + b
).F(sqrt) >> _**2 >> str
wCallable
rezultacie powstaje obiekt, którego można używać dowolną liczbę razy.źródło
_
nie jest to w 100% elastyczne: nie obsługuje wszystkich operatorów Pythona. Dodatkowo, jeśli planujesz używać_
w sesji interaktywnej, powinieneś zaimportować go pod inną nazwą (np.from fn import _ as var
), Ponieważ większość (jeśli nie wszystkie) interaktywnych powłok Pythona używa_
do reprezentowania ostatniej nieprzypisanej zwróconej wartości, w ten sposób cieniując zaimportowany obiekt.Jednym z alternatywnych rozwiązań byłoby użycie dask narzędzia przepływu pracy. Chociaż nie jest to tak zabawne składniowo, jak ...
... nadal umożliwia przepływ zmiennej w dół łańcucha, a użycie dask zapewnia dodatkową korzyść w postaci równoległości, jeśli to możliwe.
Oto jak używam dask, aby uzyskać wzór łańcucha rurowego:
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
Po pracy z eliksirem chciałem użyć wzorca rurociągów w Pythonie. To nie jest dokładnie ten sam wzorzec, ale jest podobny i, jak powiedziałem, ma dodatkowe zalety równoległości; jeśli powiesz dask, aby otrzymał zadanie w swoim przepływie pracy, które nie jest zależne od innych, aby uruchomiły się jako pierwsze, będą one działać równolegle.
Jeśli chcesz mieć prostszą składnię, możesz ją opakować w coś, co zajmie się nazewnictwem zadań za Ciebie. Oczywiście w takiej sytuacji wszystkie funkcje powinny przyjmować potok jako pierwszy argument i stracisz korzyści z paralizacji. Ale jeśli nie masz nic przeciwko, możesz zrobić coś takiego:
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]})
Teraz, dzięki temu opakowaniu, możesz utworzyć potok według jednego z tych wzorców składniowych:
# wf, lt = dask_pipe(initial_var, [function_1, function_2]) # wf, lt = dask_pipe(initial_var, {function_1:[], function_2:[arg1, arg2]})
lubię to:
# 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']
źródło
Jest tu bardzo fajny
pipe
moduł https://pypi.org/project/pipe/ Przeciąża | operator i zapewnia wiele funkcjiadd, first, where, tail
potokowych, takich jak itp.>>> [1, 2, 3, 4] | where(lambda x: x % 2 == 0) | add 6 >>> sum([1, [2, 3], 4] | traverse) 10
Dodatkowo bardzo łatwo jest napisać własne funkcje potokowe
@Pipe def p_sqrt(x): return sqrt(x) @Pipe def p_pr(x): print(x) 9 | p_sqrt | p_pr
źródło
Funkcjonalność rury można osiągnąć, komponując metody pandy z kropką. Oto przykład poniżej.
Załaduj przykładową ramkę danych:
import seaborn iris = seaborn.load_dataset("iris") type(iris) # <class 'pandas.core.frame.DataFrame'>
Zilustruj skład metod pandy kropką:
(iris.query("species == 'setosa'") .sort_values("petal_width") .head())
W razie potrzeby możesz dodać nowe metody do ramki danych panda (jak na przykład tutaj ):
źródło
Moje dwa centy zainspirowane http://tomerfiliba.com/blog/Infix-Operators/
class FuncPipe: class Arg: def __init__(self, arg): self.arg = arg def __or__(self, func): return func(self.arg) def __ror__(self, arg): return self.Arg(arg) pipe = FuncPipe()
Następnie
1 |pipe| \ (lambda x: return x+1) |pipe| \ (lambda x: return 2*x)
zwroty
4
źródło