Cara Pythonic untuk menggabungkan pernyataan FOR loop dan IF

266

Saya tahu cara menggunakan keduanya untuk loop dan jika pernyataan pada baris terpisah, seperti:

>>> 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

Dan saya tahu saya bisa menggunakan daftar pemahaman untuk menggabungkan ini ketika pernyataan sederhana, seperti:

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

Tetapi apa yang saya tidak dapat temukan adalah contoh yang baik di mana saja (untuk menyalin dan belajar dari) menunjukkan serangkaian perintah yang kompleks (bukan hanya "cetak x") yang terjadi setelah kombinasi dari loop for dan beberapa pernyataan if. Sesuatu yang saya harapkan terlihat seperti:

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

Apakah ini bukan cara python seharusnya bekerja?

ChewyChunks
sumber
23
Begitulah ... jangan terlalu rumit dengan mencoba menyederhanakannya. Pythonic tidak berarti menghindari setiap forperulangan dan ifpernyataan eksplisit .
Felix Kling
2
Anda bisa menggunakan daftar yang dihasilkan dalam pemahaman daftar Anda dalam for for loop. Itu akan terlihat seperti contoh terakhir Anda.
Yakub
Jadi, turun ke pemrosesan, apa cara tercepat untuk menggabungkan loop for dengan pernyataan if, jika pernyataan if tidak termasuk nilai-nilai yang telah dicocokkan dan daftar terus tumbuh selama iterasi for loop?
ChewyChunks
3
@Chewy, struktur data yang tepat akan membuat kode lebih cepat, bukan gula sintaksis. Misalnya, x in alambat jika adaftar.
Nick Dandoulakis
1
Ini adalah Python, bahasa yang ditafsirkan; mengapa ada yang mendiskusikan seberapa cepat kode itu?
ArtOfWarfare

Jawaban:

323

Anda dapat menggunakan ekspresi generator seperti ini:

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

for x in gen:
    print x
Kugel
sumber
1
gen = (y for (x,y) in enumerate(xyz) if x not in a)mengembalikan >>> 12saat saya mengetik for x in gen: print x- jadi mengapa perilaku tak terduga dengan penghitungan?
ChewyChunks
9
Mungkin, tetapi tidak lebih baik daripada yang asli untuk dan jika blok.
Mike Graham
1
@ChewyChunks. Itu akan berhasil tetapi panggilan untuk menghitung itu berlebihan.
Johnsyweb
132
Saya sangat merindukan python untuk bisa mengatakanfor x in xyz if x:
bgusach
10
for x in (x for x in xyz if x not in a):bekerja untuk saya, tetapi mengapa Anda seharusnya tidak hanya bisa melakukan for x in xyz if x not in a:, saya tidak yakin ...
Matt Wenham
34

Per The Zen of Python (jika Anda bertanya-tanya apakah kode Anda adalah "Pythonic", itu adalah tempat untuk pergi):

  • Cantik lebih baik dari yang jelek.
  • Eksplisit lebih baik daripada implisit.
  • Sederhana lebih baik daripada kompleks.
  • Flat lebih baik daripada bersarang.
  • Jumlah keterbacaan diperhitungkan.

Cara Pythonic untuk mendapatkan keduanya adalah:sorted intersectionset

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

Atau elemen-elemen yang ada xyztetapi tidak di a:

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

Tetapi untuk loop yang lebih rumit, Anda mungkin ingin meratakannya dengan mengulangi ekspresi generator yang disebut dan / atau memanggil ke fungsi yang telah ditentukan. Mencoba menyesuaikan segala sesuatunya dalam satu baris jarang "Pythonic".


Perbarui komentar tambahan berikut pada pertanyaan Anda dan jawaban yang diterima

Saya tidak yakin apa yang ingin Anda lakukan enumerate, tetapi jika aini adalah kamus, Anda mungkin ingin menggunakan kunci, seperti ini:

>>> 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
sumber
Kedengarannya seperti dari komentar di bawah ini, saya harus mempelajari generator. Saya tidak pernah menggunakannya. Terima kasih. Apakah generator lebih cepat daripada kombinasi setara pernyataan FOR dan IF? Saya juga menggunakan set, tetapi terkadang elemen yang berlebihan dalam daftar adalah informasi yang tidak dapat saya buang.
ChewyChunks
@ChewyChunks: Generator bukan satu-satunya cara untuk menjadi Pythonic!
Johnsyweb
3
@Johnsyweb, jika Anda akan mengutip Zen dari Python: "Seharusnya ada satu - dan lebih disukai hanya satu - cara yang jelas untuk melakukannya."
Wooble
@ Wooble: Harus ada. Saya mengutip bagian itu dalam jawaban saya untuk pertanyaan lain sekitar waktu yang sama!
Johnsyweb
18

Saya pribadi berpikir ini adalah versi tercantik:

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

Edit

jika Anda sangat ingin menghindari penggunaan lambda, Anda dapat menggunakan aplikasi fungsi parsial dan menggunakan modul operator (yang menyediakan fungsi sebagian besar operator).

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
sumber
4
filter(a.__contains__, xyz). Biasanya ketika orang menggunakan lambda, mereka benar-benar membutuhkan sesuatu yang lebih sederhana.
Veky
Saya pikir Anda salah mengerti sesuatu. __contains__adalah metode seperti yang lainnya, hanya metode khusus , artinya dapat dipanggil secara tidak langsung oleh operator ( indalam hal ini). Tapi itu juga bisa disebut langsung, itu adalah bagian dari API publik. Nama-nama pribadi secara khusus didefinisikan memiliki paling banyak satu garis bawah garis, untuk memberikan pengecualian untuk nama metode khusus - dan mereka tunduk pada nama mangling ketika secara leksikal berada dalam cakupan kelas. Lihat docs.python.org/3/reference/datamodel.html#specialnames dan docs.python.org/3.6/tutorial/classes.html#private-variables .
Veky
Tentu saja ok, tetapi dua impor hanya untuk dapat merujuk ke metode yang dapat diakses hanya dengan menggunakan atribut tampak aneh (operator biasanya digunakan ketika pengiriman ganda sangat penting, tetapi insecara terpisah dikirimkan operan yang tepat). Selain itu, perhatikan bahwa operatorjuga mengekspor containsmetode dengan nama __contains__, jadi itu pasti bukan nama pribadi. Saya pikir Anda hanya harus belajar untuk hidup dengan fakta bahwa tidak setiap garis bawah ganda berarti "menjauh". : -]
Veky
Saya pikir Anda lambdaperlu memperbaiki untuk memasukkan not: lambda w: not w in a, xyz
javadba
Filter tampaknya lebih elegan, terutama untuk kondisi kompleks yang akan menjadi fungsi yang didefinisikan alih-alih lambda, mungkin penamaan fungsi lambda akan menambah beberapa keterbacaan, Generator tampak lebih baik ketika elemen yang
diulang
16

Berikut ini adalah penyederhanaan / satu kalimat dari jawaban yang diterima:

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

Perhatikan bahwa generatoritu tetap sejajar . Ini diuji pada python2.7dan python3.6 (perhatikan parens di print;))

javadba
sumber
10

Saya mungkin akan menggunakan:

for x in xyz: 
    if x not in a:
        print x...
Wim Feijen
sumber
@ KirillTitov Ya python adalah bahasa non-fungsional yang pada dasarnya (ini adalah pengkodean imperatif murni - dan saya setuju dengan penulis jawaban ini bahwa itu adalah cara python diatur untuk ditulis. Mencoba menggunakan fungsional mengarah ke buruk membaca atau non- pythonichasil. Saya dapat kode secara fungsional dalam setiap bahasa lain yang saya gunakan (scala, kotlin, javascript, R, swift, ..) tetapi sulit / canggung dengan python
javadba
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
sumber
Sangat Zen, @ lazyr, tetapi tidak akan membantu saya meningkatkan blok kode kompleks yang bergantung pada iterasi melalui satu daftar dan mengabaikan elemen yang cocok dalam daftar lain. Apakah lebih cepat memperlakukan daftar pertama sebagai satu set dan membandingkan gabungan / perbedaan dengan daftar "abaikan" yang bertambah?
ChewyChunks
Coba iniimport 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 jika salah satu daftar berubah selama iterasi, mungkin akan lebih cepat untuk memeriksa setiap elemen terhadap daftar abaikan - kecuali Anda harus membuatnya sebagai set abaikan. Memeriksa untuk keanggotaan di set sangat cepat: if x in ignore: ....
Lauritz V. Thaulow
@ Lazyr Saya baru saja menulis ulang kode saya menggunakan set mengabaikan atas daftar mengabaikan Tampaknya proses waktu jauh lebih lambat. (Agar adil saya membandingkan menggunakan if set(a) - set(ignore) == set([]):jadi mungkin itu sebabnya itu jauh lebih lambat daripada memeriksa keanggotaan. Saya akan menguji ini lagi di masa depan pada contoh yang jauh lebih sederhana daripada apa yang saya tulis.
ChewyChunks
5

Anda dapat menggunakan generator juga, jika ekspresi generator menjadi terlalu terlibat atau kompleks:

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

for x in gen():
    print x
Lauritz V. Thaulow
sumber
Ini sedikit lebih berguna bagi saya. Saya tidak pernah melihat generator. Kedengarannya menakutkan (karena saya melihatnya dalam modul yang umumnya sulit digunakan).
ChewyChunks
2

Gunakan intersectionatauintersection_update

  • persimpangan :

    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)

    maka bjawaban Anda adalah

Chung-Yen Hung
sumber
2

Saya menyukai jawaban Alex , karena filter adalah persis jika diterapkan ke daftar, jadi jika Anda ingin menjelajahi subset daftar yang diberikan syarat, ini sepertinya cara yang paling alami

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)

metode ini berguna untuk pemisahan masalah, jika fungsi kondisinya berubah, satu-satunya kode yang digunakan adalah fungsi itu sendiri

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

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

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

The Generator Metode tampaknya lebih baik bila Anda tidak ingin anggota daftar, tapi modifikasi dari anggota mengatakan, yang tampaknya lebih cocok untuk sebuah pembangkit

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)

Juga, filter bekerja dengan generator, meskipun dalam kasus ini tidak efisien

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)

Tapi tentu saja, tetap menyenangkan menulis seperti ini:

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
sumber
0

Cara sederhana untuk menemukan elemen umum yang unik dari daftar a dan b:

a = [1,2,3]
b = [3,6,2]
for both in set(a) & set(b):
    print(both)
peawormsworth
sumber