Pythoniczny sposób łączenia pętli FOR i instrukcji IF

266

Wiem, jak używać zarówno do pętli, jak i instrukcji w osobnych wierszach, takich jak:

>>> a = [2,3,4,5,6,7,8,9,0]
... xyz = [0,12,4,6,242,7,9]
... for x in xyz:
...     if x in a:
...         print(x)
0,4,6,7,9

I wiem, że mogę użyć rozumienia listy, aby połączyć je, gdy stwierdzenia są proste, takie jak:

print([x for x in xyz if x in a])

Ale nie mogę znaleźć nigdzie dobrego przykładu (do skopiowania i uczenia się) demonstrującego złożony zestaw poleceń (nie tylko „print x”), które występują po kombinacji pętli for i niektórych instrukcji if. Coś, czego oczekiwałbym, wygląda:

for x in xyz if x not in a:
    print(x...)

Czy to nie tak powinien działać Python?

ChewyChunks
źródło
23
Tak to jest ... nie komplikuj rzeczy, próbując je uprościć. Pythonic nie oznacza unikania każdej wyraźnej forpętli i ifinstrukcji.
Felix Kling
2
Możesz użyć listy wygenerowanej w trybie rozumienia listy w pętli for. To by wyglądało trochę jak twój ostatni przykład.
Jacob
Przejdźmy więc do przetwarzania, jaki jest najszybszy sposób na połączenie pętli for z instrukcją if, jeśli instrukcja if wyklucza wartości, które już zostały dopasowane, a lista stale się powiększa podczas iteracji pętli for?
ChewyChunks
3
@Chewy, odpowiednie struktury danych sprawią, że kod będzie szybszy, a nie cukier składniowy. Na przykład x in ajest wolny, jeśli ajest listą.
Nick Dandoulakis,
1
To jest Python, język interpretowany; dlaczego ktokolwiek dyskutuje o tym, jak szybki jest kod?
ArtOfWarfare

Odpowiedzi:

323

Możesz użyć wyrażeń generatora takich jak to:

gen = (x for x in xyz if x not in a)

for x in gen:
    print x
Kugel
źródło
1
gen = (y for (x,y) in enumerate(xyz) if x not in a)zwraca >>> 12podczas pisania for x in gen: print x- więc dlaczego nieoczekiwane zachowanie z wyliczeniem?
ChewyChunks
9
Możliwe, ale nie ładniejsze niż oryginał dla i jeśli bloki.
Mike Graham,
1
@ChewyChunks. To by działało, ale wezwanie do wyliczenia jest zbędne.
Johnsyweb,
132
Naprawdę brakuje mi możliwości powiedzenia for x in xyz if x:
pytona
10
for x in (x for x in xyz if x not in a):działa dla mnie, ale dlaczego nie powinieneś być w stanie to zrobić for x in xyz if x not in a:, nie jestem pewien ...
Matt Wenham
34

Zgodnie z Zen of Python (jeśli zastanawiasz się, czy Twój kod to „ Python ”, to jest miejsce, do którego należy przejść):

  • Piękne jest lepsze niż brzydkie.
  • Jawne jest lepsze niż niejawne.
  • Prosty jest lepszy niż złożony.
  • Mieszkanie jest lepsze niż zagnieżdżone.
  • Liczy się czytelność.

Pythoniczny sposób uzyskania dwóch s jest następujący:sorted intersectionset

>>> sorted(set(a).intersection(xyz))
[0, 4, 6, 7, 9]

Lub te elementy, które xyznie są w a:

>>> sorted(set(xyz).difference(a))
[12, 242]

Ale w przypadku bardziej skomplikowanej pętli możesz ją spłaszczyć, wykonując iterację nad dobrze nazwanym wyrażeniem generatora i / lub wywołując dobrze nazwaną funkcję. Próbowanie dopasowania wszystkiego w jednym wierszu rzadko jest „pytoniczne”.


Zaktualizuj po dodatkowych komentarzach do pytania i zaakceptowanej odpowiedzi

Nie jestem pewien, co próbujesz zrobić enumerate, ale jeśli ajest słownikiem, prawdopodobnie chcesz użyć klawiszy:

>>> a = {
...     2: 'Turtle Doves',
...     3: 'French Hens',
...     4: 'Colly Birds',
...     5: 'Gold Rings',
...     6: 'Geese-a-Laying',
...     7: 'Swans-a-Swimming',
...     8: 'Maids-a-Milking',
...     9: 'Ladies Dancing',
...     0: 'Camel Books',
... }
>>>
>>> xyz = [0, 12, 4, 6, 242, 7, 9]
>>>
>>> known_things = sorted(set(a.iterkeys()).intersection(xyz))
>>> unknown_things = sorted(set(xyz).difference(a.iterkeys()))
>>>
>>> for thing in known_things:
...     print 'I know about', a[thing]
...
I know about Camel Books
I know about Colly Birds
I know about Geese-a-Laying
I know about Swans-a-Swimming
I know about Ladies Dancing
>>> print '...but...'
...but...
>>>
>>> for thing in unknown_things:
...     print "I don't know what happened on the {0}th day of Christmas".format(thing)
...
I don't know what happened on the 12th day of Christmas
I don't know what happened on the 242th day of Christmas
Johnsyweb
źródło
Brzmi jak z poniższych komentarzy, powinienem studiować generatory. Nigdy ich nie użyłem. Dzięki. Czy generator jest szybszy niż równoważna kombinacja instrukcji FOR i IF? Użyłem również zestawów, ale czasami zbędne elementy na liście to informacje, których nie mogę odrzucić.
ChewyChunks
@ChewyChunks: Generatory to nie jedyny sposób na Pythonic!
Johnsyweb,
3
@Johnsyweb, jeśli chcesz zacytować Zen Pythona: „Powinien być jeden - a najlepiej tylko jeden - oczywisty sposób na zrobienie tego”.
Wooble,
@Wooble: Powinien. W tym samym czasie zacytowałem tę sekcję w odpowiedzi na inne pytanie !
Johnsyweb,
18

Osobiście uważam, że jest to najładniejsza wersja:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]
for x in filter(lambda w: w in a, xyz):
  print x

Edytować

jeśli bardzo zależy ci na uniknięciu użycia lambda, możesz użyć częściowej aplikacji funkcji i użyć modułu operatora (który zapewnia funkcje większości operatorów).

https://docs.python.org/2/library/operator.html#module-operator

from operator import contains
from functools import partial
print(list(filter(partial(contains, a), xyz)))
Alex
źródło
4
filter(a.__contains__, xyz). Zwykle, gdy ludzie używają lambda, naprawdę potrzebują czegoś znacznie prostszego.
Veky
Myślę, że coś źle zrozumiałeś. __contains__jest metodą jak każda inna, tylko jest to metoda specjalna , co oznacza, że ​​może być wywołana pośrednio przez operatora ( inw tym przypadku). Ale można go również wywołać bezpośrednio, jest to część publicznego API. Nazwy prywatne są specjalnie zdefiniowane jako mające co najwyżej jeden końcowy znak podkreślenia, aby zapewnić wyjątek dla specjalnych nazw metod - i podlegają manglowaniu nazw, gdy leksykalnie w zakresach klas. Zobacz docs.python.org/3/reference/datamodel.html#specialnames i docs.python.org/3.6/tutorial/classes.html#private-variables .
Veky
Z pewnością jest ok, ale dwa importy, aby móc odwoływać się do metody dostępnej za pomocą tylko atrybutu, wydają się dziwne (operatory są zwykle używane, gdy podwójne wysyłanie jest niezbędne, ale injest wysyłane pojedynczo z odpowiednim operandem). Poza tym zauważ, że operatoreksportuje również containsmetodę pod nazwą __contains__, więc na pewno nie jest to nazwa prywatna. Myślę, że będziesz musiał nauczyć się żyć z faktem, że nie każdy podwójny znak podkreślenia oznacza „trzymaj się z dala”. : -]
Veky
Myślę, że twoje lambdapotrzeby obejmują not: lambda w: not w in a, xyz
javadba,
Filtr wydaje się bardziej elegancki, szczególnie w przypadku złożonych warunków, które stałyby się zdefiniowanymi funkcjami zamiast lambdas, może nazwanie funkcji lambda zwiększyłoby czytelność, Generator wydaje się lepszy, gdy elementy iterowane są modyfikacją elementów listy
Khanis Rok
16

Poniżej znajduje się uproszczenie / jedna linijka z zaakceptowanej odpowiedzi:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]

for x in (x for x in xyz if x not in a):
    print(x)

12
242

Zwróć uwagę, że generatorzostał utrzymany w linii . Zostało to przetestowane python2.7i python3.6 (zauważ pareny w print;))

javadba
źródło
10

Prawdopodobnie użyłbym:

for x in xyz: 
    if x not in a:
        print x...
Wim Feijen
źródło
@KirillTitov Tak Python jest zasadniczo niefunkcjonalnym językiem (jest to kodowanie absolutnie konieczne - i zgadzam się z autorem tej odpowiedzi, że jest to sposób, w jaki python jest skonfigurowany do pisania. Próba użycia funkcjonałów prowadzi do słabego czytania lub pythonicwyniki. Mogę kodować funkcjonalnie w każdym innym używanym
przeze
9
a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]  
set(a) & set(xyz)  
set([0, 9, 4, 6, 7])
Kracekumar
źródło
Bardzo Zen, @lazyr, ale nie pomógłby mi ulepszyć złożonego bloku kodu, który polega na iteracji jednej listy i ignorowaniu pasujących elementów na innej liście. Czy szybciej jest traktować pierwszą listę jako zestaw i porównywać sumę / różnicę z drugą rosnącą listą „ignoruj”?
ChewyChunks
Spróbuj tegoimport time a = [2,3,4,5,6,7,8,9,0] xyz = [0,12,4,6,242,7,9] start = time.time() print (set(a) & set(xyz)) print time.time() - start
Kracekumar
@ChewyChunks, jeśli którakolwiek z list zmieni się podczas iteracji, prawdopodobnie szybciej będzie sprawdzić każdy element na liście ignorowanych - z wyjątkiem tego, że powinieneś ustawić go na ignorowanie. Sprawdzanie członkostwa w zestawach jest bardzo szybki: if x in ignore: ....
Lauritz V. Thaulow
@lazyr Właśnie przepisałem mój kod za pomocą zestawu ignorowanego na liście ignorowanych. Wydaje się, że czas przetwarzania jest znacznie wolniejszy. (Szczerze mówiąc, porównywałem używanie, if set(a) - set(ignore) == set([]):więc być może dlatego było to znacznie wolniejsze niż sprawdzanie członkostwa. W przyszłości przetestuję to na znacznie prostszym przykładzie niż to, co piszę.
ChewyChunks
5

Możesz także użyć generatorów , jeśli wyrażenia generatorów stają się zbyt skomplikowane lub złożone:

def gen():
    for x in xyz:
        if x in a:
            yield x

for x in gen():
    print x
Lauritz V. Thaulow
źródło
Jest to dla mnie trochę bardziej przydatne. Nigdy nie patrzyłem na generatory. Brzmią przerażająco (ponieważ widziałem je w modułach, które generalnie były uciążliwe w użyciu).
ChewyChunks
2

Użyj intersectionlubintersection_update

  • skrzyżowanie :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    ans = sorted(set(a).intersection(set(xyz)))
  • intersection_update :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    b = set(a)
    b.intersection_update(xyz)

    to bjest twoja odpowiedź

Chung-Yen Hung
źródło
2

Lubiłem odpowiedź Alexa , ponieważ filtr jest akuratnie jeśli stosuje się do listy, więc jeśli chcesz poznać podzbiór listy dany warunek, to wydaje się być najbardziej naturalnym sposobem

mylist = [1,2,3,4,5]
another_list = [2,3,4]

wanted = lambda x:x in another_list

for x in filter(wanted, mylist):
    print(x)

metoda ta jest przydatna do rozdzielenia problemów, jeśli zmieni się funkcja warunku, jedynym kodem do manipulowania jest sama funkcja

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

for x in filter(wanted, mylist):
    print(x)

Metoda generatora wydaje się lepsza, gdy nie chcesz członków listy, ale modyfikacja tych elementów, która wydaje się bardziej dopasowana do generatora

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.5 for x in mylist if wanted(x))

for x in generator:
    print(x)

Ponadto filtry działają z generatorami, chociaż w tym przypadku nie jest to wydajne

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.9 for x in mylist)

for x in filter(wanted, generator):
    print(x)

Ale oczywiście byłoby miło napisać w ten sposób:

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

# for x in filter(wanted, mylist):
for x in mylist if wanted(x):
    print(x)
Khanis Rok
źródło
0

Prosty sposób na znalezienie unikalnych wspólnych elementów list a i b:

a = [1,2,3]
b = [3,6,2]
for both in set(a) & set(b):
    print(both)
Peawormsworth
źródło