Pertanyaan Memahami Python super () dengan metode __init __ () [duplikat]


Pertanyaan ini sudah memiliki jawaban di sini:

Saya mencoba memahami penggunaan super(). Dari kelihatannya, kedua kelas anak dapat dibuat, baik-baik saja.

Saya ingin tahu tentang perbedaan nyata antara 2 kelas anak berikut.

class Base(object):
    def __init__(self):
        print "Base created"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()

1979
2018-02-23 00:30


asal


Jawaban:


super() memungkinkan Anda menghindari mengacu pada kelas dasar secara eksplisit, yang bisa menyenangkan. Tetapi keunggulan utama datang dengan multiple inheritance, di mana segala macam hal menyenangkan bisa terjadi. Lihat dokumen standar tentang super jika Anda belum melakukannya.

Perhatikan itu sintaks berubah dengan Python 3.0: Anda hanya bisa mengatakan super().__init__() dari pada super(ChildB, self).__init__() yang IMO sedikit lebih baik.


1423
2018-02-23 00:37



Saya mencoba untuk mengerti super()

Alasan yang kami gunakan super adalah agar kelas anak yang mungkin menggunakan multiple inheritance kooperatif akan memanggil fungsi kelas induk yang benar berikutnya dalam Method Resolution Order (MRO).

Dengan Python 3, kita dapat menyebutnya seperti ini:

class ChildB(Base):
    def __init__(self):
        super().__init__() 

Di Python 2, kami diminta untuk menggunakannya seperti ini:

        super(ChildB, self).__init__()

Tanpa super, Anda terbatas dalam kemampuan Anda untuk menggunakan multiple inheritance:

        Base.__init__(self) # Avoid this.

Lebih lanjut saya jelaskan di bawah ini.

"Apa perbedaan sebenarnya dalam kode ini ?:"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

Perbedaan utama dalam kode ini adalah bahwa Anda mendapatkan lapisan tipuan di __init__ dengan super, yang menggunakan kelas saat ini untuk menentukan kelas selanjutnya __init__ untuk mencari di MRO.

Saya mengilustrasikan perbedaan ini dalam jawaban di pertanyaan kanonis, Bagaimana menggunakan 'super' dengan Python?, yang menunjukkan injeksi ketergantungan dan multiple inheritance kooperatif.

Jika Python tidak punya super

Ini kode yang sebenarnya sangat mirip super (bagaimana itu diimplementasikan dalam C, minus beberapa perilaku pengecekan dan penggantian, dan diterjemahkan ke Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

Tertulis sedikit lebih seperti Python asli:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

Jika kami tidak memiliki super objek, kita harus menulis kode manual ini di mana-mana (atau menciptakannya kembali!) untuk memastikan bahwa kita memanggil metode berikutnya yang sesuai dalam Cara Penyelesaian Metode!

Bagaimana super melakukan ini dengan Python 3 tanpa diberitahu secara eksplisit kelas dan contoh dari metode mana yang dipanggil?

Ia mendapat frame stack panggilan, dan menemukan kelas (implisit disimpan sebagai variabel bebas lokal, __class__, membuat pemanggilan fungsi penutupan atas kelas) dan argumen pertama untuk fungsi itu, yang seharusnya menjadi instance atau kelas yang menginformasikannya yang mana Method Resolution Order (MRO) digunakan.

Karena itu membutuhkan argumen pertama untuk MRO, menggunakan super dengan metode statis tidak mungkin.

Kritik dari jawaban lain:

super () memungkinkan Anda menghindari merujuk ke kelas dasar secara eksplisit, yang bisa menyenangkan. . Tetapi keunggulan utama datang dengan multiple inheritance, di mana segala macam hal menyenangkan dapat terjadi. Lihat dokumen standar tentang super jika Anda belum melakukannya.

Ini bukan gelombang tangan dan tidak memberi tahu kami banyak, tetapi intinya super bukan untuk menghindari menulis kelas induk. Intinya adalah untuk memastikan bahwa metode berikutnya sejalan dalam urutan resolusi metode (MRO) dipanggil. Ini menjadi penting dalam multiple inheritance.

Saya akan jelaskan di sini.

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super(ChildB, self).__init__()

Dan mari kita ciptakan ketergantungan yang ingin kita dipanggil setelah Anak:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super(UserDependency, self).__init__()

Sekarang ingat, ChildB menggunakan super, ChildA tidak:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super(UserA, self).__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super(UserB, self).__init__()

Dan UserA tidak memanggil metode UserDependency:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

Tapi UserB, karena ChildB menggunakan super, tidak !:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

Kritik untuk jawaban lain

Dalam keadaan apa pun Anda tidak harus melakukan yang berikut ini, yang disarankan oleh jawaban lainnya, karena Anda pasti akan mendapatkan kesalahan saat Anda subkelas ChildB:

        super(self.__class__, self).__init__() # Don't do this. Ever.

(Jawaban itu tidak pintar atau sangat menarik, tetapi terlepas dari kritik langsung dalam komentar dan lebih dari 17 downvotes, penjawab tetap bersikeras menyarankannya sampai editor yang baik memperbaiki masalahnya.)

Penjelasan: Jawaban itu menyarankan memanggil super seperti ini:

super(self.__class__, self).__init__()

Ini adalah sama sekali salah. super mari kita mencari orangtua berikutnya di MRO (lihat bagian pertama dari jawaban ini) untuk kelas anak. Jika Anda memberi tahu super kita berada dalam metode instance anak, kemudian akan mencari metode berikutnya sesuai (mungkin yang ini) yang menghasilkan rekursi, mungkin menyebabkan kegagalan logis (dalam contoh jawaban, ya) atau RuntimeError ketika kedalaman rekurinya terlampaui.

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'

407
2017-11-25 19:00



Telah dicatat bahwa dalam Python 3.0+ dapat Anda gunakan

super().__init__() 

untuk membuat panggilan Anda, yang singkat dan tidak mengharuskan Anda untuk mereferensi orang tua ATAU nama kelas secara eksplisit, yang dapat berguna. Saya hanya ingin menambahkan bahwa untuk Python 2.7 atau di bawah, adalah mungkin untuk mendapatkan perilaku tidak peka nama ini dengan menulis self.__class__ alih-alih nama kelas, yaitu

super(self.__class__, self).__init__()

NAMUN, ini istirahat panggilan ke super untuk setiap kelas yang mewarisi dari kelas Anda, di mana self.__class__ dapat mengembalikan kelas anak. Sebagai contoh:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

Di sini saya memiliki kelas Square, yang merupakan sub-kelas dari Rectangle. Katakanlah saya tidak ingin menulis konstruktor terpisah untuk Square karena konstruktor untuk Rectangle cukup baik, tetapi untuk alasan apa pun saya ingin menerapkan Square, jadi saya dapat menerapkan kembali metode lain.

Saat saya membuat Square menggunakan mSquare = Square('a', 10,10), Python menyebut konstruktor untuk Rectangle karena saya belum memberi Square konstruktornya sendiri. Namun, dalam konstruktor untuk Rectangle, panggilan super(self.__class__,self) akan mengembalikan superclass of mSquare, sehingga memanggil konstruktor untuk Rectangle lagi. Ini adalah bagaimana pengulangan tak terbatas terjadi, seperti yang disebutkan oleh @S_C. Dalam hal ini, ketika saya menjalankan super(...).__init__() Saya memanggil konstruktor untuk Rectangle tetapi karena saya tidak memberikan argumen, saya akan mendapatkan kesalahan.


221
2017-10-08 20:08



Super tidak memiliki efek samping

Base = ChildB

Base()

bekerja sesuai harapan

Base = ChildA

Base()

masuk ke rekursi tak terbatas.


75
2017-11-27 23:26



Hanya kepala ... dengan Python 2.7, dan saya percaya sejak itu super() diperkenalkan pada versi 2.2, Anda hanya bisa menelepon super()jika salah satu orang tua mewarisi dari kelas yang akhirnya mewarisi object (kelas gaya baru).

Secara pribadi, untuk kode python 2.7, saya akan terus menggunakan BaseClassName.__init__(self, args) sampai saya benar-benar mendapatkan keuntungan dari menggunakan super().


68
2018-05-25 17:52



Tidak ada, sungguh. super() melihat kelas berikutnya di MRO (urutan resolusi metode, diakses dengan cls.__mro__) untuk memanggil metode. Hanya menelepon pangkalan __init__ memanggil basis __init__. Seperti yang terjadi, MRO memiliki tepat satu barang - dasar. Jadi Anda benar-benar melakukan hal yang sama persis, tetapi dengan cara yang lebih baik super() (terutama jika Anda masuk ke multiple inheritance nanti).


45
2018-02-23 00:34



Perbedaan utamanya adalah itu ChildA.__init__ akan menelepon tanpa syarat Base.__init__ sedangkan ChildB.__init__ akan menelepon __init__ di kelas apa pun yang terjadi ChildB leluhur di selfgaris leluhur (yang mungkin berbeda dari yang Anda harapkan).

Jika Anda menambahkan ClassC yang menggunakan multiple inheritance:

class Mixin(Base):
  def __init__(self):
    print "Mixin stuff"
    super(Mixin, self).__init__()

class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Base
  pass

ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base

kemudian Base bukan lagi induk dari ChildB untuk ChildC contoh. Sekarang super(ChildB, self) akan menunjuk ke Mixin jika self adalah ChildC contoh.

Anda telah menyisipkan Mixin diantara ChildB dan Base. Dan Anda dapat memanfaatkannya dengan super()

Jadi jika Anda mendesain kelas Anda sehingga mereka dapat digunakan dalam skenario Koperasi Beberapa Warisan, Anda gunakan super karena Anda tidak benar-benar tahu siapa yang akan menjadi leluhur saat runtime.

Itu pos super super dianggap dan pycon 2015 video yang menyertainya jelaskan ini dengan cukup baik.


22
2017-09-21 06:41