Mengapa metode superclass __init__ tidak secara otomatis dipanggil?


155

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'

2
Anda dapat menulis dekorator untuk mewarisi __init__metode ini, dan bahkan mungkin secara otomatis mencari subkelas dan menghiasnya.
Osa

2
@ Ya, terdengar ide yang sangat bagus. Anda ingin menggambarkan sedikit lebih banyak pada bagian dekorator?
Diansheng

1
@ Ya, ya jelaskan lebih banyak!
Charlie Parker

Jawaban:


163

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__! -).


7
Terpilih karena bagi saya, manfaat sebenarnya adalah kemampuan yang Anda sebutkan untuk memanggil superclass's _ init () _ pada setiap titik dalam inisialisasi subclass (atau tidak sama sekali).
kindall

53
-1 untuk " __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!
Ben

8
Saya juga berpikir ini adalah pernyataan yang agak tidak masuk akal: " membangun semua superclasses ... jelas merupakan bagian dari mengatakan Anda sedang membangun instance subclass, yang jelas bukan kasus untuk menginisialisasi ". Tidak ada dalam konstruksi / inisialisasi kata-kata yang membuat ini "jelas" kepada siapa pun. Dan __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.
Ben

36
Bahkan, dari python docs untuk __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.
Ben

3
Dalam terminologi Python / Java, __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.
Elazar

36

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.

class myFile(object):
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
class readFile(myFile):
    def __init__(self, filename):
        super(readFile, self).__init__(filename, "r")
class tempFile(myFile):
    def __init__(self, mode):
        super(tempFile, self).__init__("/tmp/file", mode)
class wordsFile(myFile):
    def __init__(self, language):
        super(wordsFile, self).__init__("/usr/share/dict/%s" % language, "r")

Ini berlaku untuk semua metode turunan, bukan hanya __init__.


6
Apakah contoh ini menawarkan sesuatu yang istimewa? bahasa statis dapat melakukan ini juga
jean

18

Java dan C ++ mengharuskan konstruktor kelas dasar dipanggil karena tata letak memori.

Jika Anda memiliki kelas BaseClassdengan anggota field1, dan Anda membuat kelas baru SubClassyang menambahkan anggota field2, maka instance SubClassberisi ruang untuk field1dan field2. Anda memerlukan konstruktor BaseClassuntuk diisi field1, kecuali Anda mengharuskan semua kelas pewarisan untuk mengulang BaseClassinisialisasi pada konstruktor mereka sendiri. Dan jika field1bersifat pribadi, maka mewarisi kelas tidak dapat diinisialisasi field1.

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 BaseClassmetode dan atribut yang ditambahkan oleh kode dari suatu SubClassmetode.

Jika, seperti biasa, SubClasssebenarnya ingin semua BaseClassinvarian diatur sebelum melanjutkan untuk melakukan kustomisasi sendiri, maka ya Anda bisa memanggil BaseClass.__init__()(atau menggunakan super, 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 memanggil BaseClass.__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 atribut self. 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.


2
Adapun C ++, tidak ada hubungannya dengan "tata letak memori". Model inisialisasi yang sama dengan penawaran Python dapat diimplementasikan dalam C ++. Satu-satunya alasan mengapa konstruksi / penghancuran adalah cara mereka dalam C ++ adalah karena keputusan desain untuk menyediakan fasilitas yang dapat diandalkan dan berperilaku baik untuk manajemen sumber daya (RAII), yang juga dapat dihasilkan secara otomatis (artinya lebih sedikit kode dan kesalahan manusia) oleh kompiler karena aturan untuk mereka (urutan panggilannya) didefinisikan dengan ketat. Tidak yakin tetapi Java kemungkinan besar hanya mengikuti pendekatan ini sebagai bahasa lain seperti POLA C.
Alexander Shukaev

10

"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?


8
Saya setuju dengan Anda untuk sebagian besar, tetapi aturan Java sebenarnya cukup sederhana: konstruktor no-arg dipanggil kecuali Anda secara spesifik meminta yang lain.
Laurence Gonsalves

1
@Laurence - Apa yang terjadi ketika kelas induk tidak mendefinisikan konstruktor no-arg? Apa yang terjadi ketika konstruktor no-arg dilindungi atau pribadi?
Mike Axiak

8
Hal yang persis sama yang akan terjadi jika Anda mencoba menyebutnya secara eksplisit.
Laurence Gonsalves

8

Seringkali subkelas memiliki parameter tambahan yang tidak dapat diteruskan ke superclass.


7

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 ...


Ini jawaban yang tepat. Python perlu mendefinisikan semantik argumen yang lewat antara subclass dan superclass. Sayang sekali jawaban ini tidak dibatalkan. Mungkin jika itu menunjukkan masalah dengan beberapa contoh?
Gary Weiss

5

Untuk menghindari kebingungan, perlu diketahui bahwa Anda dapat memanggil __init__()metode base_class jika child_class tidak memiliki __init__()kelas.

Contoh:

class parent:
  def __init__(self, a=1, b=0):
    self.a = a
    self.b = b

class child(parent):
  def me(self):
    pass

p = child(5, 4)
q = child(7)
z= child()

print p.a # prints 5
print q.b # prints 0
print z.a # prints 1

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

    class child(parent):
      def __init__(self):
        pass
      def me(self):
        pass

    p = child(5, 4) # Error: constructor gets one argument 3 is provided.
    q = child(7)  # Error: constructor gets one argument 2 is provided.

    z= child()
    print z.a # Error: No attribute named as a can be found.

3

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 adalah dostuffsebaliknya, apakah Anda masih ingin Python secara otomatis memanggil fungsi yang sesuai di kelas induk?


2

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 dengan B.__init__(). membuat panggilan eksplisit berarti seorang programmer dapat memiliki mis mendefinisikan B.__init__()dengan parameter yang sama sekali berbeda, melakukan beberapa perhitungan dengan data itu, memanggil A.__init__()dengan argumen yang sesuai untuk metode itu, dan kemudian melakukan beberapa postprocessing. fleksibilitas semacam ini akan canggung untuk dicapai jika A.__init__()akan dipanggil B.__init__()secara implisit, baik sebelum B.__init__()dieksekusi atau tepat setelah itu.

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.