Katakanlah saya memiliki beberapa skenario warisan:
class A(object):
# code for A here
class B(object):
# code for B here
class C(A, B):
def __init__(self):
# What's the right code to write here to ensure
# A.__init__ and B.__init__ get called?
Ada dua pendekatan yang khas untuk menulis C
's __init__
:
- (gaya lama)
ParentClass.__init__(self)
- (gaya baru)
super(DerivedClass, self).__init__()
Namun, dalam kedua kasus, jika kelas induk ( A
dan B
) tidak mengikuti konvensi yang sama, maka kode tidak akan berfungsi dengan benar (beberapa mungkin terlewatkan, atau dipanggil beberapa kali).
Jadi apa cara yang benar lagi? Sangat mudah untuk mengatakan "konsisten saja, ikuti satu atau yang lain", tetapi jika A
atau B
berasal dari perpustakaan pihak ke-3, lalu bagaimana? Apakah ada pendekatan yang dapat memastikan bahwa semua konstruktor kelas induk dipanggil (dan dalam urutan yang benar, dan hanya sekali)?
Edit: untuk melihat apa yang saya maksud, jika saya lakukan:
class A(object):
def __init__(self):
print("Entering A")
super(A, self).__init__()
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
A.__init__(self)
B.__init__(self)
print("Leaving C")
Lalu saya mendapatkan:
Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C
Perhatikan bahwa B
init dipanggil dua kali. Jika aku melakukan:
class A(object):
def __init__(self):
print("Entering A")
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
super(C, self).__init__()
print("Leaving C")
Lalu saya mendapatkan:
Entering C
Entering A
Leaving A
Leaving C
Perhatikan bahwa B
init tidak pernah dipanggil. Jadi sepertinya kecuali saya tahu / mengontrol init tentang kelas yang saya warisi dari ( A
dan B
) saya tidak bisa membuat pilihan yang aman untuk kelas yang saya tulis ( C
).
sumber
Jawaban:
Keduanya bekerja dengan baik. Pendekatan menggunakan
super()
mengarah pada fleksibilitas yang lebih besar untuk subkelas.Dalam pendekatan panggilan langsung,
C.__init__
dapat memanggil keduanyaA.__init__
danB.__init__
.Saat menggunakan
super()
, kelas-kelas perlu dirancang untuk multiple inheritance kooperatif di manaC
panggilansuper
, yang memanggilA
kode yang juga akan memanggilsuper
yang memanggilB
kode itu. Lihat http://rhettinger.wordpress.com/2011/05/26/super-considered-super untuk detail lebih lanjut tentang apa yang dapat dilakukansuper
.[Pertanyaan tanggapan yang kemudian diedit]
Artikel yang direferensikan menunjukkan bagaimana menangani situasi ini dengan menambahkan kelas pembungkus di sekitar
A
danB
. Ada contoh yang berhasil di bagian berjudul "Cara Memasukkan Kelas Non-kooperatif".Orang mungkin berharap bahwa pewarisan berganda lebih mudah, memungkinkan Anda dengan mudah membuat kelas Mobil dan Pesawat untuk mendapatkan FlyingCar, tetapi kenyataannya komponen yang dirancang secara terpisah sering membutuhkan adaptor atau pembungkus sebelum dipasang bersama dengan mulus seperti yang kita inginkan :-)
Satu pemikiran lain: jika Anda tidak senang menyusun fungsi menggunakan banyak pewarisan, Anda dapat menggunakan komposisi untuk kontrol penuh atas metode mana yang dipanggil pada kesempatan mana.
sumber
super().__init__()
pendekatan. Jika saya meneleponA.__init__()
danB.__init__()
langsung, maka (jika A dan B melakukan panggilansuper
) saya mendapatkan init B dipanggil beberapa kali.Jawaban atas pertanyaan Anda tergantung pada satu aspek yang sangat penting: Apakah kelas dasar Anda dirancang untuk pewarisan berganda?
Ada 3 skenario berbeda:
Kelas dasar adalah kelas mandiri, yang tidak terkait.
Jika kelas dasar Anda adalah entitas terpisah yang mampu berfungsi secara independen dan mereka tidak mengenal satu sama lain, mereka tidak dirancang untuk pewarisan berganda. Contoh:
Penting: Perhatikan bahwa tidak satu
Foo
punBar
panggilansuper().__init__()
! Inilah sebabnya mengapa kode Anda tidak berfungsi dengan benar. Karena cara kerja pewarisan intan dalam python, kelas yang kelas dasarnyaobject
tidak boleh dipanggilsuper().__init__()
. Seperti yang Anda perhatikan, hal itu akan merusak banyak warisan karena Anda akhirnya memanggil kelas lain__init__
daripadaobject.__init__()
. ( Disclaimer: Menghindarisuper().__init__()
diobject
-subclasses adalah rekomendasi pribadi saya dan tidak berarti yang telah disepakati konsensus dalam komunitas python Sebagian orang memilih untuk digunakan.super
Di setiap kelas, dengan alasan bahwa Anda selalu dapat menulis adapter jika kelas tidak berperilaku seperti Anda harapkan.)Ini juga berarti bahwa Anda tidak boleh menulis kelas yang mewarisi
object
dan tidak memiliki__init__
metode. Tidak mendefinisikan__init__
metode sama sekali memiliki efek yang sama dengan meneleponsuper().__init__()
. Jika kelas Anda mewarisi langsung dariobject
, pastikan untuk menambahkan konstruktor kosong seperti:Bagaimanapun, dalam situasi ini, Anda harus memanggil setiap induk konstruktor secara manual. Ada dua cara untuk melakukan ini:
Tanpa
super
Dengan
super
Masing-masing dari dua metode ini memiliki kelebihan dan kekurangan. Jika Anda menggunakan
super
, kelas Anda akan mendukung injeksi ketergantungan . Di sisi lain, lebih mudah melakukan kesalahan. Misalnya jika Anda mengubah urutanFoo
danBar
(sukaclass FooBar(Bar, Foo)
), Anda harus memperbaruisuper
panggilan yang cocok. Tanpasuper
Anda tidak perlu khawatir tentang ini, dan kode jauh lebih mudah dibaca.Salah satu kelas adalah mixin.
Sebuah mixin adalah kelas yang dirancang untuk digunakan dengan multiple inheritance. Ini berarti kita tidak perlu memanggil kedua konstruktor induk secara manual, karena mixin akan secara otomatis memanggil konstruktor kedua untuk kita. Karena kita hanya perlu memanggil konstruktor tunggal kali ini, kita dapat melakukannya dengan
super
menghindari keharusan membuat kode nama kelas induk.Contoh:
Detail penting di sini adalah:
super().__init__()
dan melewati argumen apa pun yang diterimanya.class FooBar(FooMixin, Bar)
. Jika urutan kelas dasar salah, konstruktor mixin tidak akan pernah dipanggil.Semua kelas dasar dirancang untuk pewarisan kooperatif.
Kelas yang dirancang untuk pewarisan kooperatif sangat mirip dengan mixin: Mereka melewati semua argumen yang tidak digunakan ke kelas berikutnya. Seperti sebelumnya, kita hanya perlu menelepon
super().__init__()
dan semua konstruktor induk akan dipanggil berantai.Contoh:
Dalam hal ini, urutan kelas induk tidak menjadi masalah. Kita mungkin juga mewarisi dari
CoopBar
dulu, dan kodenya akan tetap sama. Tapi itu hanya benar karena semua argumen dilewatkan sebagai argumen kata kunci. Menggunakan argumen posisional akan membuatnya mudah untuk mendapatkan urutan argumen yang salah, sehingga biasanya kelas koperasi hanya menerima argumen kata kunci.Ini juga merupakan pengecualian terhadap aturan yang saya sebutkan sebelumnya: Keduanya
CoopFoo
danCoopBar
mewarisi dariobject
, tetapi mereka masih memanggilsuper().__init__()
. Jika tidak, tidak akan ada warisan kooperatif.Intinya: Implementasi yang benar tergantung pada kelas yang Anda warisi.
Konstruktor adalah bagian dari antarmuka publik kelas. Jika kelas dirancang sebagai mixin atau untuk warisan koperasi, itu harus didokumentasikan. Jika dokumen tidak menyebutkan hal semacam itu, aman untuk mengasumsikan bahwa kelas tidak dirancang untuk pewarisan berganda.
sumber
super().__init__(*args, **kwargs)
ke dalam mixin dan menulisnya terlebih dahulu. Sangat masuk akal.Baik pendekatan ("gaya baru" atau "gaya lama") akan berfungsi jika Anda memiliki kendali atas kode sumber untuk
A
danB
. Jika tidak, penggunaan kelas adaptor mungkin diperlukan.Kode sumber dapat diakses: Penggunaan "gaya baru" dengan benar
Di sini, metode resolusi pesanan (MRO) menentukan berikut ini:
C(A, B)
menentukanA
terlebih dahulu, laluB
. MRO adalahC -> A -> B -> object
.super(A, self).__init__()
terus sepanjang rantai MRO dimulai padaC.__init__
keB.__init__
.super(B, self).__init__()
terus sepanjang rantai MRO dimulai padaC.__init__
keobject.__init__
.Anda bisa mengatakan bahwa case ini dirancang untuk multiple inheritance .
Kode sumber dapat diakses: Penggunaan "gaya lama" dengan benar
Di sini, MRO tidak penting, karena
A.__init__
danB.__init__
disebut secara eksplisit.class C(B, A):
akan bekerja dengan baik.Meskipun case ini tidak "dirancang" untuk multiple inheritance dengan style baru seperti sebelumnya, multiple inheritance masih dimungkinkan.
Sekarang, bagaimana jika
A
danB
berasal dari perpustakaan pihak ketiga - yaitu, Anda tidak memiliki kendali atas kode sumber untukA
danB
? Jawaban singkat: Anda harus merancang kelas adaptor yang mengimplementasikansuper
panggilan yang diperlukan , kemudian menggunakan kelas kosong untuk mendefinisikan MRO (lihat artikel Raymond Hettinger padasuper
- terutama bagian, "Cara Memasukkan Kelas Non-kooperatif").Orang tua pihak ketiga:
A
tidak menerapkansuper
;B
tidakKelas
Adapter
mengimplementasikansuper
sehinggaC
dapat mendefinisikan MRO, yang ikut bermain ketikasuper(Adapter, self).__init__()
dieksekusi.Dan bagaimana jika sebaliknya?
Orang tua pihak ketiga:
A
implementasisuper
;B
tidakPola yang sama di sini, kecuali urutan eksekusi diaktifkan
Adapter.__init__
;super
panggilan dulu, lalu panggilan eksplisit. Perhatikan bahwa setiap case dengan orang tua pihak ketiga membutuhkan kelas adaptor yang unik.Meskipun Anda dapat menangani kasus di mana Anda tidak mengontrol kode sumber
A
danB
dengan menggunakan kelas adaptor, memang benar bahwa Anda harus tahu bagaimana init dari kelas induk menerapkansuper
(jika sama sekali) untuk melakukannya.sumber
Seperti yang dikatakan Raymond dalam jawabannya, panggilan langsung ke
A.__init__
danB.__init__
berfungsi dengan baik, dan kode Anda akan terbaca.Namun, itu tidak menggunakan tautan warisan antara
C
dan kelas-kelas itu. Memanfaatkan tautan itu memberi Anda lebih banyak konsistensi dan membuat refactoring akhirnya lebih mudah dan lebih sedikit rawan kesalahan. Contoh cara melakukannya:sumber
Artikel ini membantu menjelaskan beberapa pewarisan kooperatif:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
Ini menyebutkan metode
mro()
yang berguna yang menunjukkan kepada Anda urutan resolusi metode. Dalam contoh 2 Anda, di mana Anda meneleponsuper
diA
, yangsuper
panggilan terus di dalam MRO. Kelas berikutnya dalam urutannya adalahB
, inilah mengapaB
init disebut pertama kali.Inilah artikel yang lebih teknis dari situs python resmi:
http://www.python.org/download/releases/2.3/mro/
sumber
Jika Anda mengalikan kelas sub-kelas dari perpustakaan pihak ketiga, maka tidak, tidak ada pendekatan buta untuk memanggil kelas dasar
__init__
metode (atau metode lain) yang benar-benar berfungsi terlepas dari bagaimana kelas dasar diprogram.super
membuatnya mungkin untuk kelas menulis dirancang untuk kooperatif menerapkan metode sebagai bagian dari pohon warisan beberapa kompleks yang tidak perlu diketahui penulis kelas. Tetapi tidak ada cara untuk menggunakannya dengan benar mewarisi dari kelas sewenang-wenang yang mungkin atau mungkin tidak digunakansuper
.Pada dasarnya, apakah kelas dirancang untuk sub-kelas menggunakan
super
atau dengan panggilan langsung ke kelas dasar adalah properti yang merupakan bagian dari "antarmuka publik" kelas, dan harus didokumentasikan seperti itu. Jika Anda menggunakan perpustakaan pihak ketiga dengan cara yang diharapkan oleh pembuat perpustakaan dan perpustakaan memiliki dokumentasi yang masuk akal, biasanya akan memberi tahu Anda apa yang harus Anda lakukan untuk mensubklasifikasikan hal-hal tertentu. Jika tidak, maka Anda harus melihat kode sumber untuk kelas yang Anda sub-klasifikasi dan melihat apa konvensi pemanggilan kelas-dasar mereka. Jika Anda menggabungkan beberapa kelas dari satu atau lebih perpustakaan pihak ketiga dengan cara yang tidak diharapkan oleh penulis perpustakaan , maka mungkin tidak mungkin untuk secara konsisten memanggil metode kelas super sama sekali; jika kelas A adalah bagian dari hierarki menggunakansuper
dan kelas B adalah bagian dari hierarki yang tidak menggunakan super, maka tidak ada opsi yang dijamin untuk bekerja. Anda harus memikirkan strategi yang sesuai untuk setiap kasus tertentu.sumber