Pertanyaan Mengapa daftar pemahaman menulis ke variabel loop, tetapi generator tidak? [duplikat]


Pertanyaan ini sudah memiliki jawaban di sini:

Jika saya melakukan sesuatu dengan pemahaman daftar, itu menulis ke variabel lokal:

i = 0
test = any([i == 2 for i in xrange(10)])
print i

Ini mencetak "9". Namun, jika saya menggunakan generator, itu tidak menulis ke variabel lokal:

i = 0
test = any(i == 2 for i in xrange(10))
print i

Ini mencetak "0".

Apakah ada alasan bagus untuk perbedaan ini? Apakah ini keputusan desain, atau hanya produk sampingan acak dari cara generator dan daftar pemahaman diterapkan? Secara pribadi, tampaknya lebih baik bagi saya jika pemahaman daftar tidak menulis ke variabel lokal.


76
2017-11-07 22:32


asal


Jawaban:


Pencipta Python, Guido van Rossum, menyebutkan ini ketika dia menulis tentang ekspresi generator yang seragam dibangun ke Python 3: (penekanan saya)

Kami juga membuat perubahan lain dalam Python 3, untuk meningkatkan kesetaraan antara pemahaman daftar dan ekspresi generator. Dalam Python 2, daftar pemahaman "bocor" variabel kontrol loop ke lingkup sekitarnya:

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'

Ini adalah artefak dari implementasi asli dari pemahaman daftar; itu adalah salah satu "rahasia kecil kotor" Python selama bertahun-tahun. Ini dimulai sebagai kompromi yang disengaja untuk membuat daftar pemahaman menyilaukan cepat, dan sementara itu bukan perangkap umum untuk pemula, itu pasti menyengat orang kadang-kadang. Untuk ekspresi generator kami tidak bisa melakukan ini. Ekspresi generator diimplementasikan menggunakan generator, yang pelaksanaannya membutuhkan kerangka eksekusi terpisah. Dengan demikian, ekspresi generator (terutama jika mereka iterate atas urutan pendek) kurang efisien daripada pemahaman daftar.

Namun, dengan Python 3, kami memutuskan untuk memperbaiki "rahasia kecil yang kotor" dari daftar pemahaman dengan menggunakan strategi implementasi yang sama seperti untuk ekspresi generator. Jadi, dalam Python 3, contoh di atas (setelah modifikasi untuk menggunakan cetak (x) :-) akan mencetak 'sebelum', membuktikan bahwa 'x' dalam daftar pemahaman bayangan sementara tetapi tidak mengesampingkan 'x' di sekitarnya cakupan.

Jadi dengan Python 3 Anda tidak akan melihat ini terjadi lagi.

Menariknya, pemahaman dict di Python 2 jangan lakukan ini juga; Hal ini sebagian besar karena pemahaman dimengerti didukung oleh Python 3 dan karena itu sudah memiliki perbaikan di dalamnya.

Ada beberapa pertanyaan lain yang membahas topik ini juga, tetapi saya yakin Anda telah melihat itu ketika Anda mencari topik, bukan? ;)


74
2017-11-07 22:38



Sebagai PEP 289 (Ekspresi Generator) menjelaskan:

Variabel loop (jika variabel sederhana atau tupel variabel sederhana) tidak terkena fungsi di sekitarnya. Ini memfasilitasi implementasi dan membuat kasus penggunaan umum lebih dapat diandalkan.

Tampaknya telah dilakukan untuk alasan implementasi.

Secara pribadi, tampaknya lebih baik bagi saya jika pemahaman daftar tidak menulis ke variabel lokal.

PEP 289 menjelaskan ini juga:

Daftar pemahaman juga "bocor" variabel loop mereka ke lingkup sekitarnya. Ini juga akan berubah dengan Python 3.0, sehingga definisi semantik dari daftar pemahaman dalam Python 3.0 akan setara dengan daftar ().

Dengan kata lain, perilaku yang Anda jelaskan berbeda dalam Python 2 tetapi telah diperbaiki dengan Python 3.


16
2017-11-07 22:35



Secara pribadi, tampaknya lebih baik bagi saya jika pemahaman daftar tidak menulis ke variabel lokal.

Anda benar. Ini diperbaiki dengan Python 3.x. Perilaku tidak berubah dalam 2.x sehingga tidak mempengaruhi kode yang ada yang (ab) menggunakan lubang ini.


9
2017-11-07 22:35



Karena ... karena.

Tidak, itu saja. Alur implementasi. Dan bisa dibilang bug, karena itu diperbaiki dengan Python 3.


4
2017-11-07 22:35



Sebagai produk sampingan dari mengembara bagaimana daftar-pemahaman benar-benar diterapkan, saya menemukan jawaban yang bagus untuk pertanyaan Anda.

Dalam Python 2, lihat kode byte yang dihasilkan untuk pemahaman daftar sederhana:

>>> s = compile('[i for i in [1, 2, 3]]', '', 'exec')
>>> dis(s)
  1           0 BUILD_LIST               0
              3 LOAD_CONST               0 (1)
              6 LOAD_CONST               1 (2)
              9 LOAD_CONST               2 (3)
             12 BUILD_LIST               3
             15 GET_ITER            
        >>   16 FOR_ITER                12 (to 31)
             19 STORE_NAME               0 (i)
             22 LOAD_NAME                0 (i)
             25 LIST_APPEND              2
             28 JUMP_ABSOLUTE           16
        >>   31 POP_TOP             
             32 LOAD_CONST               3 (None)
             35 RETURN_VALUE  

itu pada dasarnya diterjemahkan menjadi sederhana for-loop, itulah gula sintaksis untuk itu. Akibatnya, semantik yang sama seperti untuk for-loops menerapkan:

a = []
for i in [1, 2, 3]
    a.append(i)
print(i) # 3 leaky

Dalam kasus daftar-pemahaman, (C) Python menggunakan "nama daftar tersembunyi" dan instruksi khusus LIST_APPEND untuk menangani ciptaan tetapi benar-benar tidak lebih dari itu.

Jadi pertanyaan Anda harus menggeneralisasi mengapa Python menulis ke variabel loop untuk di for-loops; yang dijawab dengan baik oleh posting blog dari Eli Bendersky.

Python 3, sebagaimana disebutkan dan oleh orang lain, telah mengubah semantik daftar-pemahaman untuk lebih cocok dengan generator (dengan membuat objek kode terpisah untuk pemahaman) dan pada dasarnya adalah gula sintaksis sebagai berikut:

a = [i for i in [1, 2, 3]]

# equivalent to
def __f(it):
    _ = []
    for i in it
        _.append(i)
    return _
a = __f([1, 2, 3])

ini tidak akan bocor karena tidak berjalan di ruang lingkup paling atas seperti yang dilakukan oleh Python 2. Itu i bocor, hanya di __f dan kemudian dihancurkan sebagai variabel lokal ke fungsi itu.

Jika Anda ingin, lihat kode byte yang dihasilkan untuk Python 3 oleh berlari dis('a = [i for i in [1, 2, 3]]'). Anda akan melihat bagaimana objek kode "tersembunyi" dimuat dan kemudian panggilan fungsi dibuat pada akhirnya.


1
2018-02-26 02:26



Salah satu konsekuensi halus dari rahasia kotor yang dijelaskan oleh poke di atas, adalah itu list(...) dan [...] tidak memiliki efek samping yang sama pada Python 2:

In [1]: a = 'Before'
In [2]: list(a for a in range(5))
In [3]: a
Out[3]: 'Before'

Jadi tidak ada efek samping untuk ekspresi generator di dalam daftar-konstruktor, tetapi efek sampingnya ada dalam daftar-pemahaman langsung:

In [4]: [a for a in range(5)]
In [5]: a
Out[5]: 4

0
2017-10-28 10:51