Neden süper sınıf __init__ yöntemleri otomatik olarak çağrılmıyor?


156

Python tasarımcıları neden alt sınıfların __init__()yöntemlerinin __init__()diğer bazı dillerde olduğu gibi üst sınıflarının yöntemlerini otomatik olarak çağırmadıklarına karar verdiler ? Pythonic ve önerilen deyim gerçekten aşağıdaki gibi mi?

class Superclass(object):
    def __init__(self):
        print 'Do something'

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'

2
__init__Yöntemi devralmak için bir dekoratör yazabilir ve hatta otomatik olarak alt sınıfları arayabilir ve dekore edebilirsiniz.
osa

2
@osa, kulağa gerçekten iyi bir fikir. Dekoratör kısmında biraz daha açıklamak ister misiniz?
Diansheng

1
@osa yes daha açıklamak!
Charlie Parker

Yanıtlar:


162

Python en arasındaki önemli ayrım __init__ve bu diğer diller yapıcıları olmasıdır __init__olduğu değil bir açıklanmıştır: Bir yapıcı başlatıcısı (gerçek yapıcı bakın sonra ;-) olduğunu (varsa ancak __new__) ve işleri tamamen farklı tekrar. İken inşa tüm süper sınıf (ve aşağı doğru inşa devam "önce", hiç şüphesiz, bunu yaparken) açıkçası söylüyorsun parçasıdır inşa , alt sınıfın örneği, açıkça için durum böyle değildir başlatılıyorçünkü üst sınıfların başlatılmasının atlanması, değiştirilmesi, kontrol edilmesi - eğer varsa, alt sınıf başlatmanın "ortasında" ve bunun gibi birçok kullanım durumu vardır.

Temel olarak, başlatıcısı süper sınıf heyeti aynı tür heyeti, otomatik olmayan nedenlerle Python otomatik olmayan herhangi olanlar "diğer diller" herhangi biri için otomatik süper sınıf heyet yapmayın ve not - diğer yöntemler diğer yöntem de ... sadece kurucu (ve varsa yıkıcı) için, bahsettiğim gibi, Python'un ne olduğu değil__init__ . (Davranışı __new__da oldukça tuhaftır, ancak gerçekten doğrudan sorunuzla ilgili değildir, çünkü __new__aslında bir şey inşa etmek zorunda olmadığı o kadar tuhaf bir kurucu - mevcut bir örneği, hatta örnek olmayan bir örneği mükemmel bir şekilde geri döndürebilir ... açıkça Python size çok şey sunuyormekanikte, aklınızdaki "diğer dillerden" daha fazla kontrol vardır ve bu da__new__ kendi içinde otomatik delegasyona sahip değildir ! -).


7
Benim için asıl fayda, alt sınıfın başlatılmasının herhangi bir noktasında (ya da hiç değil) üst sınıfın _ init () _ini çağırmaktan bahsettiğiniz yeteneğidir .
tür

54
-1 için " __init__yapıcı değil ... asıl kurucu ... __new__" dir. Kendiniz belirttiğiniz __new__gibi, diğer dillerden bir kurucu gibi davranmaz. __init__aslında çok benzerdir (yeni bir nesnenin yaratılması sırasında, bu nesne tahsis edildikten sonra , yeni nesne üzerinde üye değişkenleri ayarlamak için çağrılır ) ve neredeyse her zaman diğer dillerde koyacağınız işlevselliği uygulayabileceğiniz yerdir. bir kurucu. Bu yüzden sadece bir yapıcı deyin!
Ben

9
Ben de bunun ne kadar saçma ifadesi olduğunu düşünüyorum: " inşa tüm süper sınıf ... açıkçası söylüyorsun parçasıdır inşa açıkça için durum böyle değil, alt sınıfın örneği, başlatılırken ". İnşaat / başlatma kelimelerinde bunu "herkese açık" yapan hiçbir şey yoktur. Üstelik __new__otomatik olarak üst sınıfı __new__da çağırmaz . Dolayısıyla, temel ayrımın, inşaatın mutlaka üst sınıfların inşasını içerdiği iddiası, ilklendirme__new__ yapıcı olduğu iddianızla tutarsız değildir .
Ben

37
Aslında, piton dokümanlardan için __init__en docs.python.org/reference/datamodel.html#basic-customization : "özel bir kısıtlama olarak kurucular , hiçbir değer döndürülebilir, bir TypeError neden olacaktır Bunu yaparken çalışma zamanında yetiştirilmek üzere "(benimkini vurgulayın). Yani orada, resmi, __init__bir kurucu.
Ben

3
Python / Java terminolojisinde __init__yapıcı olarak adlandırılır. Bu yapıcı, nesne tamamen oluşturulduktan ve son çalışma zamanı türünü içeren varsayılan bir duruma getirildikten sonra çağrılan bir başlatma işlevidir. Tanımlanmamış bir duruma sahip statik olarak yazılan, tahsis edilen bir nesne üzerinde çağrılan C ++ yapıcılarına eşdeğer değildir. Bunlar da farklıdır __new__, bu yüzden gerçekten en az dört farklı tahsis / inşaat / başlatma fonksiyonuna sahibiz. Diller karışık terminoloji kullanır ve önemli kısmı terminoloji değil davranıştır.
Elazar

35

İnsanlar "Python Zen" i papağan gibi bir şey için sanki biraz utanıyorum. Bu bir tasarım felsefesi; belirli tasarım kararları her zaman daha spesifik terimlerle açıklanabilir - bunlar olmalıdır, aksi takdirde "Python Zen" i herhangi bir şey yapmak için bir bahane haline gelir.

Nedeni basit: mutlaka temel sınıfı nasıl oluşturduğunuza benzer bir şekilde türetilmiş bir sınıf oluşturmazsınız. Daha fazla parametreniz olabilir, daha az, farklı bir sırada olabilir veya hiç alakalı olmayabilir.

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

Bu sadece türetilmiş tüm yöntemler için geçerlidir __init__.


7
Bu örnek özel bir şey sunuyor mu? statik diller de bunu yapabilir
jean

Ve bu yüzden? Varsayılan (parametre yok) üst sınıf yapıcısının dolaylı olarak çağrılmasını nasıl engeller? Diğer diller de budur (C ++, Java, vb.).
Maggyero

19

Java ve C ++ , bellek düzeni nedeniyle bir temel sınıf yapıcısının çağrılmasını gerektirir .

Eğer bir sınıf varsa BaseClassbir üyesi ile field1, ve yeni bir sınıf oluşturmak SubClassüye ekler field2, sonra bir örneğini SubClassiçin boşluk içeriyorsa field1ve field2. Sen bir yapıcısı ihtiyaç BaseClassdoldurmak için field1tekrarlamayı tüm miras sınıfları gerektirmediği sürece, BaseClasskendi kurucularınızdaki 'ın başlatma. Ve eğer field1özeldir, o zaman miras sınıflar yapamam initialise field1.

Python Java veya C ++ değildir. Tüm kullanıcı tanımlı sınıfların tüm örnekleri aynı 'şekle' sahiptir. Temel olarak sadece özelliklerin eklenebileceği sözlüklerdir. Herhangi bir başlatma yapılmadan önce, kullanıcı tanımlı tüm sınıfların tüm örnekleri neredeyse tamamen aynıdır ; bunlar henüz saklamayan özellikleri depolayabilecek yerlerdir.

Bu nedenle, bir Python alt sınıfının temel sınıf yapıcısını çağırmaması mükemmeldir. İsterse özniteliklerin kendisini ekleyebilir. Hiyerarşideki her sınıf için belirli sayıda alan için ayrılmış alan yoktur ve bir BaseClassyöntemden kod tarafından eklenen bir özellik ile bir yöntemden kod tarafından eklenen bir özellik arasında fark yoktur SubClass.

Yaygın olduğu gibi, SubClassaslında BaseClasskendi özelleştirmesini yapmadan önce tüm değişmezlerinin ayarlanmasını isterse, evet, sadece arayabilir BaseClass.__init__()(veya kullanabilirsiniz super, ancak bu karmaşık ve bazen kendi sorunları vardır). Ama zorunda değilsin. Ve bunu önce, sonra veya farklı argümanlarla yapabilirsiniz. Cehennem, eğer istersen BaseClass.__init__tamamen başka bir yöntemden çağırabilirsin __init__; belki bazı tuhaf tembel başlatma şeyleri gidiyor.

Python bu esnekliği işleri basit tutarak elde eder. __init__Nitelikleri ayarlayan bir yöntem yazarak nesneleri başlatırsınız self. Bu kadar. Tam olarak bir yöntem gibi davranır, çünkü tam olarak bir yöntemdir. İlk olarak yapılması gerekenler veya başka şeyler yapmazsanız otomatik olarak gerçekleşecek şeyler hakkında başka garip ve sezgisel kurallar yoktur. Hizmet etmesi gereken tek amaç, başlangıç ​​niteliği değerlerini ayarlamak için nesne başlatma sırasında yürütülecek bir kanca olmaktır ve bunu yapar. Başka bir şey yapmasını istiyorsanız, bunu açıkça kodunuza yazarsınız.


2
C ++ gelince, "bellek düzeni" ile ilgisi yoktur. Python'un sunduğu aynı başlatma modeli C ++ 'da uygulanabilirdi. İnşaat / yıkımın C ++ 'da olduğu gibi olmasının tek nedeni, kaynak yönetimi (RAII) için otomatik ve aynı zamanda üretilebilecek güvenilir ve iyi davranan tesisler sağlama tasarım kararından kaynaklanmaktadır (daha az kod ve insan hataları anlamına gelir). onlar için kurallar (çağrı sıraları) titizlikle tanımlandığından derleyiciler tarafından. Emin değilim ama Java büyük olasılıkla başka bir POLA C benzeri dil olarak bu yaklaşımı izledi.
Alexander Shukaev

11

"Açık, örtük olmaktan iyidir." Açıkça 'benlik' yazmamız gerektiğini gösteren aynı mantık.

Sonunda bunun bir fayda olduğunu düşünüyorum - Java'nın üst sınıf yapıcılarını çağırmakla ilgili tüm kuralları okuyabilir misiniz?


8
Çoğunlukla seninle aynı fikirdeyim ama Java'nın kuralı aslında oldukça basit: özellikle farklı bir kural istemedikçe no-arg yapıcısı çağrılır.
Laurence Gonsalves

2
@Laurence - Üst sınıf argüman yapıcı tanımlamazsa ne olur? Arg-no yapıcısı korunduğunda veya özel olduğunda ne olur?
Mike Axiak

8
Açıkça aramaya çalışırsanız, aynı şey olur.
Laurence Gonsalves

7

Genellikle alt sınıf, üst sınıfa geçirilemeyen ekstra parametrelere sahiptir.


Ve bu yüzden? Varsayılan (parametre yok) üst sınıf yapıcısının dolaylı olarak çağrılmasını nasıl engeller? Diğer diller de budur (C ++, Java, vb.).
Maggyero

7

Şu anda, birden fazla kalıtım durumunda yöntem çözümleme sırasını açıklayan oldukça uzun bir sayfamız var: http://www.python.org/download/releases/2.3/mro/

Yapıcılar otomatik olarak çağrıldıysa, gerçekleşme sırasını açıklayan en azından aynı uzunlukta başka bir sayfaya ihtiyacınız olacaktır. Bu cehennem olurdu ...


Bu doğru cevaptı. Python'un alt sınıf ve üst sınıf arasında geçen argüman anlamını tanımlaması gerekir. Bu cevap çok kötü değil. Belki bazı örneklerle sorunu gösterdiyse?
Gary Weiss

5

Karışıklığı önlemek __init__()için child_class'ın bir __init__()sınıfı yoksa base_class yöntemini çağırabileceğinizi bilmek yararlıdır .

Misal:

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

Aslında Python'daki MRO __init__(), alt sınıfta çocuk sınıfında bulamadığında arayacaktır . __init__()Children sınıfında zaten bir yönteminiz varsa, üst sınıf yapıcısını doğrudan çağırmanız gerekir .

Örneğin, aşağıdaki kod bir hata döndürür: class parent: 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

Belki __init__de alt sınıfın geçersiz kılması gereken yöntemdir. Bazen alt sınıflar, sınıfa özel kod eklemeden önce üst öğenin çalışması için, bazen de üst öğenin işlevini çağırmadan önce örnek değişkenleri ayarlaması gerekir. Python'un bu işlevleri çağırmanın ne zaman en uygun olacağını bilmesi mümkün olmadığı için tahmin etmemeliyiz.

Eğer bunlar sizi sallamazsa, bunun __init__sadece bir başka fonksiyon olduğunu düşünün . Söz konusu işlev dostuffbunun yerine, Python'un yine de üst sınıftaki ilgili işlevi otomatik olarak çağırmasını ister misiniz?


2

Burada çok önemli bir husus, otomatik bir çağrı ile super.__init__(), tasarımla, bu başlatma yöntemi çağrıldığında ve hangi argümanlarla yasaklandığınıza inanıyorum. otomatik olarak çağırmak ve programcıdan bu çağrıyı açıkça yapmasını istemek çok fazla esneklik gerektirir.

ne de olsa, B sınıfı A sınıfından türetildiği A.__init__()için, aynı argümanlarla çağrılabilir veya çağrılmalıdır B.__init__(). çağrının açık hale getirilmesi, bir programcının örneğin B.__init__()tamamen farklı parametrelerle tanımlayabileceği , bu verilerle bazı hesaplamalar yapabileceği, A.__init__()bu yönteme uygun argümanlarla çağrı yapabileceği ve daha sonra bazı postprocess yapabileceği anlamına gelir. bu tür bir esneklik , yürütmeden önce veya hemen sonra örtülü A.__init__()olarak çağrılıp çağrılmayacaksa, bu tür bir esneklik elde etmek zor olacaktır .B.__init__()B.__init__()

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.