Mengapa perancang Python memutuskan bahwa __init__()
metode subclass tidak otomatis memanggil __init__()
metode superclasses mereka, seperti dalam beberapa bahasa lain? Apakah idiom Pythonic dan direkomendasikan benar-benar seperti berikut?
class Superclass(object):
def __init__(self):
print 'Do something'
class Subclass(Superclass):
def __init__(self):
super(Subclass, self).__init__()
print 'Do something else'
python
inheritance
subclass
delegation
superclass
jrdioko
sumber
sumber
__init__
metode ini, dan bahkan mungkin secara otomatis mencari subkelas dan menghiasnya.Jawaban:
Perbedaan penting antara konstruktor
__init__
bahasa Python dan bahasa lainnya adalah bahwa itu bukan konstruktor: itu adalah penginisialisasi ( konstruktor yang sebenarnya (jika ada, tetapi, lihat nanti ;-) adalah dan bekerja sama sekali berbeda lagi). Walaupun membangun semua kacamata super (dan, tidak diragukan lagi, melakukannya "sebelum" Anda terus membangun ke bawah) jelas merupakan bagian dari mengatakan Anda sedang membangun instance subclass, yang jelas bukan kasus untuk menginisialisasi__init__
__new__
, karena ada banyak kasus penggunaan di mana inisialisasi superclasses perlu dilewati, diubah, dikendalikan - terjadi, jika sama sekali, "di tengah" inisialisasi subkelas, dan sebagainya.Pada dasarnya, delegasi super-kelas initializer tidak otomatis dalam Python untuk alasan yang sama delegasi tersebut juga tidak otomatis untuk setiap metode lain - dan catatan bahwa mereka "bahasa lain" tidak melakukan otomatis delegasi super-kelas untuk setiap Metode lain baik ... hanya untuk konstruktor (dan jika berlaku, destruktor), yang, seperti yang saya sebutkan, bukan apa itu Python
__init__
. (Perilaku__new__
juga cukup aneh, meskipun benar-benar tidak berhubungan langsung dengan pertanyaan Anda, karena__new__
konstruktor yang sedemikian aneh sehingga tidak perlu membangun apa pun - dapat dengan baik mengembalikan contoh yang ada, atau bahkan non-instance ... jelas Python menawarkan banyak hallebih banyak kendali atas mekanika daripada "bahasa lain" yang ada dalam pikiran Anda, yang juga termasuk tidak memiliki delegasi otomatis__new__
! -).sumber
__init__
bukan konstruktor ... konstruktor yang sebenarnya ... adalah__new__
". Seperti yang Anda perhatikan,__new__
berperilaku tidak seperti konstruktor dari bahasa lain.__init__
sebenarnya sangat mirip (disebut saat pembuatan objek baru, setelah objek dialokasikan, untuk mengatur variabel anggota pada objek baru), dan hampir selalu merupakan tempat untuk mengimplementasikan fungsionalitas yang dalam bahasa lain Anda akan memasukkan sebuah konstruktor. Jadi sebut saja konstruktor!__new__
juga tidak secara otomatis memanggil superclass__new__
. Jadi, klaim Anda bahwa perbedaan utama adalah bahwa konstruksi harus melibatkan konstruksi kacamata super, sedangkan inisialisasi tidak bertentangan dengan klaim Anda yang__new__
merupakan konstruktor.__init__
di docs.python.org/reference/datamodel.html#basic-customization : "Sebagai kendala khusus pada konstruktor , tidak ada nilai yang dapat dikembalikan, dengan melakukan hal itu akan menyebabkan TypeError dinaikkan saat runtime "(penekanan milikku). Jadi di sana, itu resmi,__init__
adalah konstruktor.__init__
disebut konstruktor. Konstruktor ini adalah fungsi inisialisasi dipanggil setelah objek telah sepenuhnya dibangun dan diinisialisasi ke keadaan default termasuk tipe runtime terakhirnya. Ini tidak setara dengan konstruktor C ++, yang dipanggil pada objek yang diketik secara statis, dialokasikan dengan keadaan tidak terdefinisi. Ini juga berbeda dari__new__
, jadi kami benar-benar memiliki setidaknya empat jenis fungsi alokasi / konstruksi / inisialisasi yang berbeda. Bahasa menggunakan terminologi campuran, dan bagian penting adalah perilaku dan bukan terminologi.Saya agak malu ketika orang-orang nuri "Zen of Python", seolah-olah itu pembenaran untuk apa pun. Ini adalah filosofi desain; keputusan desain tertentu selalu dapat dijelaskan dalam istilah yang lebih spesifik - dan itu harus, atau "Zen Python" menjadi alasan untuk melakukan apa pun.
Alasannya sederhana: Anda tidak perlu membangun kelas turunan dengan cara yang sama sekali mirip dengan cara Anda membangun kelas dasar. Anda mungkin memiliki lebih banyak parameter, lebih sedikit, mereka mungkin dalam urutan yang berbeda atau tidak terkait sama sekali.
Ini berlaku untuk semua metode turunan, bukan hanya
__init__
.sumber
Java dan C ++ mengharuskan konstruktor kelas dasar dipanggil karena tata letak memori.
Jika Anda memiliki kelas
BaseClass
dengan anggotafield1
, dan Anda membuat kelas baruSubClass
yang menambahkan anggotafield2
, maka instanceSubClass
berisi ruang untukfield1
danfield2
. Anda memerlukan konstruktorBaseClass
untuk diisifield1
, kecuali Anda mengharuskan semua kelas pewarisan untuk mengulangBaseClass
inisialisasi pada konstruktor mereka sendiri. Dan jikafield1
bersifat pribadi, maka mewarisi kelas tidak dapat diinisialisasifield1
.Python bukan Java atau C ++. Semua instance dari semua kelas yang ditentukan pengguna memiliki 'bentuk' yang sama. Mereka pada dasarnya hanya kamus di mana atribut dapat dimasukkan. Sebelum inisialisasi dilakukan, semua instance dari semua kelas yang ditentukan pengguna hampir persis sama ; mereka hanya tempat untuk menyimpan atribut yang belum menyimpan apa pun.
Jadi masuk akal bagi subclass Python untuk tidak memanggil konstruktor kelas dasarnya. Itu bisa saja menambahkan atribut itu sendiri jika mau. Tidak ada ruang yang disediakan untuk sejumlah bidang tertentu untuk setiap kelas dalam hierarki, dan tidak ada perbedaan antara atribut yang ditambahkan oleh kode dari suatu
BaseClass
metode dan atribut yang ditambahkan oleh kode dari suatuSubClass
metode.Jika, seperti biasa,
SubClass
sebenarnya ingin semuaBaseClass
invarian diatur sebelum melanjutkan untuk melakukan kustomisasi sendiri, maka ya Anda bisa memanggilBaseClass.__init__()
(atau menggunakansuper
, tapi itu rumit dan terkadang memiliki masalah sendiri). Tetapi Anda tidak harus melakukannya. Dan Anda dapat melakukannya sebelum, atau setelah, atau dengan berbagai argumen. Sial, jika Anda mau, Anda dapat memanggilBaseClass.__init__
dari metode lain sepenuhnya dari__init__
; mungkin Anda memiliki beberapa hal inisialisasi malas malas terjadi.Python mencapai fleksibilitas ini dengan menjaga hal-hal sederhana. Anda menginisialisasi objek dengan menulis
__init__
metode yang menetapkan atributself
. Itu dia. Itu berperilaku persis seperti metode, karena itu adalah metode. Tidak ada aturan aneh dan tidak intuitif lainnya tentang hal-hal yang harus dilakukan terlebih dahulu, atau hal-hal yang secara otomatis akan terjadi jika Anda tidak melakukan hal-hal lain. Satu-satunya tujuan yang perlu dilayaninya adalah menjadi pengait untuk dieksekusi selama inisialisasi objek untuk menetapkan nilai atribut awal, dan tidak hanya itu. Jika Anda ingin melakukan hal lain, Anda secara eksplisit menuliskannya dalam kode Anda.sumber
"Eksplisit lebih baik daripada implisit." Ini alasan yang sama yang mengindikasikan bahwa kita harus secara eksplisit menulis 'diri'.
Saya pikir pada akhirnya itu bermanfaat - dapatkah Anda membaca semua aturan yang dimiliki Java tentang memanggil konstruktor superclasses?
sumber
Seringkali subkelas memiliki parameter tambahan yang tidak dapat diteruskan ke superclass.
sumber
Saat ini, kami memiliki halaman yang agak panjang yang menggambarkan urutan resolusi metode jika beberapa pewarisan: http://www.python.org/download/releases/2.3/mro/
Jika konstruktor dipanggil secara otomatis, Anda perlu halaman lain setidaknya dengan panjang yang sama menjelaskan urutan yang terjadi. Itu akan menjadi neraka ...
sumber
Untuk menghindari kebingungan, perlu diketahui bahwa Anda dapat memanggil
__init__()
metode base_class jika child_class tidak memiliki__init__()
kelas.Contoh:
Bahkan MRO di python akan mencari
__init__()
di kelas induk ketika tidak dapat menemukannya di kelas anak-anak. Anda perlu memanggil konstruktor kelas induk secara langsung jika Anda sudah memiliki__init__()
metode di kelas anak-anak.Misalnya kode berikut akan mengembalikan kesalahan: kelas induk: def init (self, a = 1, b = 0): self.a = a self.b = b
sumber
Mungkin
__init__
metode yang perlu ditimpa oleh subclass. Kadang-kadang subclass membutuhkan fungsi induk untuk dijalankan sebelum mereka menambahkan kode khusus kelas, dan di lain waktu mereka perlu mengatur variabel instance sebelum memanggil fungsi induk. Karena tidak ada cara Python mungkin tahu kapan akan paling tepat untuk memanggil fungsi-fungsi itu, seharusnya tidak menebak.Jika itu tidak mempengaruhi Anda, anggap itu
__init__
adalah fungsi lain Jika fungsi yang dimaksud adalahdostuff
sebaliknya, apakah Anda masih ingin Python secara otomatis memanggil fungsi yang sesuai di kelas induk?sumber
Saya percaya satu pertimbangan yang sangat penting di sini adalah bahwa dengan panggilan otomatis untuk
super.__init__()
, Anda melarang, dengan desain, ketika metode inisialisasi dipanggil, dan dengan argumen apa. menghindari panggilan itu secara otomatis, dan mengharuskan programmer untuk secara eksplisit melakukan panggilan itu, memerlukan banyak fleksibilitas.setelah semua, hanya karena kelas B berasal dari kelas A tidak berarti
A.__init__()
dapat atau harus dipanggil dengan argumen yang sama denganB.__init__()
. membuat panggilan eksplisit berarti seorang programmer dapat memiliki mis mendefinisikanB.__init__()
dengan parameter yang sama sekali berbeda, melakukan beberapa perhitungan dengan data itu, memanggilA.__init__()
dengan argumen yang sesuai untuk metode itu, dan kemudian melakukan beberapa postprocessing. fleksibilitas semacam ini akan canggung untuk dicapai jikaA.__init__()
akan dipanggilB.__init__()
secara implisit, baik sebelumB.__init__()
dieksekusi atau tepat setelah itu.sumber