Bagaimana cara mengakses kelas luar dari kelas dalam?


102

Saya memiliki situasi seperti ini ...

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.Outer.some_method()    # <-- this is the line in question

Bagaimana cara mengakses Outermetode Innerkelas dari kelas?


Mengapa kau melakukan ini? Apa yang salah dengan hubungan rekan yang sederhana? Apakah Anda mencoba untuk "menyembunyikan" sesuatu?
S. Lotot

1
Contoh skenario ini bisa jadi memiliki kelas dengan sub-kelas yang perlu mengakses kelas luar, seperti biasa, tetapi kemudian perlu membuat kelas lain (tingkat atas) yang diturunkan dari kelas pertama. Dalam hal ini, sub-kelas dari kelas kedua akan mencoba mengakses induknya menggunakan self.<original_parent_name>dan mendapatkan kelas aslinya, bukan kelas baru tempat mereka menjadi sub-kelas . Saya berharap orang-orang yang membaca ini dapat memvisualisasikan skenario yang sulit ini dan melihat inti dari pertanyaan seperti ini.
Edward

1
Gandakan ke stackoverflow.com/questions/2278426 dan ini memiliki jawaban yang bagus.
xmedeko

Jawaban:


68

Metode kelas bersarang tidak bisa langsung mengakses atribut instance dari kelas luar.

Perhatikan bahwa tidak selalu terjadi bahwa instance dari kelas luar ada bahkan ketika Anda telah membuat instance dari kelas dalam.

Faktanya, sering kali direkomendasikan untuk tidak menggunakan kelas bersarang, karena penumpukan tidak menyiratkan hubungan tertentu antara kelas dalam dan luar.


1
Hmm, Python lebih lincah dari pada Java / C ++ ... lihat jawaban saya di bawah ini. Jika kita membelah rambut, yang biasanya kita lakukan, saya tidak bisa benar-benar memberi tahu Anda apakah "kelas bertingkat dalam metode" saya dihitung sebagai kelas dalam. Namun, pada titik ini, saya harus memanggil bebek mengetik: jika itu melakukan semua yang mungkin dilakukan oleh kelas batin ... dari sudut pandang Pythonic, mungkin inilah saatnya untuk bosan dengan rambut yang membelah
mike rodent

21
Kelas dalam tentu saja menyiratkan hubungan dengan kelas luar, biasanya berkaitan dengan cakupan penggunaan kelas dalam yang tersirat atau namespace organisasi.
Acumenus

67

Anda mencoba untuk mengakses contoh kelas Luar, dari contoh kelas dalam. Jadi gunakan saja metode pabrik untuk membuat instance Inner dan meneruskan instance Outer ke sana.

class Outer(object):

    def createInner(self):
        return Outer.Inner(self)

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

        def inner_method(self):
            self.outer_instance.anothermethod()

Ini adalah jawaban yang tepat - harus dipilih sebagai jawaban yang benar karena memberikan solusi untuk pertanyaan OP - terima kasih banyak!
Daniel

29

mungkin aku marah tapi ini nampaknya memang sangat mudah - masalahnya adalah membuat kelas dalammu di dalam metode kelas luar ...

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

Plus ... "self" hanya digunakan oleh konvensi, jadi Anda dapat melakukan ini:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

Mungkin ada keberatan bahwa Anda tidak dapat membuat kelas dalam ini dari luar kelas luar ... tapi ini tidak benar:

class Bumblebee():

    def do_sthg( self ):
        print "sthg"

    def giveMeAnInnerClass( outerClassSelf ):

        class mooble():
            def do_sthg_different( self ):
                print "something diff\n"
                outerClassSelf.do_sthg()
        return mooble

kemudian, di suatu tempat bermil-mil jauhnya:

blob = Bumblebee().giveMeAnInnerClass()()
blob.do_sthg_different()    

bahkan mendorong perahu keluar sedikit dan memperluas kelas dalam ini (NB untuk mendapatkan super () untuk bekerja Anda harus mengubah tanda tangan kelas dari mooble menjadi "kelas mooble (objek)"

class InnerBumblebeeWithAddedBounce( Bumblebee().giveMeAnInnerClass() ):
    def bounce( self ):
        print "bounce"

    def do_sthg_different( self ):
        super( InnerBumblebeeWithAddedBounce, self ).do_sthg_different()
        print "and more different"


ibwab = InnerBumblebeeWithAddedBounce()    
ibwab.bounce()
ibwab.do_sthg_different()

kemudian

mrh1997 mengangkat poin yang menarik tentang warisan non-umum dari kelas dalam yang disampaikan menggunakan teknik ini. Tetapi tampaknya solusinya cukup mudah:

class Fatty():
    def do_sthg( self ):
        pass

    class InnerFatty( object ):
        pass

    def giveMeAnInnerFattyClass(self):
        class ExtendedInnerFatty( Fatty.InnerFatty ):
            pass
        return ExtendedInnerFatty

fatty1 = Fatty()
fatty2 = Fatty()

innerFattyClass1 = fatty1.giveMeAnInnerFattyClass()
innerFattyClass2 = fatty2.giveMeAnInnerFattyClass()

print ( issubclass( innerFattyClass1, Fatty.InnerFatty ))
print ( issubclass( innerFattyClass2, Fatty.InnerFatty ))

1
Ini berhasil untuk saya. Apa tepatnya konstruksi ini? Fungsi pabrik? Penutupan?
fanatik telanjang

Saya belum tahu apa namanya ... tapi bolehkah saya menyarankan bahwa alasan mengapa poster lain tidak melihat ini adalah karena mungkin tidak sepenuhnya dihargai bahwa sebagian besar hal dalam Python tidak suci, termasuk "diri sendiri "(nama sewenang-wenang) dan kelas - mereka adalah" objek kelas satu ", yang tampaknya berarti Anda dapat memanipulasinya dengan cara yang sangat keterlaluan
mike rodent

Kerja bagus. Terinspirasi oleh ide solusi ini dan sedikit pemikiran, saya memperluas bagian dari jawaban ini untuk membuat jawaban lain di bawah ini dengan beberapa penjelasan lagi.
Edward

1
@mikerodent Karena atas komentar Anda (atas jawaban saya), saya sekarang telah mengedit jawaban saya untuk menghapus deskripsi "wiki komunitas". Seperti Anda, saya tidak memiliki pengetahuan tentang jawaban "wiki komunitas", jadi ada baiknya Anda memperbaiki kesalahan Anda.
Edward

@mikerodent Juga, jangan tersinggung, tapi menurut saya jawaban "wiki komunitas" tidak memiliki tag "wiki komunitas", mereka pasti hanya berupa jenis jawaban. Mungkin akan ada halaman bantuan dengan deskripsi jawaban "wiki komunitas" di Stack Overflow jika Anda tertarik.
Edward

4

Apakah Anda bermaksud menggunakan warisan, daripada kelas bertingkat seperti ini? Apa yang Anda lakukan tidak masuk akal dengan Python.

Anda dapat mengakses Outersome_method dengan hanya mereferensikan Outer.some_methoddi dalam metode kelas dalam, tetapi itu tidak akan bekerja seperti yang Anda harapkan. Misalnya, jika Anda mencoba ini:

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            Outer.some_method()

... Anda akan mendapatkan TypeError saat menginisialisasi Innerobjek, karena Outer.some_methodmengharapkan untuk menerima sebuah Outerinstance sebagai argumen pertamanya. (Dalam contoh di atas, pada dasarnya Anda mencoba memanggil some_methodsebagai metode kelas Outer.)


1
Alasan mengapa ini mungkin tidak masuk akal adalah karena saya sengaja hacky. Menambahkan metode kustom ke QuerySet di Django memerlukan sedikit kode boilerplate, dan saya mencoba mendapatkan cara cerdas untuk melakukannya menggunakan python yang memungkinkan saya untuk membuat template kode boilerplate dan cukup menulis bagian terkait dalam kode Model saya.
T. Stone

Maaf - Saya tidak tahu Django dan karenanya tidak dapat menyarankan cara untuk membuat template kode boilerplate, tetapi Anda mungkin menggonggong pohon yang salah dalam mencoba menyusun kelas Anda. Kelas Batin Anda tidak memperoleh apa pun dari kelas Luar Anda. Semua yang dilakukan bersarang di dalam Luar adalah memaksa Anda untuk mengaksesnya melalui Luar.Inner, bukan hanya Inner polos.
zenbot

3

Anda dapat dengan mudah mengakses ke kelas luar menggunakan metaclass: setelah pembuatan kelas luar periksa atribut itu dikt untuk kelas apa pun (atau terapkan logika apa pun yang Anda butuhkan - milik saya hanyalah contoh sepele) dan mengatur nilai yang sesuai:

import six
import inspect


# helper method from `peewee` project to add metaclass
_METACLASS_ = '_metaclass_helper_'
def with_metaclass(meta, base=object):
    return meta(_METACLASS_, (base,), {})


class OuterMeta(type):
    def __new__(mcs, name, parents, dct):
        cls = super(OuterMeta, mcs).__new__(mcs, name, parents, dct)
        for klass in dct.values():
            if inspect.isclass(klass):
                print("Setting outer of '%s' to '%s'" % (klass, cls))
                klass.outer = cls

        return cls


# @six.add_metaclass(OuterMeta) -- this is alternative to `with_metaclass`
class Outer(with_metaclass(OuterMeta)):
    def foo(self):
        return "I'm outer class!"

    class Inner(object):
        outer = None  # <-- by default it's None

        def bar(self):
            return "I'm inner class"


print(Outer.Inner.outer)
>>> <class '__main__.Outer'>
assert isinstance(Outer.Inner.outer(), Outer)

print(Outer().foo())
>>> I'm outer class!
print(Outer.Inner.outer().foo())
>>> I'm outer class!
print(Outer.Inner().outer().foo())
>>> I'm outer class!
print(Outer.Inner().bar())
>>> I'm inner class!

Dengan menggunakan pendekatan ini, Anda dapat dengan mudah mengikat dan merujuk dua kelas satu sama lain.


3

Saya telah membuat beberapa kode Python untuk menggunakan kelas luar dari kelas dalam , berdasarkan ide bagus dari jawaban lain untuk pertanyaan ini. Menurut saya ini singkat, sederhana dan mudah dimengerti.

class higher_level__unknown_irrelevant_name__class:
    def __init__(self, ...args...):
        ...other code...
        # Important lines to access sub-classes.
        subclasses = self._subclass_container()
        self.some_subclass = subclasses["some_subclass"]
        del subclasses # Free up variable for other use.

    def sub_function(self, ...args...):
        ...other code...

    def _subclass_container(self):
        _parent_class = self # Create access to parent class.
        class some_subclass:
            def __init__(self):
                self._parent_class = _parent_class # Easy access from self.
                # Optional line, clears variable space, but SHOULD NOT BE USED
                # IF THERE ARE MULTIPLE SUBCLASSES as would stop their parent access.
                #  del _parent_class
        class subclass_2:
            def __init__(self):
                self._parent_class = _parent_class
        # Return reference(s) to the subclass(es).
        return {"some_subclass": some_subclass, "subclass_2": subclass_2}

Kode utama, "produksi siap" (tanpa komentar, dll.). Ingatlah untuk mengganti semua nilai dalam tanda kurung siku (misalnya <x>) dengan nilai yang diinginkan.

class <higher_level_class>:
    def __init__(self):
        subclasses = self._subclass_container()
        self.<sub_class> = subclasses[<sub_class, type string>]
        del subclasses

    def _subclass_container(self):
        _parent_class = self
        class <sub_class>:
            def __init__(self):
                self._parent_class = _parent_class
        return {<sub_class, type string>: <sub_class>}

Penjelasan cara kerja metode ini (langkah-langkah dasar):

  1. Buat fungsi bernama _subclass_containeruntuk bertindak sebagai pembungkus untuk mengakses variabel self, referensi ke kelas tingkat yang lebih tinggi (dari kode yang berjalan di dalam fungsi).

    1. Buat variabel bernama _parent_classyang merupakan referensi ke variabel selffungsi ini, yang _subclass_containerdapat diakses sub-kelas (hindari konflik nama dengan selfvariabel lain di subkelas).

    2. Kembalikan sub-kelas / sub-kelas sebagai kamus / daftar sehingga kode yang memanggil _subclass_containerfungsi dapat mengakses sub-kelas di dalamnya.

  2. Dalam __init__fungsi di dalam kelas tingkat yang lebih tinggi (atau di mana pun dibutuhkan), menerima sub-kelas yang dikembalikan dari fungsi _subclass_containerke dalam variabel subclasses.

  3. Tetapkan sub-kelas yang disimpan dalam subclassesvariabel ke atribut kelas tingkat yang lebih tinggi.

Beberapa tip untuk membuat skenario lebih mudah:

Membuat kode untuk menetapkan sub kelas ke kelas tingkat yang lebih tinggi lebih mudah untuk disalin dan digunakan di kelas yang berasal dari kelas tingkat yang lebih tinggi yang __init__ fungsinya berubah:

Sisipkan sebelum baris 12 di kode utama:

def _subclass_init(self):

Kemudian masukkan ke dalam fungsi ini baris 5-6 (dari kode utama) dan ganti baris 4-7 dengan kode berikut:

self._subclass_init(self)

Membuat subclass menetapkan ke kelas tingkat yang lebih tinggi mungkin ketika ada banyak / jumlah subclass yang tidak diketahui.

Ganti baris 6 dengan kode berikut:

for subclass_name in list(subclasses.keys()):
    setattr(self, subclass_name, subclasses[subclass_name])

Contoh skenario di mana solusi ini akan berguna dan di mana nama kelas tingkat yang lebih tinggi seharusnya tidak mungkin didapat:

Sebuah kelas, bernama "a" ( class a:) dibuat. Ini memiliki subclass yang perlu mengaksesnya (induk). Satu subclass disebut "x1". Di subclass ini, kode a.run_func()dijalankan.

Kemudian kelas lain, bernama "b" dibuat, diturunkan dari kelas "a" ( class b(a):). Setelah itu, beberapa kode dijalankan b.x1()(memanggil subfungsi "x1" dari b, sub-kelas turunan). Fungsi ini berjalan a.run_func(), memanggil fungsi "run_func" dari kelas "a", bukan fungsi "run_func" dari induknya, "b" (sebagaimana mestinya), karena fungsi yang didefinisikan di kelas "a" diatur untuk merujuk ke fungsi kelas "a", karena itu adalah induknya.

Hal ini akan menyebabkan masalah (misalnya jika fungsi a.run_functelah dihapus) dan satu-satunya solusi tanpa menulis ulang kode di kelas a.x1adalah dengan mendefinisikan kembali sub-kelas x1dengan kode yang diperbarui untuk semua kelas yang diturunkan dari kelas "a" yang jelas akan sulit dan tidak bernilai. Itu.


terima kasih atas komentar bagus Anda atas jawaban saya. Saya tidak tahu apa-apa tentang tag "jawaban wiki komunitas" dan sekarang sudah menghapusnya! Jadi terima kasih juga untuk menunjukkan hal ini. Anda mungkin ingin mengedit jawaban Anda sesuai, untuk mencegah kebingungan bagi pembaca selanjutnya!
mike rodent

3

Saya menemukan ini .

Tweak untuk menyesuaikan pertanyaan Anda:

class Outer(object):
    def some_method(self):
        # do something

    class _Inner(object):
        def __init__(self, outer):
            outer.some_method()
    def Inner(self):
        return _Inner(self)

Saya yakin Anda entah bagaimana bisa menulis dekorator untuk ini atau sesuatu

terkait: Apa tujuan kelas dalam python?


2

Kemungkinan lain:

class _Outer (object):
    # Define your static methods here, e.g.
    @staticmethod
    def subclassRef ():
        return Outer

class Outer (_Outer):
    class Inner (object):
        def outer (self):
            return _Outer

        def doSomething (self):
            outer = self.outer ()
            # Call your static mehthods.
            cls = outer.subclassRef ()
            return cls ()

1

Memperluas pemikiran meyakinkan @ tsnorri, bahwa metode luar mungkin metode statis :

class Outer(object):

    @staticmethod
    def some_static_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.some_static_method()    # <-- this will work later

    Inner.some_static_method = some_static_method

Sekarang baris yang dimaksud harus berfungsi pada saat sebenarnya dipanggil.

Baris terakhir dalam kode di atas memberikan kelas Inner metode statis yang merupakan tiruan dari metode statis Luar.


Ini memanfaatkan dua fitur Python, yaitu fungsi objek , dan cakupan tekstual .

Biasanya, lingkup lokal mereferensikan nama lokal dari fungsi (secara tekstual) saat ini.

... atau kelas saat ini dalam kasus kami. Jadi objek "lokal" dengan definisi kelas Luar ( Innerdan some_static_method) dapat dirujuk langsung dalam definisi itu.


1

Beberapa tahun terlambat ke pesta .... tapi untuk memperluas @mike rodentjawaban indah 's, saya telah menyediakan contoh saya sendiri di bawah ini menunjukkan bahwa betapa fleksibel solusinya adalah, dan mengapa itu harus (atau harus telah menjadi) yang diterima menjawab.

Python 3.7

class Parent():

    def __init__(self, name):
        self.name = name
        self.children = []

    class Inner(object):
        pass

    def Child(self, name):
        parent = self
        class Child(Parent.Inner):
            def __init__(self, name):
                self.name = name
                self.parent = parent
                parent.children.append(self)
        return Child(name)



parent = Parent('Bar')

child1 = parent.Child('Foo')
child2 = parent.Child('World')

print(
    # Getting its first childs name
    child1.name, # From itself
    parent.children[0].name, # From its parent
    # Also works with the second child
    child2.name,
    parent.children[1].name,
    # Go nuts if you want
    child2.parent.children[0].name,
    child1.parent.children[1].name
)

print(
    # Getting the parents name
    parent.name, # From itself
    child1.parent.name, # From its children
    child2.parent.name,
    # Go nuts again if you want
    parent.children[0].parent.name,
    parent.children[1].parent.name,
    # Or insane
    child2.parent.children[0].parent.children[1].parent.name,
    child1.parent.children[1].parent.children[0].parent.name
)


# Second parent? No problem
parent2 = Parent('John')
child3 = parent2.Child('Doe')
child4 = parent2.Child('Appleseed')

print(
    child3.name, parent2.children[0].name,
    child4.name, parent2.children[1].name,
    parent2.name # ....
)

Keluaran:

Foo Foo World World Foo World
Bar Bar Bar Bar Bar Bar Bar
Doe Doe Appleseed Appleseed John

Sekali lagi, jawaban yang bagus, alat peraga untuk Anda mike!


-2

Itu terlalu sederhana:

Memasukkan:

class A:
    def __init__(self):
        pass

    def func1(self):
        print('class A func1')

    class B:
        def __init__(self):
            a1 = A()
            a1.func1()

        def func1(self):
            print('class B func1')

b = A.B()
b.func1()

Keluaran

kelas A func1

kelas B func1


2
Hmm, perhatikan pertanyaannya, dan kata-kata mengakses kelas luar . Dengan kebetulan yang menyenangkan, cukup lucu, kelas luar dan dalam Anda berdua memiliki metode func1. Dan, sama lucunya, Anda membuat Adi dalam konstruktor B, yang sama sekali BUKAN Aobjek kelas luar tertentu B.
mike rodent
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.