Bagaimana Anda mengakses variabel kelas lain dari pemahaman daftar dalam definisi kelas? Berikut ini berfungsi di Python 2 tetapi gagal dalam Python 3:
class Foo:
x = 5
y = [x for i in range(1)]
Python 3.2 memberikan kesalahan:
NameError: global name 'x' is not defined
Mencoba Foo.x
juga tidak berhasil. Ada ide tentang cara melakukan ini di Python 3?
Contoh motivasi yang sedikit lebih rumit:
from collections import namedtuple
class StateDatabase:
State = namedtuple('State', ['name', 'capital'])
db = [State(*args) for args in [
['Alabama', 'Montgomery'],
['Alaska', 'Juneau'],
# ...
]]
Dalam contoh ini, apply()
akan menjadi solusi yang layak, tetapi sayangnya dihapus dari Python 3.
python
python-3.x
scope
list-comprehension
python-internals
Mark Lodato
sumber
sumber
NameError: global name 'x' is not defined
menggunakan Python 3.2 dan 3.3 yang saya harapkan.Jawaban:
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 :
dan dalam
class
dokumentasi pernyataan majemuk :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 memanipulasiFoo.x
juga, misalnya? Lebih penting lagi, apa artinya bagi subclassFoo
? 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:
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.
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)
:Dengan demikian, menggunakan
x
dalam ekspresi itu tidak akan menimbulkan kesalahan:Ini hanya berlaku untuk yang terluar iterable; jika pemahaman memiliki beberapa
for
klausa, iterables untukfor
klausa dalam dievaluasi dalam lingkup pemahaman: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:Pertama
LOAD_CONST
ada memuat objek kode untukFoo
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:Bytecode di atas menciptakan tubuh kelas. Fungsi dieksekusi dan
locals()
namespace yang dihasilkan , berisix
dany
digunakan untuk membuat kelas (kecuali bahwa itu tidak berfungsi karenax
tidak didefinisikan sebagai global). Perhatikan bahwa setelah menyimpan5
dix
, 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:
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:Potongan bytecode ini memuat argumen pertama yang dilewatkan (
range(1)
iterator), dan seperti versi Python 2.x yang digunakanFOR_ITER
untuk mengulanginya dan membuat outputnya.Seandainya kita mendefinisikan
x
dalamfoo
fungsi sebagai gantinya,x
akan menjadi variabel sel (sel merujuk pada cakupan bersarang):The
LOAD_DEREF
secara tidak langsung akan memuatx
dari benda sel kode objek: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:Jadi, untuk meringkas:
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:Fungsi 'sementara'
y
dapat dipanggil langsung; kita menggantinya ketika kita lakukan dengan nilai pengembaliannya. Ruang lingkup yang dipertimbangkan ketika menyelesaikanx
: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: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:sumber
y = (lambda x=x: [x for i in range(1)])()
lambda
itu hanya fungsi anonim.Menurut pendapat saya itu adalah cacat dalam Python 3. Saya harap mereka mengubahnya.
Old Way (bekerja di 2.7, melempar
NameError: name 'x' is not defined
3+):CATATAN: hanya dengan scoping tidak
A.x
akan menyelesaikannyaCara Baru (berfungsi dalam 3+):
Karena sintaksnya sangat jelek, saya hanya menginisialisasi semua variabel kelas saya di konstruktor
sumber
def
untuk membuat fungsi).python -c "import IPython;IPython.embed()"
. Jalankan IPython secara langsung menggunakan katakanipython
dan masalahnya akan hilang.Jawaban yang diterima memberikan informasi yang sangat baik, tetapi tampaknya ada beberapa kerutan di sini - perbedaan antara pemahaman daftar dan ekspresi generator. Demo yang saya mainkan:
sumber
Ini adalah bug di Python. Pemahaman diiklankan sebagai setara dengan untuk loop, tetapi ini tidak benar di kelas. Setidaknya hingga Python 3.6.6, dalam pemahaman yang digunakan dalam kelas, hanya satu variabel dari luar pemahaman yang dapat diakses di dalam pemahaman, dan itu harus digunakan sebagai iterator terluar. Dalam suatu fungsi, batasan ruang lingkup ini tidak berlaku.
Untuk mengilustrasikan mengapa ini bug, mari kita kembali ke contoh aslinya. Ini gagal:
Tapi ini berhasil:
Batasan tersebut dinyatakan di akhir bagian ini dalam panduan referensi.
sumber
Karena iterator terluar dievaluasi dalam lingkup sekitar kita dapat menggunakan
zip
bersama-samaitertools.repeat
untuk membawa dependensi ke ruang lingkup pemahaman:Satu juga dapat menggunakan
for
loop bersarang dalam pemahaman dan menyertakan dependensi di terluar iterable:Untuk contoh spesifik OP:
sumber