'super' ใน Python คืออะไร


564

ความแตกต่างระหว่าง:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

และ:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

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

คำตอบ:


309

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

super()แต่ก็เกือบจะไม่สามารถใช้มรดกหลายโดยไม่ต้อง ซึ่งรวมถึงสำนวนทั่วไปเช่นมิกซ์อิน, อินเทอร์เฟซ, คลาสนามธรรมเป็นต้นซึ่งรวมถึงโค้ดที่ขยายออกไปในภายหลัง ถ้าใครบางคนต้องการที่จะเขียนชั้นเรียนที่ขยายChildและมิกซ์อินรหัสของพวกเขาจะทำงานไม่ถูกต้อง


6
คุณสามารถให้ตัวอย่างด้วยสิ่งที่คุณหมายถึงด้วย "มันจะไม่ทำงานอย่างถูกต้อง"?
Charlie Parker

319

ความแตกต่างคืออะไร?

SomeBaseClass.__init__(self) 

หมายถึงการเรียกร้องของSomeBaseClass __init__ในขณะที่

super(Child, self).__init__()

หมายถึงการโทรที่ถูกผูกไว้__init__จากคลาสผู้ปกครองที่ตามมาChildในการแก้ไขวิธีการสั่งซื้อ (MRO) ของอินสแตนซ์

หากอินสแตนซ์เป็นคลาสย่อยของ Child อาจมีพาเรนต์อื่นที่มาถัดจาก MRO

อธิบายง่ายๆ

เมื่อคุณเขียนคลาสคุณต้องการให้คลาสอื่นสามารถใช้งานได้ super()ทำให้ชั้นเรียนอื่น ๆ สามารถใช้ชั้นเรียนที่คุณเขียนได้ง่ายขึ้น

ดังที่ Bob Martin กล่าวว่าสถาปัตยกรรมที่ดีจะช่วยให้คุณเลื่อนการตัดสินใจออกไปได้นานที่สุด

super() สามารถเปิดใช้งานสถาปัตยกรรมประเภทนั้นได้

เมื่อคลาสอื่นมีคลาสย่อยที่คุณเขียนคลาสนั้นอาจสืบทอดมาจากคลาสอื่นได้ และคลาสเหล่านั้นอาจมีสิ่ง__init__ที่เกิดขึ้นหลังจากนี้__init__ตามลำดับของคลาสสำหรับการแก้ไขเมธอด

หากไม่มีsuperโค้ดน่าจะเป็นรหัสแม่ของชั้นเรียนที่คุณเขียน (เช่นตัวอย่าง) นี่หมายความว่าคุณจะไม่เรียกใช้ตัวถัดไป__init__ใน MRO และคุณจะไม่สามารถใช้รหัสซ้ำได้

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

Python 2 กับ 3

ใช้งานได้ใน Python 2 และ 3:

super(Child, self).__init__()

ใช้งานได้ใน Python 3 เท่านั้น:

super().__init__()

มันทำงานโดยไม่มีข้อโต้แย้งโดยเลื่อนขึ้นในกรอบสแต็กและรับอาร์กิวเมนต์แรกกับวิธีการ (โดยปกติจะselfเป็นวิธีการตัวอย่างหรือวิธีclsการเรียน - แต่อาจเป็นชื่ออื่น ๆ ) และหาชั้นเรียน (เช่นChild) ในตัวแปรอิสระ ( มันค้นหาด้วยชื่อ__class__เป็นตัวแปรฟรีปิดในวิธีการ)

ฉันชอบที่จะแสดงให้เห็นถึงวิธีการใช้งานข้ามกันได้superแต่ถ้าคุณใช้ Python 3 เท่านั้นคุณสามารถเรียกมันได้โดยไม่มีข้อโต้แย้ง

การบอกทิศทางด้วย Forward Compatibility

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

ความเข้ากันได้ไปข้างหน้าเป็นสิ่งสำคัญมากสำหรับนักพัฒนาที่มีประสบการณ์ คุณต้องการให้รหัสของคุณทำงานกับการเปลี่ยนแปลงที่น้อยที่สุดในขณะที่คุณเปลี่ยนแปลง เมื่อคุณดูประวัติการแก้ไขของคุณคุณต้องการดูสิ่งที่เปลี่ยนแปลงเมื่อ

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

ฉีดพึ่งพา

คนอื่น ๆ สามารถใช้รหัสของคุณและฉีดผู้ปกครองเข้ากับวิธีการแก้ไข:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

สมมติว่าคุณเพิ่มคลาสอื่นในวัตถุของคุณและต้องการเพิ่มคลาสระหว่าง Foo และ Bar (สำหรับการทดสอบหรือด้วยเหตุผลอื่น):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

การใช้ un-super child ล้มเหลวในการฉีดการพึ่งพาเนื่องจากเด็กที่คุณใช้อยู่นั้นมีวิธีการเข้ารหัสแบบฮาร์ดโค้ดที่จะถูกเรียกใช้หลังจากนั้น:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

อย่างไรก็ตามคลาสที่มีลูกที่ใช้superสามารถฉีดการพึ่งพาได้อย่างถูกต้อง:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

พูดถึงความคิดเห็น

ทำไมในโลกนี้จะมีประโยชน์

Python จัดทำแผนผังการสืบทอดที่ซับซ้อนผ่านอัลกอริธึมเชิงเส้นตรง C3เพื่อสร้างลำดับการแก้ปัญหาวิธี (MRO)

เราต้องการวิธีการที่จะเงยหน้าขึ้นมองในลำดับที่

สำหรับวิธีที่กำหนดไว้ในพาเรนต์เพื่อค้นหาวิธีถัดไปตามลำดับที่ไม่มีsuperก็จะต้องทำ

  1. รับ mro จากประเภทของอินสแตนซ์
  2. ค้นหาประเภทที่กำหนดวิธีการ
  3. ค้นหาประเภทถัดไปด้วยวิธีการ
  4. ผูกเมธอดนั้นและเรียกมันด้วยอาร์กิวเมนต์ที่คาดไว้

ไม่ควรมีการเข้าถึงUnsuperChild InjectMeทำไมข้อสรุปไม่ "หลีกเลี่ยงการใช้super" เสมอ? ฉันหายไปนี่อะไร

UnsuperChildไม่ได้InjectMeมีการเข้าถึง มันเป็นUnsuperInjectorที่มีการเข้าถึงInjectMe- UnsuperChildและยังไม่สามารถเรียกวิธีของชั้นเรียนที่มาจากวิธีการที่จะสืบทอดจาก

ชั้นเรียนเด็กทั้งสองตั้งใจที่จะเรียกใช้เมธอดด้วยชื่อเดียวกันที่มาถัดไปใน MRO ซึ่งอาจเป็นอีกคลาสหนึ่งที่ไม่ทราบว่าเมื่อใดที่มันถูกสร้างขึ้น

สิ่งที่ไม่มีsuperรหัสฮาร์ดเมธอดของพาเรนต์ - ดังนั้นจึงถูก จำกัด พฤติกรรมของวิธีการและคลาสย่อยไม่สามารถฉีดฟังก์ชันการทำงานในห่วงโซ่การโทร

หนึ่งที่มี superมีความยืดหยุ่นมากขึ้น ห่วงโซ่การโทรสำหรับวิธีการที่สามารถดักจับและฉีดการทำงาน

คุณอาจไม่จำเป็นต้องใช้ฟังก์ชั่นนั้น แต่คลาสย่อยของรหัสของคุณอาจ

ข้อสรุป

ใช้superอ้างอิงคลาสแม่เสมอแทนการเข้ารหัสอย่างหนัก

สิ่งที่คุณตั้งใจจะอ้างอิงถึงคลาสพาเรนต์ที่เป็นบรรทัดถัดไปไม่ใช่เฉพาะคลาสที่คุณเห็นว่าเด็กสืบทอดมา

การไม่ใช้superอาจทำให้ผู้ใช้รหัสของคุณมีข้อ จำกัด ที่ไม่จำเป็น


ใน C, DI เป็นเหมือนนี้ รหัสคือที่นี่ ถ้าฉันเพิ่มการใช้งานlistอินเทอร์เฟซเพิ่มเติมให้พูดว่าdoublylinkedlistแอปพลิเคชันจะเลือกอย่างราบรื่น ฉันสามารถทำให้ตัวอย่างของฉันสามารถกำหนดค่าได้มากขึ้นโดยการแนะนำconfig.txtและการใช้ลิงก์ในเวลาโหลด นี่เป็นตัวอย่างที่ถูกต้องหรือไม่ ถ้าใช่ฉันจะเชื่อมโยงรหัสของคุณได้อย่างไร ดูคำแนะนำแรกของ DI ในวิกิ การใช้งานใหม่สามารถกำหนดค่าได้ที่ไหน? ในรหัสของคุณ
แลกเปลี่ยนที่มากเกินไป

การใช้งานใหม่ถูกสร้างขึ้นผ่านการสืบทอดตัวอย่างเช่นหนึ่งในคลาส "Injector" ที่สืบทอดมาจากInjectMeคลาส อย่างไรก็ตามความคิดเห็นไม่ได้มีไว้เพื่อการสนทนาดังนั้นฉันขอแนะนำให้คุณพูดคุยเรื่องนี้กับผู้อื่นในการแชทหรือถามคำถามใหม่ในเว็บไซต์หลัก
Aaron Hall

คำตอบที่ดี! แต่เมื่อใช้หลายการสืบทอดจะมีความยุ่งยากกับ super () และ__init__ฟังก์ชั่น โดยเฉพาะถ้าลายเซ็นของ__init__แตกต่างกันระหว่างคลาสในลำดับชั้น ฉันได้เพิ่มคำตอบที่มุ่งเน้นด้านนี้แล้ว
Aviad Rozenhek

35

ฉันเล่นไปด้วยsuper()และรู้ว่าเราสามารถเปลี่ยนคำสั่งการโทรได้

ตัวอย่างเช่นเรามีโครงสร้างลำดับชั้นถัดไป:

    A
   / \
  B   C
   \ /
    D

ในกรณีนี้MRO of D จะเป็น (เฉพาะ Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

มาสร้างคลาสที่super()เรียกใช้หลังจากการเรียกใช้เมธอด

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / 
  B  C
    /
    D

เราจะเห็นได้ว่าลำดับการแก้ปัญหานั้นเหมือนกับใน MRO แต่เมื่อเราเรียกsuper()วิธีเริ่มต้น:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

เรามีคำสั่งที่แตกต่างมันกลับคำสั่งของทูเปิล MRO

    A
   / 
  B  C
    /
    D 

สำหรับการอ่านเพิ่มเติมฉันอยากจะแนะนำคำตอบต่อไป:

  1. ตัวอย่างการสร้างเส้นตรง C3 กับ super (ลำดับชั้นขนาดใหญ่)
  2. การเปลี่ยนแปลงพฤติกรรมที่สำคัญระหว่างคลาสสไตล์เก่าและใหม่
  3. The Inside Story ในห้องเรียนสไตล์ใหม่

ฉันไม่เข้าใจว่าทำไมคำสั่งซื้อถึงเปลี่ยนไป ส่วนแรกฉันเข้าใจว่า DBCA เพราะ D เป็นคลาสแรกจากนั้นเมื่อโหลดตัวเอง (B, C) ในที่สุดก็จะพิมพ์ B, C จากนั้นเพียง A ตั้งแต่ B (A), C (A) ชี้กลับไปที่ตัวเองสำหรับรอบสุดท้าย ส่วนหนึ่ง ถ้าฉันทำตามความเข้าใจนี้แล้วส่วนที่สองไม่ควรเป็นเหมือน BCAD เหรอ? คุณกรุณาอธิบายให้ฉันหน่อยได้ไหม
JJson

ของฉันไม่ดีฉันไม่ได้สังเกตว่าทุกครั้งในแต่ละชั้นเรียนได้รับการริเริ่มด้วย super () ก่อน ถ้าเป็นอย่างนั้นแล้วมันควรจะเป็น ABCD เหรอ? ฉันเข้าใจวิธี ACBD มา แต่ก็ยังไม่สามารถโน้มน้าวใจและยังมีความสับสนเล็กน้อย ความเข้าใจของฉันคือว่า d = D () เรียกว่า Class D (B, C) พร้อมพารามิเตอร์ 2 ตัวตั้งแต่ super () เริ่มต้นก่อนจากนั้น B จะถูกเรียกพร้อมกับคุณลักษณะของมันจากนั้น D จะไม่ถูกพิมพ์ก่อน C เป็นเพราะ Class D (B, C) มีพารามิเตอร์ตัวเอง 2 ตัวดังนั้นจึงต้องดำเนินการตัวที่สองซึ่งเป็น Class C (A) หลังจากดำเนินการแล้วจะไม่มีพารามิเตอร์ตัวเองที่ต้องดำเนินการอีกต่อไป
JJson

มันจะขึ้นอยู่กับคำนิยามmro
SKhalymon

1
จากนั้นมันจะพิมพ์ C จากนั้นพิมพ์ B และในที่สุดก็พิมพ์ D ฉันถูกไหม?
JJson

2
มันง่ายมากที่จะเข้าใจอันที่สองตราบเท่าที่คุณได้อันที่สอง มันก็เหมือนกอง คุณดันพิมพ์ '' ลงในสแต็กและทำ super () เมื่อเสร็จแล้ว A มันจะเริ่มพิมพ์สิ่งต่าง ๆ ในสแต็กนั้นดังนั้นลำดับจะกลับกัน
อนุญาตอาทิตย์

35

ทั้งหมดนี้ไม่ได้สมมติว่าคลาสพื้นฐานเป็นคลาสสไตล์ใหม่ใช่หรือไม่

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

จะไม่ทำงานใน Python 2 class Aต้องเป็นรูปแบบใหม่เช่น:class A(object)


20

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

พิจารณาลำดับชั้นA, BและCที่แต่ละชั้นเป็นแม่ของหนึ่งดังต่อไปนี้มันและa, bและcกรณีที่เกี่ยวข้องของแต่ละ

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

ใช้superกับวิธีคงที่

เช่นใช้super()จากภายใน__new__()วิธีการ

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

คำอธิบาย:

1- ถึงแม้ว่าเป็นเรื่องปกติที่__new__()จะใช้เป็นพารามิเตอร์แรกในการอ้างอิงถึงคลาสที่เรียก แต่จะไม่ถูกนำมาใช้ใน Python เป็น classmethod แต่เป็นวิธีการแบบคงที่ นั่นคือการอ้างอิงไปยังคลาสจะต้องผ่านอย่างชัดเจนว่าเป็นอาร์กิวเมนต์แรกเมื่อโทร__new__()โดยตรง:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- เมื่อมีการโทรsuper()เพื่อไปยังชั้นผู้ปกครองเราผ่านชั้นเรียนเด็กAเป็นอาร์กิวเมนต์แรกจากนั้นเราจะส่งการอ้างอิงไปยังวัตถุที่น่าสนใจในกรณีนี้มันคือการอ้างอิงชั้นที่ถูกส่งผ่านเมื่อA.__new__(cls)ถูกเรียก ในกรณีส่วนใหญ่มันจะเกิดขึ้นเพื่ออ้างอิงถึงชั้นเรียนเด็ก ในบางสถานการณ์มันอาจจะไม่ใช่เช่นในกรณีของการสืบทอดหลายชั่วอายุคน

super(A, cls)

3- เนื่องจากกฎทั่วไป__new__()เป็นสแตติกวิธีsuper(A, cls).__new__ก็จะส่งกลับสแตติกเมธอดและจำเป็นต้องระบุอาร์กิวเมนต์ทั้งหมดอย่างชัดเจนรวมถึงการอ้างอิงไปยังวัตถุที่มีความไม่สมบูรณ์ในกรณีclsนี้

super(A, cls).__new__(cls, *a, **kw)

4- ทำสิ่งเดียวกันโดยไม่ super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

ใช้superกับวิธีการตัวอย่าง

เช่นใช้super()จากภายใน__init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

คำอธิบาย:

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

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- เมื่อโทรsuper()ภายใน__init__()เราส่งคลาสเด็กเป็นอาร์กิวเมนต์แรกและวัตถุที่น่าสนใจเป็นอาร์กิวเมนต์ที่สองซึ่งโดยทั่วไปเป็นการอ้างอิงถึงอินสแตนซ์ของคลาสเด็ก

super(A, self)

3- การโทรsuper(A, self)จะส่งคืนพร็อกซีที่จะแก้ไขขอบเขตและใช้กับselfราวกับว่าตอนนี้เป็นอินสแตนซ์ของคลาสพาเรนต์ ลองเรียกพร็อกซี่sนั้น เนื่องจาก__init__()เป็นวิธีการเช่นการเรียกร้องs.__init__(...)โดยปริยายจะผ่านการอ้างอิงของเป็นอาร์กิวเมนต์แรกของผู้ปกครองself__init__()

4- จะทำเช่นเดียวกันโดยไม่ต้องเราต้องผ่านการอ้างอิงถึงตัวอย่างชัดเจนกับรุ่นของผู้ปกครองของsuper__init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

ใช้superกับ classmethod

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

คำอธิบาย:

1- วิธีการเรียนสามารถเรียกจากชั้นเรียนโดยตรงและใช้เป็นพารามิเตอร์แรกของการอ้างอิงไปยังชั้นเรียน

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- เมื่อโทรsuper()ภายใน classmethod เพื่อแก้ไขเวอร์ชันของ parent เราต้องการส่งคลาสย่อยปัจจุบันเป็นอาร์กิวเมนต์แรกเพื่อระบุขอบเขตของพาเรนต์ที่เรากำลังพยายามแก้ไขและวัตถุที่น่าสนใจเป็นอาร์กิวเมนต์ที่สอง เพื่อระบุว่าวัตถุใดที่เราต้องการใช้ขอบเขตนั้นซึ่งโดยทั่วไปเป็นการอ้างอิงถึงคลาสย่อยเองหรือหนึ่งในคลาสย่อย

super(B, cls_or_subcls)

3- โทรsuper(B, cls)แก้ไขขอบเขตของและนำไปใช้A clsเนื่องจากalternate_constructor()เป็นวิธีการในชั้นเรียนการโทรsuper(B, cls).alternate_constructor(...)จะส่งผ่านการอ้างอิงโดยนัยว่าclsเป็นอาร์กิวเมนต์แรกAของเวอร์ชันของalternate_constructor()

super(B, cls).alternate_constructor()

4- เพื่อทำสิ่งเดียวกันโดยไม่ใช้super()คุณจำเป็นต้องได้รับการอ้างอิงถึงเวอร์ชันที่ไม่ได้ผูกไว้ของA.alternate_constructor()(เช่นเวอร์ชันที่ชัดเจนของฟังก์ชัน) เพียงทำเช่นนี้จะไม่ทำงาน:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

ข้างต้นจะไม่ทำงานเพราะA.alternate_constructor()วิธีการใช้การอ้างอิงโดยนัยเพื่อAเป็นอาร์กิวเมนต์แรก clsถูกส่งผ่านไปที่นี่จะเป็นข้อโต้แย้งที่สอง

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)

6

คำตอบที่ยอดเยี่ยมมากมาย แต่สำหรับผู้เรียนรู้ด้วยสายตา: ก่อนอื่นให้สำรวจด้วยข้อโต้แย้งกับสุดยอด ตัวอย่างต้นไม้ซุปเปอร์มรดก

ลองนึกภาพมีตัวอย่างที่jackสร้างขึ้นจากชั้นเรียนJackที่มีห่วงโซ่มรดกตามที่แสดงในสีเขียวในภาพ โทรศัพท์:

super(Jack, jack).method(...)

จะใช้ MRO (วิธีการสั่งซื้อมติ) ของjack(ต้นไม้มรดกในลำดับที่แน่นอน) Jackและจะเริ่มต้นการค้นหาจาก ทำไมหนึ่งสามารถให้ชั้นผู้ปกครอง? ถ้าเราเริ่มค้นหาจากอินสแตนซ์jackมันก็จะหาวิธีอินสแตนซ์จุดรวมคือการหาวิธีผู้ปกครอง

ถ้าไม่จัดหาอาร์กิวเมนต์ซุปเปอร์เช่นอาร์กิวเมนต์แรกผ่านในเป็นชั้นของและอาร์กิวเมนต์ที่สองผ่านมีself selfสิ่งเหล่านี้คำนวณโดยอัตโนมัติสำหรับคุณใน Python3

อย่างไรก็ตามกล่าวว่าเราไม่ต้องการใช้Jackวิธีการแทนการส่งผ่านJackเราสามารถส่งผ่านJenเพื่อเริ่มค้นหาวิธีการJenข้างบนได้

มันค้นหาหนึ่งชั้นในเวลา (ความกว้างไม่ลึก) เช่นถ้าAdamและSueทั้งสองมีวิธีการที่จำเป็นหนึ่งจากSueจะพบก่อน

ถ้าCainและSueทั้งคู่มีวิธีที่ต้องการวิธีCainของมันจะถูกเรียกก่อน สิ่งนี้สอดคล้องในรหัสไปที่:

Class Jen(Cain, Sue):

MRO มาจากซ้ายไปขวา


2

บางคำตอบที่ดีที่นี่ แต่พวกเขาไม่ได้จัดการกับวิธีการใช้super()ในกรณีที่ชั้นเรียนที่แตกต่างกันในลำดับชั้นมีลายเซ็นที่แตกต่างกัน ... โดยเฉพาะในกรณีของ__init__

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

นี่เป็นเพียงวิธีแก้ไขสถานการณ์นี้:

  1. คลาสระดับบนสุดในลำดับชั้นของคุณต้องสืบทอดจากคลาสที่กำหนดเองเช่นSuperObject:
  2. **kwargsถ้าเรียนสามารถใช้ข้อโต้แย้งที่แตกต่างกันเสมอผ่านขัดแย้งทั้งหมดที่คุณได้รับไปยังฟังก์ชั่นสุดเป็นข้อโต้แย้งคำหลักและยอมรับเสมอ
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

ตัวอย่างการใช้งาน:

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

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

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

เอาท์พุท:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

0
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

มันค่อนข้างเข้าใจง่าย

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

ตกลงสิ่งที่เกิดขึ้นตอนนี้ถ้าคุณใช้super(Child,self)?

เมื่ออินสแตนซ์ลูกถูกสร้างขึ้น MRO (ลำดับการแก้ไขเมธอด) จะอยู่ในลำดับของ (ลูก, SomeBaseClass, วัตถุ) ตามการสืบทอด (สมมติว่า SomeBaseClass ไม่มีผู้ปกครองอื่นยกเว้นวัตถุเริ่มต้น)

โดยผ่านChild, self, superการค้นหาใน MRO ของselfอินสแตนซ์และกลับวัตถุพร็อกซีต่อไปของเด็กในกรณีนี้มัน SomeBaseClass วัตถุนี้แล้วเรียก__init__วิธีการ SomeBaseClass กล่าวอีกนัยหนึ่งถ้าเป็นsuper(SomeBaseClass,self)วัตถุของพร็อกซีที่superส่งคืนจะเป็นobject

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


0

พิจารณารหัสต่อไปนี้:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

หากเปลี่ยน constructor ของYเป็นX.__init__คุณจะได้รับ:

X
Y
Q

แต่เมื่อใช้super(Y, self).__init__()คุณจะได้รับ:

X
P
Y
Q

และPหรือQอาจจะมีส่วนร่วมจากไฟล์อื่นที่คุณไม่ได้รู้ว่าเมื่อคุณเขียนและX Yดังนั้นโดยทั่วไปคุณจะไม่ทราบว่าสิ่งที่super(Child, self)จะอ้างอิงเมื่อคุณเขียนclass Y(X)แม้ลายเซ็นของ Y Y(X)เป็นง่ายๆเป็น นั่นเป็นเหตุผลที่ super อาจเป็นทางเลือกที่ดีกว่า

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