การสืบทอดและการเอาชนะ __init__ ใน python


129

ฉันอ่าน 'Dive Into Python' และในบทของคลาสมันให้ตัวอย่างนี้:

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

ผู้เขียนบอกว่าถ้าคุณต้องการแทนที่__init__เมธอดคุณต้องเรียกผู้ปกครอง__init__ด้วยพารามิเตอร์ที่ถูก ต้องอย่างชัดเจน

  1. เกิดอะไรขึ้นถ้าFileInfoชั้นนั้นมีมากกว่าหนึ่งชั้นบรรพบุรุษ?
    • ฉันต้องเรียก__init__วิธีการเรียนของบรรพบุรุษทั้งหมดอย่างชัดเจนหรือไม่
  2. นอกจากนี้ฉันต้องทำเช่นนี้กับวิธีอื่นที่ฉันต้องการแทนที่ด้วยหรือไม่?

3
โปรดทราบว่าการโอเวอร์โหลดเป็นแนวคิดที่แยกต่างหากจากการแทนที่
Dana the Sane

คำตอบ:


158

หนังสือเล่มนี้ค่อนข้างเก่าเมื่อเทียบกับการเรียก subclass-superclass นอกจากนี้ยังเป็นวันที่เล็กน้อยเกี่ยวกับคลาสย่อยในตัว

ดูเหมือนว่าทุกวันนี้:

class FileInfo(dict):
    """store file metadata"""
    def __init__(self, filename=None):
        super(FileInfo, self).__init__()
        self["name"] = filename

หมายเหตุดังต่อไปนี้:

  1. เราโดยตรงสามารถซับคลาสตัวในชั้นเรียนเช่นdict, list, tupleฯลฯ

  2. superฟังก์ชั่นจับการติดตามการลง superclasses ของชั้นนี้และเรียกฟังก์ชั่นในตัวเขาอย่างเหมาะสม


5
ฉันควรมองหาหนังสือ / บทช่วยสอนที่ดีขึ้นหรือไม่
liewl

2
ดังนั้นในกรณีที่มีการสืบทอดหลายอัน Super () ติดตามพวกเขาทั้งหมดในนามของคุณหรือไม่?
Dana

dict .__ init __ (ตัวเอง) ที่จริง แต่ไม่มีอะไรผิดปกติ - การเรียก super (... ) เพียงแค่ให้ไวยากรณ์ที่สอดคล้องกันมากขึ้น (ผมไม่แน่ใจว่าวิธีการทำงานให้เป็นมรดกหลายผมคิดว่ามันอาจจะเป็นเพียงพบหนึ่ง superclass init )
เดวิด Z

4
ความตั้งใจของ super () คือมันจัดการหลายมรดก ข้อเสียคือในทางปฏิบัติหลายมรดกยังแบ่งง่ายมาก (ดู < fuhm.net/super-harmful )
sth

2
ใช่ในกรณีที่มีการสืบทอดหลายคลาสและคลาสพื้นฐานโดยใช้อาร์กิวเมนต์ตัวสร้าง
Torsten Marek

18

ในแต่ละคลาสที่คุณต้องรับช่วงคุณสามารถเรียกใช้ลูปของแต่ละคลาสที่ต้องการ init'd เมื่อเริ่มต้นคลาสย่อย ... ตัวอย่างที่สามารถคัดลอกอาจเข้าใจได้ดีกว่า ...

class Female_Grandparent:
    def __init__(self):
        self.grandma_name = 'Grandma'

class Male_Grandparent:
    def __init__(self):
        self.grandpa_name = 'Grandpa'

class Parent(Female_Grandparent, Male_Grandparent):
    def __init__(self):
        Female_Grandparent.__init__(self)
        Male_Grandparent.__init__(self)

        self.parent_name = 'Parent Class'

class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
#---------------------------------------------------------------------------------------#
        for cls in Parent.__bases__: # This block grabs the classes of the child
             cls.__init__(self)      # class (which is named 'Parent' in this case), 
                                     # and iterates through them, initiating each one.
                                     # The result is that each parent, of each child,
                                     # is automatically handled upon initiation of the 
                                     # dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#



g = Female_Grandparent()
print g.grandma_name

p = Parent()
print p.grandma_name

child = Child()

print child.grandma_name

2
ดูเหมือนว่าไม่Child.__init__จำเป็นสำหรับ for loop in เมื่อฉันลบมันออกจากตัวอย่างฉันยังคงพิมพ์ "ยาย" Parentชั้นเรียนเริ่มต้นปู่ย่าตายายไม่ได้รับการจัดการหรือไม่
อดัม

4
ฉันคิดว่า init ของ granparents นั้นจัดการโดย init ของผู้ปกครองแล้วใช่ไหม
johk95

15

คุณไม่จำเป็นต้องเรียก__init__เมธอดของคลาสพื้นฐาน (s) แต่โดยปกติคุณต้องการทำเช่นนี้เพราะคลาสพื้นฐานจะเริ่มต้นสำคัญบางอย่างที่จำเป็นสำหรับการทำงานส่วนที่เหลือของคลาส

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


4
เพื่อความสมบูรณ์ทางเทคนิคบางชั้นเรียนเช่น threading.Thread จะโยนความผิดพลาดยักษ์ถ้าคุณเคยพยายามที่จะหลีกเลี่ยงการเรียกร้องของผู้ปกครองinit
David Berger

5
ฉันพบทั้งหมดนี้ "คุณไม่ต้องโทรหาคอนสตรัคเตอร์ของฐาน" พูดอย่างน่ารำคาญอย่างยิ่ง คุณไม่ต้องเรียกมันในภาษาใด ๆ ที่ฉันรู้ พวกเขาทั้งหมดจะสกรู (หรือไม่) ในลักษณะเดียวกันโดยไม่เริ่มสมาชิก การแนะนำไม่ให้เริ่มต้นคลาสพื้นฐานนั้นผิดในหลายวิธี ถ้าคลาสไม่ต้องการการกำหนดค่าเริ่มต้นตอนนี้มันจะต้องใช้ในอนาคต ตัวสร้างเป็นส่วนหนึ่งของอินเทอร์เฟซของโครงสร้างคลาส / สร้างภาษาและควรใช้อย่างถูกต้อง มันใช้งานได้อย่างถูกต้องคือการเรียกมันในบางครั้งใน Constructor ของคุณ ทำเช่นนั้น
AndreasT

2
"การแนะนำไม่ให้เริ่มคลาสพื้นฐานนั้นผิดในหลาย ๆ วิธี" ไม่มีใครแนะนำให้เริ่มต้นคลาสพื้นฐาน อ่านคำตอบอย่างระมัดระวัง มันคือทั้งหมดที่เกี่ยวกับความตั้งใจ 1) หากคุณต้องการปล่อยให้ตรรกะคลาสเริ่มต้นของคลาสพื้นฐานเหมือนเดิมคุณจะไม่แทนที่เมธอด init ในคลาสที่ได้รับ 2) หากคุณต้องการที่จะขยายตรรกะเริ่มต้นจากชั้นฐานคุณกำหนดวิธีการเริ่มต้นของคุณเองและจากนั้นเรียกวิธีการเรียนระดับฐาน 'จากมัน 3) หากคุณต้องการแทนที่ลอจิกคลาสคลาสพื้นฐานคุณจะต้องกำหนดเมธอด init ของคุณเองในขณะที่ไม่ได้เรียกมันจากคลาสพื้นฐาน
wombatonfire

4

หากคลาส FileInfo มีคลาสบรรพบุรุษมากกว่าหนึ่งคลาสคุณควรเรียกใช้ฟังก์ชัน __init __ () ทั้งหมดของพวกเขาอย่างแน่นอน คุณควรทำเช่นเดียวกันกับฟังก์ชั่น __del __ () ซึ่งเป็น destructor


2

ใช่คุณต้องโทร__init__หาผู้ปกครองแต่ละคน เช่นเดียวกับฟังก์ชั่นหากคุณกำลังเอาชนะฟังก์ชั่นที่มีอยู่ในทั้งพ่อและแม่

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