วิธีที่ถูกต้องในการใช้ super (การส่งผ่านอาร์กิวเมนต์)


109

ดังนั้นฉันจึงติดตามSuper ถือว่าเป็นอันตรายของ Pythonและไปทดสอบตัวอย่างของเขา

อย่างไรก็ตามตัวอย่าง 1-3ซึ่งควรจะแสดงวิธีการโทรที่ถูกต้องsuperเมื่อจัดการกับ__init__วิธีการที่คาดว่าจะมีอาร์กิวเมนต์ที่แตกต่างกันการแบนไม่ได้ผล

นี่คือสิ่งที่ฉันได้รับ:

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

มันดูเหมือนว่าobjectตัวเองละเมิดหนึ่งของการปฏิบัติที่ดีที่สุดที่กล่าวถึงในเอกสารซึ่งเป็นว่าวิธีการที่ใช้superจะต้องยอมรับและ*args**kwargs

ตอนนี้เห็นได้ชัดว่านายอัศวินคาดว่าตัวอย่างของเขาจะใช้งานได้ดังนั้นสิ่งนี้มีการเปลี่ยนแปลงใน Python เวอร์ชันล่าสุดหรือไม่? ฉันตรวจสอบ 2.6 และ 2.7 และล้มเหลวทั้งสองอย่าง

แล้ววิธีที่ถูกต้องในการจัดการกับปัญหานี้คืออะไร?


2
วิธีที่ฉันชอบคือ: ลำดับชั้นการสืบทอดแบบเรียบง่าย
มิลลิโมส

20
คุณควรอ่าน"Python's super () ถือว่าเป็น super"เพื่อให้ได้มุมมองที่สมดุล :)
Björn Pollex

@ BjörnPollex: ขอบคุณ! การเชื่อมโยงของคุณให้คำตอบสำหรับคำถาม: คุณสามารถเขียน "ชั้นราก" ซึ่งสืบทอดมาจากobjectและมันทำให้แน่ใจว่าจะโทรobject's __init__ได้อย่างถูกต้อง
cha0site

5
โปรดทราบว่า__init__เมื่อobjectละเว้นพารามิเตอร์ใด ๆ บน Python 2.5 แบบไม่โต้ตอบ สิ่งนี้เปลี่ยนแปลงใน Python 2.6
Wilfred Hughes

1
@ วิลเฟรด: เฮ้ขอบคุณสำหรับการตอบคำถามจริง! ตอนนี้ฉันรู้แล้วว่าทำไมเรียงความถึงล้าสมัย!
cha0site

คำตอบ:


116

บางครั้งสองคลาสอาจมีชื่อพารามิเตอร์เหมือนกัน ในกรณีที่คุณไม่สามารถปรากฏคู่ค่าคีย์ออกจากหรือลบออกจาก**kwargs *argsคุณสามารถกำหนดBaseคลาสที่ไม่เหมือนobjectดูดซับ / ละเว้นอาร์กิวเมนต์:

class Base(object):
    def __init__(self, *args, **kwargs): pass

class A(Base):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(Base):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(arg, *args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10)

ผลตอบแทน

MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

โปรดทราบว่าเพื่อให้ได้ผลBaseต้องเป็นคลาสสุดท้ายใน MRO


4
ไม่ควรBaseโทรsuper(Base, self).__init__()?
cha0site

4
เพื่อให้ได้ผลBaseต้องมาเมื่อสิ้นสุด MRO (ยกเว้นobject) โทรไม่ทำอะไรเลยดังนั้นจึงเป็นโอเคที่จะไม่โทรobject.__init__ super(Base, self).__init__()อันที่จริงฉันคิดว่ามันอาจจะชัดเจนกว่าที่จะไม่รวมถึงsuper(Base, self).__init__()การขับรถกลับบ้านในจุดที่Baseเป็นจุดสิ้นสุดของเส้น
unutbu

27

หากคุณจะได้รับมรดกจำนวนมาก (นั่นคือกรณีที่นี่) ฉันขอแนะนำให้คุณส่งผ่านพารามิเตอร์ทั้งหมดโดยใช้**kwargsจากนั้นจึงใช้พารามิเตอร์popเหล่านั้นทันทีหลังจากที่คุณใช้ (เว้นแต่คุณจะต้องการในชั้นสูง)

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

นี่เป็นวิธีที่ง่ายที่สุดในการแก้ปัญหาประเภทนี้

third = Third(first_arg=1, second_arg=2, third_arg=3)

7

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

class A(object):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(object):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(*args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(*args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(*args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10, 20, 30)

แล้วE(arg = 10)ไงล่ะ? ฉันไม่ชอบวิธีนี้ฉันจำเป็นต้องรู้ MRO ล่วงหน้าเพื่อที่จะใช้มัน วิธีอื่นที่ฉันเขียน "คลาสรูท" ที่สืบทอดมาobjectดูเหมือนจะสะอาดกว่ามาก
cha0site

วิธีนี้มีประโยชน์หากฟังก์ชันที่เรียกว่าไม่แชร์ชื่ออาร์กิวเมนต์ ฉันยอมรับว่าฉันไม่มีประสบการณ์ในทางปฏิบัติมากนักsuperดังนั้นแนวทางนี้อาจค่อนข้างเป็นทฤษฎี
Björn Pollex
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.