Czy Pythonic używa list składanych tylko do efektów ubocznych?


108

Pomyśl o funkcji, którą wywołuję ze względu na jej skutki uboczne, a nie zwracanie wartości (jak drukowanie na ekranie, aktualizacja GUI, drukowanie do pliku itp.).

def fun_with_side_effects(x):
    ...side effects...
    return y

Teraz, czy Pythonic ma używać wyrażeń listowych do wywoływania tej funkcji:

[fun_with_side_effects(x) for x in y if (...conditions...)]

Zauważ, że nigdzie nie zapisuję listy

A może powinienem nazwać tę funkcję w ten sposób:

for x in y:
    if (...conditions...):
        fun_with_side_effects(x)

Który jest lepszy i dlaczego?


6
to jest granica, ale prawdopodobnie będziesz bardziej przeciwny niż popierający. Mam zamiar
przesadzić

6
To łatwy wybór. Czytelność się liczy - zrób to w drugą stronę. Jeśli nie możesz zmieścić 2 dodatkowych linii na ekranie, kup większy monitor :)
John La Rooy Kwietnia

1
Lista składana nie jest szpiegowska, ponieważ narusza zasadę „jawne jest lepsze niż niejawne” - ukrywasz pętlę w innej konstrukcji.
Fred Foo,

3
@larsmans: Gdyby tylko GvR zdał sobie z tego sprawę, kiedy wprowadził listy składane w pierwszej kolejności!
Steve Jessop

2
@larsmans, Steve Jessop, myślę, że niepoprawne jest pojmowanie rozumienia listy jako pętli. Można to z powodzeniem zaimplementować jako pętlę, ale celem takich konstrukcji jest działanie na zagregowanych danych w funkcjonalny i (koncepcyjnie) równoległy sposób. Jeśli występuje problem ze składnią, jest for ... inon używany w obu przypadkach - co prowadzi do pytań takich jak to!
senderle

Odpowiedzi:


84

Jest to bardzo anty-Pythonic, a każdy doświadczony Pythonista sprawi, że będziesz miał nad tym piekło. Lista pośrednia jest wyrzucana po utworzeniu i potencjalnie może być bardzo, bardzo duża, a zatem kosztowna w tworzeniu.


5
Więc jaki byłby bardziej pytoniczny sposób?
Joachim Sauer

6
Ten, który nie prowadzi listy; tj. jakiś wariant drugiego sposobu (wcześniej byłem znany z używania geneksu for, żeby się go pozbyć if).
Ignacio Vazquez-Abrams

6
@Joachim Sauer: Przykład 2 powyżej. Właściwa, jawna pętla rozumienia nie będąca listą. Wyraźny. Jasny. Oczywisty.
S.Lott

31

Nie powinieneś używać rozumienia list , ponieważ, jak powiedzieli ludzie, utworzy to dużą tymczasową listę, której nie potrzebujesz. Następujące dwie metody są równoważne:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

z definicją ze consumestrony podręcznika itertools:

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is none, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

Oczywiście to drugie jest jaśniejsze i łatwiejsze do zrozumienia.


@Paul: Myślę, że tak powinno być. I rzeczywiście, możesz, chociaż mapmoże nie być tak intuicyjny, jeśli wcześniej nie robiono programowania funkcjonalnego.
Katriel

4
Nie jestem pewien, czy jest to szczególnie idiomatyczne. Nie ma żadnej przewagi nad używaniem jawnej pętli.
Marcin

1
Rozwiązaniem jestconsume = collections.deque(maxlen=0).extend
PaulMcG

24

Listy składane służą do tworzenia list. I chyba faktycznie tworzenia listy, należy nie używać wyrażeń listowych.

Więc dostałbym drugą opcję, po prostu iterując po liście, a następnie wywołując funkcję, gdy mają zastosowanie warunki.


6
Pójdę jeszcze dalej i stwierdzę, że efekty uboczne wewnątrz rozumienia listy są niezwykłe, nieoczekiwane, a zatem złe, nawet jeśli używasz wynikowej listy, kiedy skończysz.
Mark Ransom

11

Druga jest lepsza.

Pomyśl o osobie, która musiałaby zrozumieć Twój kod. Za pierwszym razem możesz łatwo zdobyć złą karmę :)

Możesz przejść między nimi, używając filter (). Rozważmy przykład:

y=[1,2,3,4,5,6]
def func(x):
    print "call with %r"%x

for x in filter(lambda x: x>3, y):
    func(x)

10
Twoja lambda jest znacznie lepiej zapisana jako lambda x : x > 3.
PaulMcG

Nie potrzebujesz nawet filtra. Wystarczy umieścić wyrażenie generatora w parens tutaj: for el in (x for x in y if x > 3):. eli xmoże mieć to samo imię, ale może to mylić ludzi.
Omnifarious

3

Zależy od twojego celu.

Jeśli próbujesz wykonać jakąś operację na każdym obiekcie z listy, należy zastosować drugie podejście.

Jeśli próbujesz wygenerować listę z innej listy, możesz użyć rozumienia list.

Jawne jest lepsze niż niejawne. Proste jest lepsze niż złożone. (Python Zen)


0

Możesz to zrobić

for z in (fun_with_side_effects(x) for x in y if (...conditions...)): pass

ale nie jest zbyt ładna.


-1

Używanie funkcji rozumienia list do efektów ubocznych jest brzydkie, nie-Pythoniczne, nieefektywne i nie zrobiłbym tego. Chciałbym użyć forpętli zamiast, ponieważ forpętla sygnalizuje styl proceduralną, w której skutki uboczne są ważne.

Jeśli jednak absolutnie nalegasz na używanie funkcji list złożonych do wywoływania skutków ubocznych, powinieneś uniknąć nieefektywności, używając zamiast tego wyrażenia generatora. Jeśli absolutnie nalegasz na ten styl, wykonaj jedną z tych dwóch czynności:

any(fun_with_side_effects(x) and False for x in y if (...conditions...))

lub:

all(fun_with_side_effects(x) or True for x in y if (...conditions...))

Są to wyrażenia generatora, które nie generują losowej listy, która zostaje wyrzucona. Myślę, że allforma jest być może nieco bardziej przejrzysta, chociaż myślę, że oba są mylące i nie powinny być używane.

Myślę, że to jest brzydkie i nie zrobiłbym tego w kodzie. Ale jeśli nalegasz na implementację swoich pętli w ten sposób, tak bym to zrobił.

Wydaje mi się, że listy składane i im podobne powinny sygnalizować próbę użycia czegoś, co najmniej trochę przypominającego styl funkcjonalny. Umieszczanie rzeczy z efektami ubocznymi, które łamią to założenie, spowoduje, że ludzie będą musieli uważniej czytać twój kod i myślę, że to zła rzecz.


A co, jeśli fun_with_side_effectszwraca True?
Katriel

7
Myślę, że to lekarstwo jest gorsze niż choroba - itertools. Konsumpcja jest dużo czystsza.
PaulMcG

@PaulMcG - itertools.consumejuż nie istnieje, prawdopodobnie dlatego, że używanie wyrażeń ze skutkami ubocznymi jest brzydkie.
Omnifarious

1
Okazuje się, że się myliłem i nigdy nie istniało jako metoda w standardowym bibliotece standardowej. To jest przepis w dokumentacji itertools: docs.python.org/3/library/ ...
PaulMcG
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.