Bagaimana cara super () Python bekerja dengan multiple inheritance?

888

Saya cukup baru dalam pemrograman berorientasi objek Python dan saya mengalami kesulitan memahami super()fungsi (kelas gaya baru) terutama ketika datang ke beberapa warisan.

Misalnya jika Anda memiliki sesuatu seperti:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Yang tidak saya dapatkan adalah: apakah Third()kelas mewarisi kedua metode konstruktor? Jika ya, lalu yang mana yang akan dijalankan dengan super () dan mengapa?

Dan bagaimana jika Anda ingin menjalankan yang lain? Saya tahu ini ada hubungannya dengan metode resolusi urutan Python ( MRO ).

Callisto
sumber
Bahkan, pewarisan berganda adalah satu-satunya kasus di mana super()ada gunanya. Saya tidak akan merekomendasikan menggunakannya dengan kelas menggunakan pewarisan linear, di mana itu hanya overhead yang tidak berguna.
Bachsau
9
@ Bachsau secara teknis benar karena merupakan overhead kecil tetapi super () lebih pythonic dan memungkinkan untuk re-factoring dan perubahan kode dari waktu ke waktu. Gunakan super () kecuali Anda benar-benar membutuhkan metode khusus kelas bernama.
Paul Whipp
2
Masalah lain super()adalah, bahwa itu memaksa setiap subclass untuk menggunakannya juga, sementara ketika tidak menggunakan super(), semua orang subclass itu dapat memutuskan sendiri. Jika pengembang menggunakannya tidak tahu super()atau tidak tahu itu digunakan, masalah dengan mro dapat muncul yang sangat sulit dilacak.
Bachsau
Saya telah menemukan hampir setiap jawaban di sini membingungkan dalam satu atau lain cara. Anda sebenarnya akan merujuk di sini sebagai gantinya.
matanster

Jawaban:

709

Ini dirinci dengan jumlah detail yang masuk akal oleh Guido sendiri dalam posting blognya Metode Resolution Order (termasuk dua upaya sebelumnya).

Dalam contoh Anda, Third()akan menelepon First.__init__. Python mencari setiap atribut dalam orangtua kelas karena mereka terdaftar kiri ke kanan. Dalam hal ini, kami sedang mencari __init__. Jadi, jika Anda mendefinisikan

class Third(First, Second):
    ...

Python akan mulai dengan melihat First, dan, jika Firsttidak memiliki atribut, maka akan terlihat Second.

Situasi ini menjadi lebih kompleks ketika warisan mulai melintasi jalur (misalnya jika Firstdiwarisi dari Second). Bacalah tautan di atas untuk detail lebih lanjut, tetapi, singkatnya, Python akan mencoba mempertahankan urutan di mana setiap kelas muncul di daftar warisan, dimulai dengan kelas anak itu sendiri.

Jadi, misalnya, jika Anda punya:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

MRO akan menjadi [Fourth, Second, Third, First].

By the way: jika Python tidak dapat menemukan urutan resolusi metode yang koheren, itu akan menimbulkan pengecualian, bukannya kembali ke perilaku yang mungkin mengejutkan pengguna.

Diedit untuk menambahkan contoh MRO yang ambigu:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

Haruskah ThirdMRO menjadi [First, Second]atau [Second, First]? Tidak ada harapan yang jelas, dan Python akan meningkatkan kesalahan:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

Sunting: Saya melihat beberapa orang berpendapat bahwa contoh di atas tidak memiliki super()panggilan, jadi izinkan saya menjelaskan: Inti dari contoh adalah untuk menunjukkan bagaimana MRO dibangun. Mereka tidak dimaksudkan untuk mencetak "first \ nsecond \ third" atau apa pun. Anda dapat - dan harus, tentu saja, bermain-main dengan contoh ini, menambahkan super()panggilan, melihat apa yang terjadi, dan mendapatkan pemahaman yang lebih dalam tentang model pewarisan Python. Tetapi tujuan saya di sini adalah untuk membuatnya tetap sederhana dan menunjukkan bagaimana MRO dibangun. Dan itu dibangun seperti yang saya jelaskan:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)
rbp
sumber
12
Menjadi lebih menarik (dan, bisa dibilang, lebih membingungkan) ketika Anda mulai memanggil super () di First, Second, dan Third [ pastebin.com/ezTyZ5Wa ].
gatoatigrado
52
Saya pikir kurangnya panggilan super di kelas pertama adalah masalah yang sangat besar dengan jawaban ini; tanpa membahas bagaimana / mengapa itu pemahaman kritis yang penting untuk pertanyaan itu hilang.
Sam Hartman
3
Jawaban ini benar-benar salah. Tanpa panggilan super () pada orang tua, tidak ada yang terjadi. @ Jawaban tak bernyawa adalah yang benar.
Cerin
8
@Cerin Maksud dari contoh ini adalah untuk menunjukkan bagaimana MRO dibangun. Contohnya TIDAK dimaksudkan untuk mencetak "first \ nsecond \ third" atau apa pun. Dan MRO memang benar: Keempat .__ mro__ == (<class ' main .ourour'>, <class ' main .Second'>, <class ' main .Third'>, <class ' main .First'>, < ketik 'objek'>)
rbp
2
Sejauh yang saya bisa lihat, jawaban ini hilang salah satu pertanyaan OP, yaitu "Dan bagaimana jika Anda ingin menjalankan yang lain?". Saya ingin melihat jawaban untuk pertanyaan ini. Apakah kita hanya perlu menyebutkan kelas dasar secara eksplisit?
Ray
251

Kode Anda, dan jawaban lainnya, semuanya bermasalah. Mereka kehilangan super()panggilan dalam dua kelas pertama yang diperlukan agar subclass koperasi bekerja.

Ini adalah versi kode yang sudah diperbaiki:

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

The super()panggilan menemukan metode berikutnya di MRO di setiap langkah, yang mengapa Pertama dan Kedua harus memilikinya juga, jika eksekusi berhenti di akhir Second.__init__().

Inilah yang saya dapatkan:

>>> Third()
second
first
third
tak bernyawa
sumber
90
Apa yang harus dilakukan jika kelas-kelas ini membutuhkan parameter berbeda untuk menginisialisasi diri mereka sendiri?
calfzhou
2
"subclass koperasi"
Quant Metropolis
6
Dengan cara ini metode init dari KEDUA kelas dasar akan dieksekusi, sedangkan contoh aslinya hanya memanggil init pertama yang ditemui di MRO. Saya kira itu tersirat oleh istilah "subclass koperasi", tetapi klarifikasi akan berguna ('Eksplisit lebih baik daripada implisit', Anda tahu;))
Quant Metropolis
1
Ya, jika Anda melewatkan parameter berbeda ke metode yang dipanggil via super, semua implementasi metode itu naik MRO menuju objek () harus memiliki tanda tangan yang kompatibel. Ini dapat dicapai melalui parameter kata kunci: terima lebih banyak parameter daripada metode, dan abaikan yang ekstra. Umumnya dianggap jelek untuk melakukan ini, dan untuk kebanyakan kasus menambahkan metode baru lebih baik, tetapi init (hampir?) Unik sebagai nama metode khusus tetapi dengan parameter yang ditentukan pengguna.
tak bernyawa
15
Desain multiple inheritance benar-benar buruk di python. Kelas-kelas dasar hampir perlu tahu siapa yang akan menurunkannya, dan berapa banyak kelas-kelas dasar lain yang diturunkan akan diturunkan, dan dalam urutan apa ... jika tidak superakan gagal dijalankan (karena parameter ketidakcocokan), atau tidak akan memanggil beberapa pangkalan (karena Anda tidak menulis superdi salah satu pangkalan yang merusak tautan)!
Nawaz
186

Saya ingin menguraikan jawaban dengan sedikit mati karena ketika saya mulai membaca tentang cara menggunakan super () dalam hierarki pewarisan berganda di Python, saya tidak segera mendapatkannya.

Yang perlu Anda pahami adalah super(MyClass, self).__init__()menyediakan metode berikutnya __init__ sesuai dengan algoritma Method Resolution Ordering (MRO) yang digunakan dalam konteks hierarki warisan lengkap .

Bagian terakhir ini penting untuk dipahami. Mari kita perhatikan contohnya lagi:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

Menurut artikel ini tentang Metode Penyelesaian Resolusi oleh Guido van Rossum, perintah untuk menyelesaikan __init__dihitung (sebelum Python 2.3) menggunakan "traversal kiri-ke-kanan kedalaman-pertama":

Third --> First --> object --> Second --> object

Setelah menghapus semua duplikat, kecuali yang terakhir, kami mendapatkan:

Third --> First --> Second --> object

Jadi, mari ikuti apa yang terjadi ketika kita membuat instance dari Thirdkelas, misalnya x = Third().

  1. Menurut MRO Third.__init__dieksekusi.
    • cetakan Third(): entering
    • kemudian super(Third, self).__init__()jalankan dan pengembalian MRO First.__init__yang disebut.
  2. First.__init__ mengeksekusi.
    • cetakan First(): entering
    • kemudian super(First, self).__init__()jalankan dan pengembalian MRO Second.__init__yang disebut.
  3. Second.__init__ mengeksekusi.
    • cetakan Second(): entering
    • kemudian super(Second, self).__init__()jalankan dan pengembalian MRO object.__init__yang disebut.
  4. object.__init__ dijalankan (tidak ada pernyataan cetak dalam kode di sana)
  5. eksekusi kembali ke Second.__init__yang kemudian dicetakSecond(): exiting
  6. eksekusi kembali ke First.__init__yang kemudian dicetakFirst(): exiting
  7. eksekusi kembali ke Third.__init__yang kemudian dicetakThird(): exiting

Ini menjelaskan mengapa instantiating Third () menghasilkan ke:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

Algoritma MRO telah ditingkatkan dari Python 2.3 dan seterusnya untuk bekerja dengan baik dalam kasus-kasus yang kompleks, tapi saya rasa menggunakan "kedalaman-pertama-ke-kanan traversal" + "menghapus duplikat harapan untuk yang terakhir" masih berfungsi dalam kebanyakan kasus (silakan berkomentar jika ini bukan masalahnya). Pastikan untuk membaca posting blog oleh Guido!

Kertas Visions
sumber
6
Saya masih tidak mengerti mengapa: Di dalam init super Pertama (Pertama, diri sendiri) .__ init __ () memanggil init Kedua, karena itulah yang ditentukan oleh MRO!
user389955
@ user389955 Objek yang dibuat adalah tipe Ketiga yang memiliki semua metode init. Jadi, jika Anda menganggap MRO membuat daftar semua fungsi init dalam urutan tertentu, dengan setiap panggilan super, Anda akan maju satu langkah hingga mencapai akhir.
Sreekumar R
15
Saya pikir Langkah 3 perlu penjelasan lebih lanjut: Jika Thirdtidak mewarisi dari Second, maka super(First, self).__init__akan memanggil object.__init__dan setelah kembali, "pertama" akan dicetak. Tetapi karena Thirdmewarisi dari keduanya Firstdan Second, alih-alih menelepon object.__init__setelah First.__init__MRO menentukan bahwa hanya panggilan akhir untuk object.__init__dipertahankan, dan laporan cetak masuk Firstdan Secondtidak tercapai sampai object.__init__kembali. Karena Seconditu yang terakhir meneleponobject.__init__ , ia kembali ke dalam Secondsebelum kembali First.
MountainDrew
1
Menariknya, PyCharm tampaknya mengetahui semua ini (petunjuknya berbicara tentang parameter mana yang sesuai dengan panggilan ke super. Ia juga memiliki beberapa gagasan tentang kovarians input, sehingga diakui List[subclass]sebagai List[superclass]jika subclassadalah subkelas dari superclass( Listberasal dari typingmodul PEP 483 iirc)
Reb.Cabin
Posting bagus tapi saya ketinggalan informasi sehubungan dengan argumen konstruktor, yaitu apa yang terjadi Jika Kedua dan Pertama mengharapkan argumen yang berbeda? Konstruktor pertama harus memproses beberapa argumen dan meneruskan sisanya ke Kedua. Apakah itu benar? Kedengarannya tidak benar bagi saya bahwa Pertama perlu tahu tentang argumen yang diperlukan untuk Kedua.
Christian K.
58

Ini dikenal sebagai Masalah Berlian , halaman tersebut memiliki entri pada Python, tetapi singkatnya, Python akan memanggil metode superclass dari kiri ke kanan.

monoceres
sumber
Ini bukan Masalah Intan. Masalah Berlian melibatkan empat kelas dan pertanyaan OP hanya melibatkan tiga kelas.
Ian Goodfellow
147
objectadalah yang keempat
GP89
28

Ini adalah bagaimana saya memecahkan masalah memiliki beberapa pewarisan dengan variabel yang berbeda untuk inisialisasi dan memiliki beberapa MixIns dengan pemanggilan fungsi yang sama. Saya harus secara eksplisit menambahkan variabel untuk lulus ** kwargs dan menambahkan antarmuka MixIn menjadi titik akhir untuk panggilan super.

Berikut Aadalah kelas dasar yang dapat diperpanjang dan Bdan Cmerupakan kelas MixIn yang menyediakan fungsi f. Adan Bkeduanya mengharapkan parameter vdi __init__dan Cmengharapkan mereka w. Fungsi ini fmengambil satu parameter y. Qmewarisi dari ketiga kelas. MixInFadalah antarmuka mixin untuk Bdan C.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)
brent.payne
sumber
Saya pikir ini mungkin harus menjadi pertanyaan dan jawaban yang terpisah, karena MRO adalah topik yang cukup besar sendiri tanpa harus berurusan dengan berbagai argumen di berbagai fungsi dengan warisan (multiple inheritance adalah kasus khusus untuk itu).
tak bernyawa
8
Secara teoritis, ya. Secara praktis, skenario ini telah muncul setiap kali saya menemukan warisan Diamond di python, jadi saya menambahkannya di sini. Karena, ini adalah tempat saya pergi setiap kali saya tidak bisa menghindari warisan berlian dengan bersih. Berikut adalah beberapa tautan tambahan untuk masa depan saya: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
brent.payne
Yang kami inginkan adalah program dengan nama parameter yang secara semantik bermakna. Tetapi dalam contoh ini hampir semua parameter diberi nama secara anonim, yang akan membuat jauh lebih sulit bagi programmer asli untuk mendokumentasikan kode dan bagi programmer lain untuk membaca kode.
Arthur
Permintaan tarik ke repo github dengan nama deskriptif akan dihargai
brent.payne
@ brent.payne Saya pikir @Arthur berarti bahwa seluruh pendekatan Anda bergantung pada penggunaan args/ kwargsdaripada parameter bernama.
maks
25

Saya mengerti ini tidak langsung menjawab super() pertanyaan, tetapi saya merasa cukup relevan untuk dibagikan.

Ada juga cara untuk memanggil langsung setiap kelas yang diwarisi:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Catatan saja bahwa jika Anda melakukannya dengan cara ini, Anda harus memanggil satu sama secara manual karena saya cukup yakin First's __init__()tidak akan dipanggil.

Seaux
sumber
5
Itu tidak akan dipanggil karena Anda tidak memanggil setiap kelas warisan. Masalahnya adalah bahwa jika Firstdan Secondkeduanya mewarisi kelas lain dan memanggilnya langsung maka kelas umum ini (titik awal berlian) disebut dua kali. Super menghindari ini.
Trilarion
@Trilarion Ya, saya yakin itu tidak akan terjadi. Namun, saya tidak tahu pasti dan saya tidak ingin menyatakan seolah-olah saya tahu meskipun itu sangat tidak mungkin. Itu poin bagus tentang objectdipanggil dua kali. Saya tidak memikirkan hal itu. Saya hanya ingin menegaskan bahwa Anda memanggil kelas induk secara langsung.
Seaux
Sayangnya, ini rusak jika init mencoba mengakses metode pribadi apa pun :(
Erik Aronesty
21

Secara keseluruhan

Dengan asumsi semuanya turun dari object(Anda berada di Anda sendiri jika tidak), Python menghitung urutan resolusi metode (MRO) berdasarkan pohon warisan kelas Anda. MRO memenuhi 3 properti:

  • Anak-anak dari kelas datang sebelum orang tua mereka
  • Orang tua kiri datang sebelum orang tua benar
  • Kelas hanya muncul sekali di MRO

Jika tidak ada pemesanan seperti itu, kesalahan Python. Cara kerja bagian dalam ini adalah Linerisasi C3 dari nenek moyang kelas. Baca semua tentang ini di sini: https://www.python.org/download/releases/2.3/mro/

Jadi, dalam kedua contoh di bawah, itu adalah:

  1. Anak
  2. Kiri
  3. Baik
  4. Induk

Ketika suatu metode dipanggil, kemunculan pertama metode itu di MRO adalah yang disebut. Setiap kelas yang tidak mengimplementasikan metode itu dilewati. Panggilan apa pun ke superdalam metode itu akan memanggil kemunculan metode itu selanjutnya di MRO. Akibatnya, itu penting baik urutan mana Anda menempatkan kelas dalam warisan, dan di mana Anda menempatkan panggilansuper dalam metode.

Dengan superpertama di setiap metode

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() Output:

parent
right
left
child

Dengan yang superterakhir di setiap metode

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() Output:

child
left
right
parent
Zags
sumber
Saya melihat bahwa Anda dapat mengakses Leftmenggunakan super()dari Child. misalkan saya ingin mengakses Rightdari dalam Child. Apakah ada cara untuk mengakses Rightdari Childmenggunakan super? Atau haruskah saya langsung menelepon Rightdari dalam super?
alpha_989
4
@ alpha_989 Jika Anda ingin mengakses metode kelas tertentu saja, Anda harus merujuk kelas itu secara langsung daripada menggunakan super. Super adalah tentang mengikuti rantai warisan, tidak sampai ke metode kelas tertentu.
Zags
1
Terima kasih karena secara eksplisit menyebutkan 'Kelas hanya muncul sekali di MRO'. Ini menyelesaikan masalah saya. Sekarang saya akhirnya mengerti cara kerja banyak pewarisan. Seseorang perlu menyebutkan properti MRO!
Tushar Vazirani
18

Tentang @ komentar calfzhou ini , Anda dapat menggunakan, seperti biasanya, **kwargs:

Contoh menjalankan online

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

Hasil:

A None
B hello
A1 6
B1 5

Anda juga dapat menggunakannya secara posisi:

B1(5, 6, b="hello", a=None)

tetapi Anda harus mengingat MRO, itu benar-benar membingungkan.

Saya bisa sedikit mengganggu, tetapi saya perhatikan bahwa orang-orang lupa setiap kali menggunakan *argsdan **kwargsketika mereka mengganti metode, sementara itu salah satu dari sedikit penggunaan yang benar-benar bermanfaat dan waras dari 'variabel ajaib' ini.

Marco Sulla
sumber
Wow itu benar-benar jelek. Sayang sekali Anda tidak bisa hanya mengatakan superclass spesifik yang ingin Anda panggil. Namun, ini memberi saya lebih banyak insentif untuk menggunakan komposisi dan menghindari pewarisan berganda seperti wabah.
Tom Busby
15

Poin lain yang belum dibahas adalah melewati parameter untuk inisialisasi kelas. Karena tujuan darisuper bergantung pada subkelas, satu-satunya cara yang baik untuk melewatkan parameter adalah mengemas semuanya. Maka berhati-hatilah untuk tidak memiliki nama parameter yang sama dengan makna yang berbeda.

Contoh:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

memberi:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

Memanggil kelas super __init__langsung ke penugasan parameter yang lebih langsung memang menggoda tetapi gagal jika adasuper panggilan di kelas super dan / atau MRO diubah dan kelas A dapat dipanggil beberapa kali, tergantung pada implementasinya.

Untuk menyimpulkan: pewarisan kooperatif dan parameter super dan spesifik untuk inisialisasi tidak bekerja bersama dengan sangat baik.

Trilarion
sumber
5
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

Output adalah

first 10
second 20
that's it

Panggilan ke Third () menempatkan init yang didefinisikan dalam Third. Dan panggilan ke super dalam rutin itu memanggil init yang didefinisikan dalam First. MRO = [Pertama, Kedua]. Sekarang panggilan ke super in init didefinisikan dalam Pertama akan terus mencari MRO dan menemukan init didefinisikan dalam Kedua, dan setiap panggilan ke super akan menekan objek init default . Saya harap contoh ini menjelaskan konsep ini.

Jika Anda tidak menelepon super dari Pertama. Rantai berhenti dan Anda akan mendapatkan output berikut.

first 10
that's it
Seraj Ahmad
sumber
1
itu karena di kelas Pertama, Anda memanggil 'print' dulu lalu 'super'.
qi
2
itu untuk mengilustrasikan perintah panggilan
Seraj Ahmad
4

Dalam learningpythonthehardway saya belajar sesuatu yang disebut super () fungsi bawaan jika tidak salah. Fungsi panggilan super () dapat membantu warisan melewati orang tua dan 'saudara kandung' dan membantu Anda melihat lebih jelas. Saya masih pemula tetapi saya senang berbagi pengalaman saya menggunakan super () ini di python2.7.

Jika Anda telah membaca komentar di halaman ini, Anda akan mendengar tentang Method Resolution Order (MRO), metode yang menjadi fungsi yang Anda tulis, MRO akan menggunakan skema Kedalaman-Pertama-Kiri-ke-Kanan untuk mencari dan menjalankan. Anda dapat melakukan lebih banyak penelitian tentang itu.

Dengan menambahkan fungsi super ()

super(First, self).__init__() #example for class First.

Anda dapat menghubungkan beberapa instance dan 'keluarga' dengan super (), dengan menambahkan masing-masing dan semua orang di dalamnya. Dan itu akan mengeksekusi metode, pergi melalui mereka dan pastikan Anda tidak ketinggalan! Namun, menambahkannya sebelum atau sesudahnya akan membuat perbedaan, Anda akan tahu jika Anda telah melakukan latihan setelah latihan 44. Biarkan kesenangan dimulai !!

Mengambil contoh di bawah ini, Anda dapat menyalin & menempel dan coba jalankan:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

Bagaimana cara kerjanya? Contoh dari kelima () akan seperti ini. Setiap langkah beralih dari kelas ke kelas tempat fungsi super ditambahkan.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

Orangtua ditemukan dan akan berlanjut ke Ketiga dan Keempat !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

Sekarang semua kelas dengan super () telah diakses! Kelas induk telah ditemukan dan dieksekusi dan sekarang terus membuka kotak fungsi dalam warisan untuk menyelesaikan kode.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

Hasil dari program di atas:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

Bagi saya dengan menambahkan super () memungkinkan saya untuk melihat lebih jelas tentang bagaimana python akan mengeksekusi pengkodean saya dan memastikan warisan dapat mengakses metode yang saya maksud.

Will MeetYou
sumber
Terima kasih untuk demo terperinci!
Tushar Vazirani
3

Saya ingin menambahkan apa yang dikatakan @Visionscaper di bagian atas:

Third --> First --> object --> Second --> object

Dalam hal ini interpreter tidak memfilter kelas objek karena diduplikasi, melainkan karena kedua muncul dalam posisi kepala dan tidak muncul dalam posisi ekor dalam subset hierarki. Sedangkan objek hanya muncul di posisi ekor dan tidak dianggap posisi kuat dalam algoritma C3 untuk menentukan prioritas.

Linearisasi (mro) dari kelas C, L (C), adalah

  • Kelas C
  • ditambah penggabungan
    • linearisasi dari orang tuanya P1, P2, .. = L (P1, P2, ...) dan
    • daftar orang tuanya P1, P2, ..

Penggabungan Linier dilakukan dengan memilih kelas umum yang muncul sebagai kepala daftar dan bukan ekor karena urutan penting (akan menjadi jelas di bawah)

Linearisasi Ketiga dapat dihitung sebagai berikut:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

Jadi untuk implementasi super () dalam kode berikut:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

menjadi jelas bagaimana metode ini akan diselesaikan

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"
supi
sumber
"Lebih karena itu Kedua muncul di posisi kepala dan tidak muncul di posisi ekor dalam subset hierarki." Tidak jelas apa posisi kepala atau ekor, atau apa hierarki subset atau subset mana yang Anda maksud.
OrangeSherbet
Posisi ekor mengacu pada kelas yang lebih tinggi dalam hirarki kelas dan sebaliknya. 'Objek' kelas dasar ada di ujung ekor. Kunci untuk memahami algoritma mro adalah bagaimana 'Kedua' muncul sebagai super dari 'Pertama'. Kami biasanya menganggapnya sebagai kelas 'objek'. Itu benar, tetapi, hanya dalam perspektif kelas 'Pertama'. Namun ketika dilihat dari perspektif kelas 'Ketiga', urutan hierarki untuk 'Pertama' berbeda dan dihitung seperti yang ditunjukkan di atas. algoritma mro mencoba membuat perspektif ini (atau hierarki subset) untuk semua beberapa kelas yang diwariskan
supi
3

Dalam pewarisan python 3.5+ terlihat dapat diprediksi dan sangat bagus untuk saya. Silakan lihat kode ini:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Output:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

Seperti yang Anda lihat, ia memanggil foo tepat SATU kali untuk setiap rantai yang diwarisi dalam urutan yang sama seperti yang diwarisi. Anda bisa mendapatkan pesanan itu dengan menelepon . mro :

Keempat -> Ketiga -> Pertama -> Kedua -> Basis -> objek

rfedorov
sumber
2

Mungkin masih ada sesuatu yang bisa ditambahkan, contoh kecil dengan Django rest_framework, dan dekorator. Ini memberikan jawaban untuk pertanyaan tersirat: "mengapa saya tetap menginginkan ini?"

Seperti yang dikatakan: kami menggunakan Django rest_framework, dan kami menggunakan tampilan umum, dan untuk setiap jenis objek dalam basis data kami, kami mendapati diri kami dengan satu kelas tampilan yang menyediakan GET dan POST untuk daftar objek, dan kelas tampilan lainnya menyediakan GET , PUT, dan DELETE untuk objek individual.

Sekarang POST, PUT, dan DELETE kami ingin menghiasnya dengan login_required Django. Perhatikan bagaimana ini menyentuh kedua kelas, tetapi tidak semua metode di kedua kelas.

Sebuah solusi bisa melalui banyak pewarisan.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

Demikian juga untuk metode lainnya.

Dalam daftar warisan kelas konkret saya, saya akan menambahkan LoginToPostsebelum ListCreateAPIViewdan LoginToPutOrDeletesebelumnya RetrieveUpdateDestroyAPIView. Kelas konkret saya getakan tetap tidak didekorasi.

mariotomo
sumber
1

Posting jawaban ini untuk referensi saya di masa mendatang.

Python Multiple Inheritance harus menggunakan model berlian dan tanda tangan fungsi tidak boleh berubah dalam model.

    A
   / \
  B   C
   \ /
    D

Cuplikan kode contoh adalah; -

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Di sini kelas A adalah object

Akhil Nadh PC
sumber
1
Aharus juga menjadi menelepon __init__. Atidak "menemukan" metode ini __init__, sehingga tidak dapat berasumsi bahwa beberapa kelas lain mungkin memiliki Asebelumnya dalam MRO-nya. Satu-satunya kelas yang __init__metodenya tidak (dan tidak seharusnya) menelepon super().__init__adalah object.
chepner
Ya. Itu sebabnya saya telah menulis A adalah objectMungkin saya pikir, saya harus menulis class A (object) : sebagai gantinya
Akhil Nadh PC
Atidak bisa objectjika Anda menambahkan parameter ke parameternya __init__.
chepner