__init __ () ควรเรียก __init __ () ของคลาสผู้ปกครองหรือไม่


133

ฉันใช้สิ่งนั้นใน Objective-C ฉันมีโครงสร้างนี้:

- (void)init {
    if (self = [super init]) {
        // init class
    }
    return self;
}

Python ควรเรียกการใช้งานของคลาสแม่__init__หรือไม่

class NewClass(SomeOtherClass):
    def __init__(self):
        SomeOtherClass.__init__(self)
        # init class

นี่เป็นจริงหรือเท็จสำหรับ__new__()และ__del__()?

แก้ไข:มีคำถามที่คล้ายกันมาก: การสืบทอดและการแทนที่__init__ใน Python


คุณได้เปลี่ยนรหัสของคุณอย่างมาก ฉันเข้าใจได้ว่าต้นฉบับobjectนั้นพิมพ์ผิด แต่ตอนนี้คุณยังไม่มีsuperชื่อคำถามที่อ้างถึง
SilentGhost

ฉันแค่คิดว่า super ถูกใช้เป็นชื่อสำหรับคลาสผู้ปกครอง ไม่คิดเลยว่าจะมีใครนึกถึงฟังก์ชัน ฉันขอโทษสำหรับความเข้าใจผิดใด ๆ
Georg Schölly

ทำไมไม่ถามคำถาม super call อัตโนมัติstackoverflow.com/questions/3782827/…
Ciro Santilli 郝海东郝海东病六四事件法轮功

คำตอบ:


68

ใน Python การเรียก super-class ' __init__เป็นทางเลือก ถ้าคุณเรียกมันก็เป็นทางเลือกว่าจะใช้superตัวระบุหรือว่าจะตั้งชื่อซุปเปอร์คลาสอย่างชัดเจน:

object.__init__(self)

ในกรณีของออบเจ็กต์การเรียกใช้ super method นั้นไม่จำเป็นอย่างยิ่งเนื่องจาก super method ว่างเปล่า เหมือนกันสำหรับ__del__.

ในทางกลับกันสำหรับ__new__คุณควรเรียก super method และใช้การส่งคืนเป็นอ็อบเจ็กต์ที่สร้างขึ้นใหม่เว้นแต่คุณต้องการส่งคืนสิ่งที่แตกต่างอย่างชัดเจน


ดังนั้นจึงไม่มีข้อตกลงที่จะเรียกการใช้งานของ super?
Georg Schölly

5
ในคลาสแบบเก่าคุณสามารถเรียกใช้ super init ได้ก็ต่อเมื่อคลาส super มีการกำหนดค่าinitไว้ (ซึ่งมักจะไม่มี) ดังนั้นคนทั่วไปมักจะคิดถึงการเรียก super method มากกว่าที่จะทำผิดหลักการ
Martin v.Löwis

1
ถ้าไวยากรณ์ใน python เรียบง่ายเหมือน[super init]กันมันจะธรรมดากว่า เป็นเพียงความคิดที่คาดเดา; โครงสร้างขั้นสูงใน Python 2.x นั้นค่อนข้างอึดอัดสำหรับฉัน
u0b34a0f6ae

ที่นี่ดูเหมือนจะเป็นตัวอย่างที่น่าสนใจ (และอาจขัดแย้งกัน): bytes.com/topic/python/answers/… init
mlvljr

"เป็นทางเลือก" ที่คุณไม่ต้องเรียก แต่ถ้าคุณไม่เรียกมันก็จะไม่ถูกเรียกโดยอัตโนมัติ
McKay

141

หากคุณต้องการบางสิ่งบางอย่างจากซุปเปอร์__init__ที่ต้องทำนอกเหนือจากสิ่งที่กำลังทำในคลาสปัจจุบัน__init__,คุณต้องเรียกมันด้วยตัวเองเพราะมันจะไม่เกิดขึ้นโดยอัตโนมัติ แต่ถ้าคุณไม่ต้องการอะไรจากซุปเปอร์ก็__init__,ไม่จำเป็นต้องเรียกมัน ตัวอย่าง:

>>> class C(object):
        def __init__(self):
            self.b = 1


>>> class D(C):
        def __init__(self):
            super().__init__() # in Python 2 use super(D, self).__init__()
            self.a = 1


>>> class E(C):
        def __init__(self):
            self.a = 1


>>> d = D()
>>> d.a
1
>>> d.b  # This works because of the call to super's init
1
>>> e = E()
>>> e.a
1
>>> e.b  # This is going to fail since nothing in E initializes b...
Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    e.b  # This is going to fail since nothing in E initializes b...
AttributeError: 'E' object has no attribute 'b'

__del__เป็นวิธีเดียวกัน (แต่ระวังการใช้ใน__del__การสรุปผล - ลองใช้คำสั่ง with แทน)

ฉันไม่ค่อยได้ใช้__new__. ฉันทำการเริ่มต้นทั้งหมดใน__init__.


3
คำจำกัดความของคลาส D (C) ต้องได้รับการแก้ไขเช่นนั้นsuper(D,self).__init__()
eyquem

12
super () .__ init __ () ใช้งานได้เฉพาะใน Python 3 เท่านั้นใน Python 2 คุณต้องใช้ super (D, self) __ init __ ()
Jacinda

1
"If you need something from super's init ... " - นี่เป็นคำสั่งที่มีปัญหามากเนื่องจากไม่ใช่กรณีที่คุณ / คลาสย่อยต้องการ "บางสิ่ง" หรือไม่ แต่ชั้นพื้นฐานต้องการบางสิ่งเพื่อที่จะถูกต้องหรือไม่ อินสแตนซ์คลาสพื้นฐานและทำงานได้อย่างถูกต้อง ในฐานะผู้ดำเนินการของคลาสที่ได้รับคลาสพื้นฐานภายในเป็นสิ่งที่คุณไม่สามารถ / ไม่ควรรู้และแม้ว่าคุณจะทำเพราะคุณเขียนทั้งสองหรือภายในเป็นเอกสารการออกแบบของฐานอาจเปลี่ยนแปลงในอนาคตและพังเพราะเขียนไม่ดี คลาสที่ได้รับ ดังนั้นให้แน่ใจเสมอว่าคลาสพื้นฐานเริ่มต้นอย่างสมบูรณ์
นิค

108

ในคำตอบของอานนท์:
"ถ้าคุณต้องการบางสิ่งจากซุปเปอร์__init__ที่ต้องทำนอกเหนือจากสิ่งที่กำลังทำอยู่ในคลาสปัจจุบัน__init__คุณต้องเรียกมันว่าตัวเองเพราะมันจะไม่เกิดขึ้นโดยอัตโนมัติ"

มันเหลือเชื่อมาก: เขาใช้ถ้อยคำที่ขัดกับหลักการสืบทอด


ไม่ใช่ว่า"บางสิ่งจาก super __init__ (... ) จะไม่เกิดขึ้นโดยอัตโนมัติ"มันจะเกิดขึ้นโดยอัตโนมัติ แต่มันไม่ได้เกิดขึ้นเพราะ base-class ' __init__ถูกแทนที่ด้วยนิยามของคลาสที่ได้รับ__init__

แล้วทำไมต้องกำหนดคลาสที่ได้รับ ' __init__เนื่องจากมันจะแทนที่สิ่งที่มุ่งเป้าไปที่เมื่อมีคนยอมรับมรดก ??

เป็นเพราะเราจำเป็นต้องกำหนดสิ่งที่ไม่ได้ทำในคลาสพื้นฐาน ' __init__และความเป็นไปได้เดียวที่จะได้รับสิ่งนั้นคือการทำให้การดำเนินการใน__init__ฟังก์ชันที่ได้รับมา'
กล่าวอีกนัยหนึ่งก็__init__คือเราต้องการบางสิ่งบางอย่างในคลาสฐาน ' นอกเหนือจากสิ่งที่จะทำโดยอัตโนมัติในชั้นฐาน' __init__หากสิ่งหลังนี้ไม่ได้ถูกลบล้าง
ไม่ตรงกันข้าม


จากนั้นปัญหาคือคำสั่งที่ต้องการที่มีอยู่ในคลาสพื้นฐาน ' __init__จะไม่เปิดใช้งานอีกต่อไปในช่วงเวลาของการสร้างอินสแตนซ์ ในการสั่งซื้อเพื่อชดเชยการใช้งานนี้สิ่งที่พิเศษจะต้อง: เรียกอย่างชัดเจนฐานระดับ__init__เพื่อที่จะKEEPไม่ได้ที่จะเพิ่มการเริ่มต้นดำเนินการโดยฐานระดับ __init__นั่นคือสิ่งที่กล่าวไว้ในเอกสารอย่างเป็นทางการ:

วิธีการแทนที่ในคลาสที่ได้รับในความเป็นจริงอาจต้องการขยายมากกว่าเพียงแค่แทนที่เมธอดคลาสฐานที่มีชื่อเดียวกัน มีวิธีง่ายๆในการเรียกใช้เมธอดคลาสฐานโดยตรง: เพียงแค่เรียก BaseClassName.methodname (ตัวเองอาร์กิวเมนต์)
http://docs.python.org/tutorial/classes.html#inheritance

นั่นคือเรื่องราวทั้งหมด:

  • เมื่อเป้าหมายคือให้การเริ่มต้นดำเนินการโดยคลาสพื้นฐานนั่นคือการสืบทอดที่บริสุทธิ์ไม่มีอะไรพิเศษที่จำเป็นต้องหลีกเลี่ยงการกำหนด__init__ฟังก์ชันในคลาสที่ได้รับ

  • เมื่อจุดมุ่งหมายคือการแทนที่การเริ่มต้นที่ดำเนินการโดยคลาสพื้นฐาน__init__จะต้องกำหนดไว้ในคลาสที่ได้รับ

  • เมื่อจุดมุ่งหมายคือการเพิ่มกระบวนการไปยังการกำหนดค่าเริ่มต้นที่ดำเนินการโดยคลาสพื้นฐาน__init__ ต้องกำหนดคลาสที่ได้รับซึ่งประกอบด้วยการเรียกอย่างชัดเจนไปยังคลาสฐาน__init__


สิ่งที่ฉันรู้สึกประหลาดใจในโพสต์ของอานนท์ไม่ใช่แค่การแสดงออกที่ตรงกันข้ามกับทฤษฎีการถ่ายทอดทางพันธุกรรมเท่านั้น แต่ยังมีผู้ชาย 5 คนที่ผ่านการโหวตโดยไม่ต้องเปลี่ยนทรงผมและยิ่งไปกว่านั้นไม่มีใครตอบสนองใน 2 ปีใน กระทู้ที่มีหัวข้อที่น่าสนใจต้องอ่านบ่อย


1
ฉันแน่ใจว่าโพสต์นี้จะได้รับการโหวต ฉันกลัวว่าจะไม่มีคำอธิบายมากนักเกี่ยวกับเหตุผลว่าทำไม การลงคะแนนทำได้ง่ายกว่าการวิเคราะห์ข้อความที่ไม่สามารถเข้าใจได้ ฉันพยายามทำความเข้าใจโพสต์ของอานนท์อยู่นานก่อนที่จะรู้ในที่สุดว่ามันเขียนอย่างเจาะจงและไม่น่าเชื่อถือมากนัก บางทีอาจตีความได้ว่าเหมาะสมสำหรับคนที่รู้เรื่องมรดก แต่ฉันพบว่ามันสับสนเมื่ออ่านโดยใครบางคนที่มีความคิดที่ไม่มั่นคงเกี่ยวกับมรดกซึ่งเป็นเรื่องที่ไม่ชัดเจนเหมือนน้ำในหินทั่วไป
eyquem

2
"ฉันแน่ใจว่าโพสต์นี้จะได้รับการโหวตขึ้น ... " ปัญหาหลักคือคุณไปงานปาร์ตี้ช้าไปหน่อยและคนส่วนใหญ่อาจไม่อ่านเกินคำตอบสองสามข้อแรก คำอธิบายที่ดีโดยทาง +1
Gerrat

คำตอบที่ดีคือ
Trilarion

3
คุณพลาดคำสำคัญ "นอกจากนี้" ในประโยคที่คุณยกมาจากแอรอน คำพูดของแอรอนถูกต้องและตรงกับสิ่งที่คุณพูด
GreenAsJade

1
นี่เป็นคำอธิบายแรกที่ทำให้ตัวเลือกการออกแบบของ python เหมาะสม
Joseph Garvin

20

แก้ไข : (หลังจากเปลี่ยนรหัส)
ไม่มีวิธีใดที่เราจะบอกคุณได้ว่าคุณต้องการหรือไม่ต้องโทรหาผู้ปกครองของคุณ__init__(หรือฟังก์ชันอื่นใด) เห็นได้ชัดว่าการสืบทอดจะทำงานได้โดยไม่ต้องมีการโทร ทุกอย่างขึ้นอยู่กับตรรกะของโค้ดของคุณ: ตัวอย่างเช่นหากคุณ__init__ทำทั้งหมดในคลาสหลักคุณสามารถข้ามคลาสย่อยได้__init__ทั้งหมด

พิจารณาตัวอย่างต่อไปนี้:

>>> class A:
    def __init__(self, val):
        self.a = val


>>> class B(A):
    pass

>>> class C(A):
    def __init__(self, val):
        A.__init__(self, val)
        self.a += val


>>> A(4).a
4
>>> B(5).a
5
>>> C(6).a
12

ฉันลบ super call ออกจากตัวอย่างของฉันฉันอยากรู้ว่าควรเรียกการใช้งานinitของคลาสแม่หรือไม่
Georg Schölly

คุณอาจต้องการแก้ไขชื่อแล้ว แต่คำตอบของฉันยังคงยืนอยู่
SilentGhost

5

ไม่มีกฎที่ยากและรวดเร็ว เอกสารประกอบสำหรับคลาสควรระบุว่าคลาสย่อยควรเรียกใช้เมธอด superclass หรือไม่ บางครั้งคุณต้องการแทนที่พฤติกรรมระดับซูเปอร์คลาสอย่างสมบูรณ์และในบางครั้งก็เพิ่มขึ้นเช่นเรียกรหัสของคุณเองก่อนและ / หรือหลังการโทรระดับซูเปอร์คลาส

อัปเดต:ตรรกะพื้นฐานเดียวกันนี้ใช้กับการเรียกเมธอดใด ๆ บางครั้งผู้สร้างจำเป็นต้องได้รับการพิจารณาเป็นพิเศษ (เนื่องจากมักตั้งค่าสถานะที่กำหนดพฤติกรรม) และผู้ทำลายเนื่องจากตัวสร้างแบบขนาน (เช่นในการจัดสรรทรัพยากรเช่นการเชื่อมต่อฐานข้อมูล) แต่อาจใช้เช่นเดียวกันกับrender()วิธีการของวิดเจ็ต

อัปเดตเพิ่มเติม: OPP คืออะไร? คุณหมายถึง OOP? ไม่ - คลาสย่อยมักจำเป็นต้องรู้บางอย่างเกี่ยวกับการออกแบบของซูเปอร์คลาส ไม่ใช่รายละเอียดการใช้งานภายใน - แต่เป็นสัญญาพื้นฐานที่ superclass มีกับลูกค้า (โดยใช้คลาส) สิ่งนี้ไม่ได้ละเมิดหลักการ OOP แต่อย่างใด นั่นเป็นเหตุผลprotectedที่แนวคิดที่ถูกต้องใน OOP โดยทั่วไป (แน่นอนว่าไม่ใช่ใน Python)


คุณบอกว่าบางครั้งมีคนต้องการโทรหารหัสของตัวเองก่อนที่จะโทรหาซุปเปอร์คลาส ในการดำเนินการนี้จำเป็นต้องมีความรู้เกี่ยวกับการใช้งานคลาสผู้ปกครองซึ่งจะเป็นการละเมิด OPP
Georg Schölly

4

IMO คุณควรเรียกมันว่า ถ้าซูเปอร์คลาสของคุณเป็นobjectคุณไม่ควร แต่ในกรณีอื่นฉันคิดว่ามันเป็นเรื่องพิเศษที่จะไม่เรียกมัน ตามที่คนอื่นตอบไปแล้วว่าสะดวกมากถ้าชั้นเรียนของคุณไม่จำเป็นต้องลบล้าง__init__ตัวเองเช่นเมื่อไม่มีสถานะภายใน (เพิ่มเติม) ให้เริ่มต้น


2

ใช่คุณควรเรียกคลาสพื้นฐานเสมอ __init__อย่างชัดเจนว่าเป็นการฝึกฝนการเขียนโค้ดที่ดี การลืมทำเช่นนี้อาจทำให้เกิดปัญหาเล็กน้อยหรือข้อผิดพลาดเวลาทำงาน นี่เป็นจริงแม้ว่า__init__จะไม่ใช้พารามิเตอร์ใด ๆ ก็ตาม ซึ่งแตกต่างจากภาษาอื่น ๆ ที่คอมไพเลอร์จะเรียกตัวสร้างคลาสพื้นฐานให้คุณโดยปริยาย Python ไม่ทำอย่างนั้น!

เหตุผลหลักในการเรียกคลาสฐานเสมอ_init__คือคลาสพื้นฐานอาจสร้างตัวแปรสมาชิกและเริ่มต้นเป็นค่าเริ่มต้น ดังนั้นหากคุณไม่เรียกใช้คลาสพื้นฐานจะไม่มีการเรียกใช้โค้ดนั้นและคุณจะจบลงด้วยคลาสพื้นฐานที่ไม่มีตัวแปรสมาชิก

ตัวอย่าง :

class Base:
  def __init__(self):
    print('base init')

class Derived1(Base):
  def __init__(self):
    print('derived1 init')

class Derived2(Base):
  def __init__(self):
    super(Derived2, self).__init__()
    print('derived2 init')

print('Creating Derived1...')
d1 = Derived1()
print('Creating Derived2...')
d2 = Derived2()

ภาพพิมพ์นี้ ..

Creating Derived1...
derived1 init
Creating Derived2...
base init
derived2 init

เรียกใช้รหัสนี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.