Lingkup kelas dan daftar, set atau pemahaman kamus, serta ekspresi generator tidak bercampur.
Itu sebabnya; atau, kata resmi tentang ini
Dalam Python 3, daftar pemahaman diberi ruang lingkup yang tepat (namespace lokal) sendiri, untuk mencegah variabel lokal mereka berdarah ke dalam cakupan sekitarnya (lihat daftar nama-nama rebind pemahaman Python bahkan setelah lingkup pemahaman. Apakah ini benar? ). Itu bagus ketika menggunakan daftar seperti pemahaman dalam modul atau fungsi, tetapi di kelas, pelingkupan sedikit, uhm, aneh .
Ini didokumentasikan dalam pep 227 :
Nama dalam lingkup kelas tidak dapat diakses. Nama diselesaikan di lingkup fungsi terlampir terdalam. Jika definisi kelas terjadi dalam rantai cakupan bersarang, proses resolusi melewatkan definisi kelas.
dan dalam class
dokumentasi pernyataan majemuk :
Rangkaian kelas kemudian dieksekusi dalam bingkai eksekusi baru (lihat bagian Penamaan dan penjilidan ), menggunakan namespace lokal yang baru dibuat dan namespace global asli. (Biasanya, suite hanya berisi definisi fungsi.) Ketika suite kelas menyelesaikan eksekusi, frame eksekusi dibuang tetapi namespace lokalnya disimpan . [4] Objek kelas kemudian dibuat menggunakan daftar warisan untuk kelas dasar dan namespace lokal yang disimpan untuk kamus atribut.
Penekanan milikku; kerangka eksekusi adalah ruang lingkup sementara.
Karena ruang lingkup adalah repurposed sebagai atribut pada objek kelas, yang memungkinkannya untuk digunakan sebagai ruang lingkup nonlokal juga mengarah pada perilaku yang tidak terdefinisi; apa yang akan terjadi jika metode kelas disebut x
sebagai variabel lingkup bersarang, lalu memanipulasi Foo.x
juga, misalnya? Lebih penting lagi, apa artinya bagi subclass Foo
? Python harus memperlakukan ruang lingkup kelas secara berbeda karena sangat berbeda dari ruang lingkup fungsi.
Terakhir, tapi jelas tidak kalah pentingnya, bagian Penamaan dan pengikatan yang tertaut dalam dokumentasi model Eksekusi menyebutkan cakupan kelas secara eksplisit:
Cakupan nama yang didefinisikan dalam blok kelas terbatas pada blok kelas; itu tidak meluas ke blok kode metode - ini termasuk pemahaman dan ekspresi generator karena mereka diimplementasikan menggunakan lingkup fungsi. Ini berarti bahwa yang berikut ini akan gagal:
class A:
a = 42
b = list(a + i for i in range(10))
Jadi, untuk meringkas: Anda tidak dapat mengakses ruang lingkup kelas dari fungsi, daftar pemahaman atau ekspresi generator terlampir dalam ruang lingkup itu; mereka bertindak seolah-olah ruang lingkup itu tidak ada. Dalam Python 2, daftar pemahaman diimplementasikan menggunakan cara pintas, tetapi dalam Python 3 mereka mendapat ruang lingkup fungsi mereka sendiri (seperti yang seharusnya mereka lakukan selama ini) dan dengan demikian contoh Anda rusak. Tipe-tipe pemahaman lain memiliki cakupannya sendiri terlepas dari versi Python, jadi contoh serupa dengan himpunan set atau dict akan pecah di Python 2.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
Pengecualian (kecil); atau, mengapa satu bagian mungkin masih berfungsi
Ada satu bagian dari pemahaman atau ekspresi generator yang dieksekusi di lingkup sekitarnya, terlepas dari versi Python. Itu akan menjadi ekspresi untuk yang terluar iterable. Dalam contoh Anda, ini adalah range(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
Dengan demikian, menggunakan x
dalam ekspresi itu tidak akan menimbulkan kesalahan:
# Runs fine
y = [i for i in range(x)]
Ini hanya berlaku untuk yang terluar iterable; jika pemahaman memiliki beberapa for
klausa, iterables untuk for
klausa dalam dievaluasi dalam lingkup pemahaman:
# NameError
y = [i for i in range(1) for j in range(x)]
Keputusan desain ini dibuat untuk melemparkan kesalahan pada waktu pembuatan genexp alih-alih waktu iterasi ketika membuat iterable yang paling luar dari ekspresi generator yang melempar kesalahan, atau ketika iterable terluar ternyata tidak menjadi iterable. Pemahaman berbagi perilaku ini untuk konsistensi.
Mencari di bawah tenda; atau, jauh lebih detail daripada yang Anda inginkan
Anda dapat melihat semua ini beraksi menggunakan dis
modul . Saya menggunakan Python 3.3 dalam contoh berikut, karena menambahkan nama yang memenuhi syarat yang dengan rapi mengidentifikasi objek kode yang ingin kita periksa. Bytecode yang dihasilkan secara fungsional identik dengan Python 3.2.
Untuk membuat kelas, Python pada dasarnya mengambil seluruh rangkaian yang membentuk tubuh kelas (jadi semuanya menjorok satu tingkat lebih dalam dari class <name>:
garis), dan mengeksekusi seolah-olah itu adalah fungsi:
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
Pertama LOAD_CONST
ada memuat objek kode untuk Foo
tubuh kelas, kemudian membuatnya menjadi fungsi, dan menyebutnya. The hasil dari panggilan yang kemudian digunakan untuk membuat namespace kelas, yang __dict__
. Sejauh ini baik.
Hal yang perlu diperhatikan di sini adalah bahwa bytecode berisi objek kode bersarang; dalam Python, definisi kelas, fungsi, pemahaman, dan generator semuanya direpresentasikan sebagai objek kode yang tidak hanya berisi bytecode, tetapi juga struktur yang mewakili variabel lokal, konstanta, variabel yang diambil dari global, dan variabel yang diambil dari lingkup bersarang. Bytecode yang dikompilasi mengacu pada struktur-struktur tersebut dan interpreter python tahu bagaimana mengakses yang diberikan bytecode yang disajikan.
Yang penting untuk diingat di sini adalah bahwa Python membuat struktur ini pada waktu kompilasi; yang class
suite benda kode ( <code object Foo at 0x10a436030, file "<stdin>", line 2>
) yang sudah disusun.
Mari kita periksa objek kode yang membuat tubuh kelas itu sendiri; objek kode memiliki co_consts
struktur:
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
Bytecode di atas menciptakan tubuh kelas. Fungsi dieksekusi dan locals()
namespace yang dihasilkan , berisi x
dan y
digunakan untuk membuat kelas (kecuali bahwa itu tidak berfungsi karena x
tidak didefinisikan sebagai global). Perhatikan bahwa setelah menyimpan 5
di x
, itu beban kode objek lain; itulah pemahaman daftar; itu dibungkus dalam objek fungsi seperti tubuh kelas itu; fungsi yang dibuat mengambil argumen posisi, range(1)
iterable untuk digunakan untuk kode pengulangan, dilemparkan ke sebuah iterator. Seperti yang ditunjukkan dalam bytecode, range(1)
dievaluasi dalam lingkup kelas.
Dari sini Anda dapat melihat bahwa satu-satunya perbedaan antara objek kode untuk fungsi atau generator, dan objek kode untuk pemahaman adalah bahwa yang terakhir dieksekusi segera ketika objek kode induk dijalankan; bytecode hanya menciptakan fungsi dengan cepat dan menjalankannya dalam beberapa langkah kecil.
Python 2.x menggunakan inline bytecode sebagai gantinya, berikut adalah output dari Python 2.7:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
Tidak ada objek kode yang dimuat, sebaliknya sebuah FOR_ITER
loop dijalankan inline. Jadi dalam Python 3.x, generator daftar diberi objek kode yang tepat sendiri, yang artinya memiliki ruang lingkup sendiri.
Namun, pemahaman dikompilasi bersama-sama dengan sisa kode sumber python ketika modul atau skrip pertama kali dimuat oleh interpreter, dan kompiler tidak menganggap suite kelas lingkup yang valid. Setiap variabel yang direferensikan dalam pemahaman daftar harus melihat dalam lingkup seputar definisi kelas, secara rekursif. Jika variabel tidak ditemukan oleh kompiler, itu menandainya sebagai global. Pembongkaran objek kode pemahaman daftar menunjukkan bahwa x
memang dimuat sebagai global:
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Potongan bytecode ini memuat argumen pertama yang dilewatkan ( range(1)
iterator), dan seperti versi Python 2.x yang digunakan FOR_ITER
untuk mengulanginya dan membuat outputnya.
Seandainya kita mendefinisikan x
dalam foo
fungsi sebagai gantinya, x
akan menjadi variabel sel (sel merujuk pada cakupan bersarang):
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
The LOAD_DEREF
secara tidak langsung akan memuat x
dari benda sel kode objek:
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
Referensi aktual terlihat nilainya dari struktur data frame saat ini, yang diinisialisasi dari .__closure__
atribut objek fungsi . Karena fungsi yang dibuat untuk objek kode pemahaman dibuang lagi, kita tidak bisa memeriksa penutupan fungsi itu. Untuk melihat penutupan dalam aksi, kita harus memeriksa fungsi bersarang sebagai gantinya:
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
Jadi, untuk meringkas:
- Daftar pemahaman mendapatkan objek kode mereka sendiri di Python 3, dan tidak ada perbedaan antara objek kode untuk fungsi, generator atau pemahaman; objek kode pemahaman dibungkus dalam objek fungsi sementara dan dipanggil segera.
- Objek kode dibuat pada waktu kompilasi, dan variabel non-lokal ditandai sebagai variabel global atau bebas, berdasarkan cakupan kode yang bersarang. Badan kelas tidak dianggap sebagai ruang lingkup untuk mencari variabel-variabel tersebut.
- Saat mengeksekusi kode, Python hanya perlu melihat ke dalam global, atau penutupan objek yang sedang dieksekusi. Karena kompiler tidak menyertakan tubuh kelas sebagai ruang lingkup, fungsi sementara namespace tidak dipertimbangkan.
Penanganan masalah; atau, apa yang harus dilakukan?
Jika Anda membuat ruang lingkup eksplisit untuk x
variabel, seperti dalam suatu fungsi, Anda bisa menggunakan variabel ruang lingkup untuk pemahaman daftar:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
Fungsi 'sementara' y
dapat dipanggil langsung; kita menggantinya ketika kita lakukan dengan nilai pengembaliannya. Ruang lingkup yang dipertimbangkan ketika menyelesaikan x
:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
Tentu saja, orang yang membaca kode Anda akan menggaruk-garuk kepala ini sedikit; Anda mungkin ingin memberikan komentar besar di sana menjelaskan mengapa Anda melakukan ini.
Cara terbaik untuk menyelesaikannya adalah dengan hanya menggunakan __init__
untuk membuat variabel instan sebagai gantinya:
def __init__(self):
self.y = [self.x for i in range(1)]
dan hindari semua yang menggaruk kepala, dan pertanyaan untuk menjelaskan diri sendiri. Sebagai contoh konkret Anda sendiri, saya bahkan tidak akan menyimpannya namedtuple
di kelas; baik menggunakan output secara langsung (jangan menyimpan kelas yang dihasilkan sama sekali), atau menggunakan global:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
menggunakan Python 3.2 dan 3.3 yang saya harapkan.