Cakupan kelas bertingkat?

116

Saya mencoba memahami ruang lingkup dalam kelas bersarang dengan Python. Ini contoh kode saya:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

Pembuatan kelas tidak selesai dan saya mendapatkan kesalahan:

<type 'exceptions.NameError'>: name 'outer_var' is not defined

Mencoba inner_var = Outerclass.outer_vartidak berhasil. Saya mendapat:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

Saya mencoba mengakses statis outer_vardari InnerClass.

Apakah ada cara untuk melakukan ini?

martineau.dll
sumber

Jawaban:

105
class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

Ini tidak persis sama dengan hal serupa yang berfungsi dalam bahasa lain, dan menggunakan pencarian global alih-alih membatasi akses ke outer_var. (Jika Anda mengubah objek apa yang Outerterikat dengan nama , maka kode ini akan menggunakan objek itu saat dijalankan berikutnya.)

Jika Anda malah ingin semua Innerobjek memiliki referensi ke Outerkarena outer_varsebenarnya adalah atribut instance:

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

    class Inner(object):
        def __init__(self, outer):
            self.outer = outer

        @property
        def inner_var(self):
            return self.outer.outer_var

Perhatikan bahwa kelas bersarang agak tidak umum di Python, dan tidak secara otomatis menyiratkan segala jenis hubungan khusus antara kelas. Anda lebih baik tidak bersarang. (Anda masih dapat menyetel atribut kelas Outerke Inner, jika Anda mau.)

martineau.dll
sumber
2
Mungkin akan membantu untuk menambahkan versi python yang mana jawaban Anda akan berfungsi.
AJ.
1
Saya menulis ini dengan 2.6 / 2.x dalam pikiran, tetapi, melihat itu, saya tidak melihat apa pun yang tidak akan bekerja sama di 3.x.
Saya tidak begitu mengerti apa yang Anda maksud di bagian ini, "(Jika Anda mengubah objek apa yang terikat dengan nama Outer, maka kode ini akan menggunakan objek itu saat dijalankan berikutnya.)" Bisakah Anda membantu saya memahami?
batbrat
1
@batbrat artinya referensi ke Outerselalu dicari lagi setiap kali Anda melakukannya Inner.inner_var. Jadi jika Anda mengembalikan nama Outerke objek baru, Inner.inner_varakan mulai mengembalikan objek baru itu.
Felipe
45

Saya pikir Anda cukup melakukan:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

Masalah yang Anda temui adalah karena ini:

Blok adalah bagian dari teks program Python yang dieksekusi sebagai satu unit. Berikut ini adalah blok-blok: modul, badan fungsi, dan definisi kelas.
(...)
Cakupan mendefinisikan visibilitas nama dalam blok.
(...)
Cakupan nama yang didefinisikan dalam blok kelas terbatas pada blok kelas; itu tidak meluas ke blok kode metode - ini termasuk ekspresi generator karena diimplementasikan menggunakan ruang lingkup fungsi. Artinya, berikut ini akan gagal:

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

Artinya di atas:
badan fungsi adalah blok kode dan metode adalah fungsi, maka nama yang ditentukan dari badan fungsi yang ada dalam definisi kelas tidak meluas ke badan fungsi.

Parafrase ini untuk kasus Anda:
definisi kelas adalah blok kode, maka nama yang ditentukan dari definisi kelas dalam yang ada dalam definisi kelas luar tidak mencakup definisi kelas dalam.

eyquem
sumber
2
Luar biasa. Contoh Anda gagal, mengklaim "nama global 'a' tidak ditentukan". Namun mengganti pemahaman daftar [a + i for i in range(10)]berhasil mengikat Ab ke daftar yang diharapkan [42..51].
George
6
@George Perhatikan bahwa contoh dengan kelas A bukan milik saya, ini dari dokumen resmi Python yang saya berikan tautannya. Contoh ini gagal dan kegagalan itulah yang ingin ditampilkan dalam contoh ini. Bahkan list(a + i for i in range(10))adalah list((a + i for i in range(10)))yang mengatakan list(a_generator). Mereka mengatakan generator diimplementasikan dengan lingkup yang sama dari lingkup fungsi.
eyquem
@George Bagi saya, itu berarti bahwa fungsi bertindak berbeda sesuai jika mereka berada dalam modul atau di kelas. Dalam kasus pertama, sebuah fungsi keluar untuk mencari objek yang diikat ke pengenal gratis. Dalam kasus kedua, sebuah fungsi, yaitu metode, tidak keluar dari tubuhnya. Fungsi dalam modul dan metode dalam kelas pada kenyataannya adalah dua jenis objek. Metode bukan hanya fungsi di kelas. Itu ideku.
eyquem
5
@ George: FWIW, baik list(...)panggilan maupun pemahaman tidak bekerja dengan Python 3. Dokumentasi untuk Py3 juga sedikit berbeda mencerminkan hal ini. Sekarang dikatakan " Ruang lingkup 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 ruang lingkup fungsi. " (Penekanan milik saya ).
martineau
Saya ingin tahu mengapa daftar (Aa + i untuk i dalam kisaran (10)) juga tidak berfungsi, di mana saya menetapkan oleh Aa Saya pikir A mungkin nama global.
gaoxinge
20

Anda mungkin lebih baik jika Anda tidak menggunakan kelas bersarang. Jika Anda harus bersarang, coba ini:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

Atau nyatakan kedua kelas sebelum menumpuknya:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(Setelah ini Anda bisa del InnerClassjika perlu.)

Jason Orendorff
sumber
3

Solusi termudah:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

Ini mengharuskan Anda untuk bersikap eksplisit, tetapi tidak membutuhkan banyak usaha.

Cristian Garcia
sumber
NameError: nama 'OuterClass' tidak ditentukan - -1
Mr_and_Mrs_D
3
Intinya adalah mengaksesnya dari lingkup kelas Luar - ditambah kesalahan serupa akan terjadi jika Anda memanggil ini dari lingkup statis di dalam Luar. Saya sarankan Anda menghapus posting ini
Mr_and_Mrs_D
1

Dalam Python, objek yang bisa berubah diteruskan sebagai referensi, sehingga Anda bisa meneruskan referensi kelas luar ke kelas dalam.

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)
T. Alpers
sumber
2
Harap dicatat bahwa Anda memiliki siklus referensi di sini, dan dalam beberapa contoh skenario kelas ini tidak akan dibebaskan. Salah satu contoh, dengan cPython, jika Anda telah mendefinisikan __del__ metode, pengumpul sampah tidak akan dapat menangani siklus referensi, dan objek akan masuk gc.garbage. Kode di atas, sebagaimana adanya, tidak bermasalah. Cara mengatasinya menggunakan referensi yang lemah . Anda dapat membaca dokumentasi di weakref (2.7) atau weakref (3.5)
guyarad
0

Semua penjelasan dapat ditemukan di Dokumentasi Python Tutorial Python

Untuk kesalahan pertama Anda <type 'exceptions.NameError'>: name 'outer_var' is not defined. Penjelasannya adalah:

Tidak ada singkatan untuk mereferensikan atribut data (atau metode lain!) Dari dalam metode. Saya menemukan bahwa ini benar-benar meningkatkan keterbacaan metode: tidak ada kemungkinan membingungkan variabel lokal dan variabel instan saat melihat sekilas melalui suatu metode.

dikutip dari The Python Tutorial 9.4

Untuk kesalahan kedua Anda <type 'exceptions.NameError'>: name 'OuterClass' is not defined

Ketika definisi kelas dibiarkan normal (melalui akhir), objek kelas dibuat.

dikutip dari Tutorial Python 9.3.1

Jadi ketika Anda mencoba inner_var = Outerclass.outer_var, Quterclassitu belum dibuat, itu sebabnyaname 'OuterClass' is not defined

Penjelasan yang lebih rinci tapi membosankan untuk kesalahan pertama Anda:

Meskipun kelas memiliki akses ke cakupan fungsi yang melingkupi, mereka tidak bertindak sebagai cakupan yang melingkupi kode yang disarangkan di dalam kelas: Python mencari fungsi yang melingkupi nama yang direferensikan, tetapi tidak pernah ada kelas yang melingkupinya. Artinya, kelas adalah cakupan lokal dan memiliki akses ke cakupan lokal yang melingkupi, tetapi tidak berfungsi sebagai cakupan lokal yang melingkupi kode bersarang lebih lanjut.

dikutip dari Learning.Python (5th) .Mark.Lutz

Andy Guo
sumber