Apakah Pythonic menggunakan pemahaman daftar hanya untuk efek samping?

108

Pikirkan tentang fungsi yang saya panggil untuk efek sampingnya, bukan nilai yang dikembalikan (seperti mencetak ke layar, memperbarui GUI, mencetak ke file, dll.).

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

Sekarang, apakah Pythonic menggunakan pemahaman daftar untuk memanggil fungsi ini:

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

Perhatikan bahwa saya tidak menyimpan daftar di mana pun

Atau haruskah saya menyebut fungsi ini seperti ini:

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

Mana yang lebih baik dan mengapa?

sinan
sumber
6
ini adalah batas, tetapi Anda mungkin akan mendapatkan lebih banyak penolakan daripada dukungan. Saya akan duduk yang ini: ^)
jcomeau_ictx
6
Ini adalah pilihan yang mudah. Keterbacaan penting - lakukan dengan cara kedua. Jika Anda tidak dapat memasukkan 2 baris ekstra pada layar Anda, dapatkan monitor yang lebih besar :)
John La Rooy
1
Pemahaman daftar adalah unpythonic karena melanggar "eksplisit lebih baik daripada implisit" - Anda menyembunyikan perulangan dalam konstruksi yang berbeda.
Fred Foo
3
@larsmans: seandainya GvR menyadari hal itu ketika dia memperkenalkan pemahaman daftar di tempat pertama!
Steve Jessop
2
@larsmans, Steve Jessop, menurut saya tidak benar untuk memahami pemahaman daftar sebagai satu lingkaran. Ini mungkin diimplementasikan sebagai loop, tetapi inti dari konstruksi seperti ini adalah untuk beroperasi pada data agregat secara fungsional dan (secara konseptual) paralel. Jika ada masalah dengan sintaks, itu yang for ... indigunakan dalam kedua kasus - mengarah ke pertanyaan seperti ini!
pengirim

Jawaban:

84

Sangat anti-Pythonic untuk melakukannya, dan Pythonista berpengalaman apa pun akan membuat Anda kesal. Daftar perantara dibuang setelah dibuat, dan berpotensi menjadi sangat, sangat besar, dan karena itu mahal untuk dibuat.

Ignacio Vazquez-Abrams
sumber
5
Jadi apa cara yang lebih pythonic?
Joachim Sauer
6
Salah satu yang tidak menyimpan daftarnya; yaitu beberapa varian dari cara kedua (saya telah dikenal menggunakan genex di forsebelumnya, untuk menyingkirkan if).
Ignacio Vazquez-Abrams
6
@Joachim Sauer: Contoh 2 di atas. Sebuah loop yang tepat, eksplisit, non-daftar-pemahaman. Eksplisit. Bersih. Jelas.
S. Lott
31

Anda tidak boleh menggunakan pemahaman daftar , karena seperti yang dikatakan orang-orang itu akan membangun daftar sementara yang besar yang tidak Anda perlukan. Dua metode berikut ini setara:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

dengan definisi consumedari itertoolshalaman manual:

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)

Tentu saja, yang terakhir lebih jelas dan lebih mudah dimengerti.

Katriel
sumber
@ Paul: Saya pikir seharusnya begitu. Dan memang Anda bisa, meskipun mapmungkin tidak seintuitif jika seseorang belum pernah melakukan pemrograman fungsional sebelumnya.
Katriel
4
Tidak yakin ini sangat idiomatis. Tidak ada keuntungan menggunakan loop eksplisit.
Marcin
1
Solusinya adalahconsume = collections.deque(maxlen=0).extend
PaulMcG
24

Pemahaman daftar adalah untuk membuat daftar. Dan kecuali Anda benar-benar membuat daftar, Anda tidak boleh menggunakan pemahaman daftar.

Jadi saya akan mendapatkan opsi kedua, hanya mengulang-ulang daftar dan kemudian memanggil fungsi ketika kondisi berlaku.

Ikke
sumber
6
Saya akan melangkah lebih jauh dan menyatakan bahwa efek samping dalam pemahaman daftar tidak biasa, tidak terduga, dan karena itu jahat, bahkan jika Anda menggunakan daftar yang dihasilkan ketika Anda selesai.
Mark Ransom
11

Kedua lebih baik.

Pikirkan orang yang perlu memahami kode Anda. Anda bisa mendapatkan karma buruk dengan mudah dengan yang pertama :)

Anda bisa pergi ke tengah di antara keduanya dengan menggunakan filter (). Perhatikan contohnya:

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)
pengguna237419
sumber
10
Lambda Anda jauh lebih baik ditulis sebagai lambda x : x > 3.
PaulMcG
Anda bahkan tidak membutuhkan filter. Hanya menempatkan ekspresi generator parens di sini: for el in (x for x in y if x > 3):. eldan xbisa memiliki nama yang sama, tapi itu mungkin membingungkan orang.
Omnifarious
3

Tergantung pada tujuan Anda.

Jika Anda mencoba melakukan beberapa operasi pada setiap objek dalam daftar, pendekatan kedua harus diterapkan.

Jika Anda mencoba membuat daftar dari daftar lain, Anda dapat menggunakan pemahaman daftar.

Eksplisit lebih baik daripada implisit. Sederhana lebih baik daripada kompleks. (Python Zen)

rubayeet
sumber
0

Anda dapat melakukan

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

tapi itu tidak terlalu cantik.

sigs
sumber
-1

Menggunakan pemahaman daftar untuk efek sampingnya jelek, non-Pythonic, tidak efisien, dan saya tidak akan melakukannya. Saya akan menggunakan forloop sebagai gantinya, karena forloop menandakan gaya prosedural di mana efek samping itu penting.

Tapi, jika Anda benar-benar bersikeras menggunakan pemahaman daftar untuk efek sampingnya, Anda harus menghindari inefisiensi dengan menggunakan ekspresi generator sebagai gantinya. Jika Anda benar-benar bersikeras pada gaya ini, lakukan salah satu dari dua berikut ini:

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

atau:

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

Ini adalah ekspresi generator, dan mereka tidak menghasilkan daftar acak yang dibuang. saya pikirall bentuknya mungkin sedikit lebih jelas, meskipun menurut saya keduanya membingungkan dan tidak boleh digunakan.

Saya pikir ini jelek dan saya tidak akan benar-benar melakukannya dalam kode. Tetapi jika Anda bersikeras menerapkan loop Anda dengan cara ini, begitulah cara saya melakukannya.

Saya cenderung merasa bahwa pemahaman daftar dan sejenisnya harus menandakan upaya untuk menggunakan sesuatu yang setidaknya mirip dengan gaya fungsional. Menempatkan hal-hal dengan efek samping yang merusak asumsi itu akan menyebabkan orang harus membaca kode Anda lebih hati-hati, dan saya pikir itu hal yang buruk.

Beraneka ragam
sumber
Bagaimana jika fun_with_side_effectsmengembalikan True?
Katriel
7
Saya pikir obat ini lebih buruk daripada penyakitnya - itertools.consume jauh lebih bersih.
PaulMcG
@PaulMcG - sudah itertools.consumetidak ada lagi, mungkin karena menggunakan pemahaman dengan efek samping jelek.
Omnifarious
1
Ternyata saya salah, dan itu tidak pernah ada sebagai metode di stdlib. Ini adalah resep di dokumen itertools: docs.python.org/3/library/…
PaulMcG