Czytając praktyczne wprowadzenie Mary Rose Cook do programowania funkcjonalnego , podaje jako przykład anty-wzorca
def format_bands(bands):
for band in bands:
band['country'] = 'Canada'
band['name'] = band['name'].replace('.', '')
band['name'] = band['name'].title()
od
- funkcja robi więcej niż jedną rzecz
- nazwa nie jest opisowa
- ma skutki uboczne
Jako zaproponowane rozwiązanie sugeruje anonimowe funkcje potokowe
pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
call(lambda x: x.replace('.', ''), 'name'),
call(str.title, 'name')])
Wydaje mi się jednak, że ma to tę wadę, że jest jeszcze mniej sprawdzalne; przynajmniej format_bands może mieć test jednostkowy, aby sprawdzić, czy robi to, co powinien, ale jak przetestować potok? Czy też pomysł, że funkcje anonimowe są tak oczywiste, że nie trzeba ich testować?
Moja aplikacja do tego celu polega na tym, aby mój pandas
kod był bardziej funkcjonalny. Często mam jakiś potok wewnątrz funkcji „munging”
def munge_data(df)
df['name'] = df['name'].str.lower()
df = df.drop_duplicates()
return df
Lub przepisywanie w stylu potoku:
def munge_data(df)
munged = (df.assign(lambda x: x['name'].str.lower()
.drop_duplicates())
return munged
Wszelkie sugestie dotyczące najlepszych praktyk w takiej sytuacji?
python
unit-testing
Max Flander
źródło
źródło
Odpowiedzi:
Myślę, że przegapiłeś chyba najważniejszą część poprawionego przykładu książki. Bardziej fundamentalna zmiana w kodzie dotyczy metody działającej na wszystkich wartościach listy na działanie na jednym elemencie.
Istnieją już funkcje takie jak
iter
(w tym przypadku o nazwiepipeline_foreach
), które wykonują daną operację na wszystkich elementach listy. Nie trzeba było powielać tego za pomocąfor
pętli. Również użycie dobrze znanej operacji na liście wyjaśnia twoje zamiary. Wraz zmap
tobą przekształcasz wartości. Ziter
wykonujesz efekt uboczny z każdym elementem. Dziękifor
pętli jesteś ... cóż, tak naprawdę nie wiesz, dopóki tego nie przejrzysz.Przykładowo poprawiony kod wciąż nie jest zbyt funkcjonalny, ponieważ (o ile wiem) mutuje wartości na liście bez zwracania ich, zapobiegając dalszemu potokowaniu lub układowi funkcji. Preferowana funkcjonalnie metoda
map
stworzyłaby nową listę pasm ze zaktualizowanymcountry
iname
. Następnie możesz przesłać dane wyjściowe do następnej funkcji lub skomponowaćmap
z inną funkcją, która pobierała listę pasm. Ziter
, to jak ślepy zaułek rurociągów.Myślę, że kod wyniku końcowego ma małe funkcje, które są zbyt trywialne, aby zawracać sobie głowę testowaniem tutaj. W końcu nie powinieneś pisać testów jednostkowych przeciwko
replace
lubtitle
. Być może teraz chcesz połączyć je razem we własne funkcje i testy jednostkowe, aby uzyskać pożądaną kombinację na jednym elemencie. Ja sam prawdopodobnie zmieniłbym sięformat_bands
naformat_band
liczbę pojedynczą, porzuciłem pętlę for i zadzwoniłempipeline_each(bands, format_band)
. Następnie możesz przetestować format_band, aby upewnić się, że niczego nie zapomniałeś.W każdym razie przejdź do twojego kodu. Drugi przykład kodu wydaje się bardziej potokowy. Ale to nie zapewnia korzyści z programowania funkcjonalnego. W praktyce programowanie funkcjonalne oznacza zapewnienie zgodności funkcji z innymi funkcjami poprzez zdefiniowanie ich zgodności tylko w kategoriach ich wejść i wyjść. Jeśli wewnątrz funkcji są ukryte efekty uboczne, to pomimo tego, że jej wejście / wyjście jest zestawione z inną funkcją, nie możesz wiedzieć, czy są one kompatybilne do czasu uruchomienia. Jeśli jednak dwie funkcje nie wywołują skutków ubocznych i dopasowują dane wyjściowe do danych wejściowych, można je potokować lub komponować, nie martwiąc się o nieoczekiwane wyniki.
źródło