Python super () ทำงานอย่างไรกับหลายมรดก


887

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

ตัวอย่างเช่นถ้าคุณมีสิ่งที่ชอบ:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

สิ่งที่ฉันไม่ได้รับคือ: Third()ชั้นเรียนจะสืบทอดทั้งตัวสร้างวิธีการหรือไม่ ถ้าใช่แล้วจะใช้อันไหนกับ super () และทำไมล่ะ

และถ้าคุณต้องการเรียกใช้อีกอันหนึ่ง ฉันรู้ว่ามันมีบางอย่างเกี่ยวกับการสั่งซื้อการแก้ไขวิธี Python ( MRO )


ในความเป็นจริงมรดกหลายรายการเป็นกรณีเดียวที่super()มีการใช้งานใด ๆ ฉันจะไม่แนะนำให้ใช้กับคลาสที่ใช้การสืบทอดเชิงเส้นซึ่งเป็นค่าใช้จ่ายที่ไร้ประโยชน์
Bachsau

9
@Bachsau ถูกต้องทางเทคนิคในการที่มันเป็นค่าใช้จ่ายเล็ก ๆ แต่ super () เป็น pythonic มากขึ้นและช่วยให้การแยกตัวประกอบและการเปลี่ยนรหัสในช่วงเวลา ใช้ super () เว้นแต่ว่าคุณต้องการวิธีระบุคลาสเฉพาะ
พอลวิปป์

2
อีกปัญหาหนึ่งsuper()คือมันบังคับให้คลาสย่อยทุกคลาสใช้งานได้เช่นกันในขณะที่ไม่ได้ใช้super()งานทุกคนการทำคลาสย่อยสามารถตัดสินใจได้เอง หากนักพัฒนาซอฟต์แวร์ที่ใช้ไม่ทราบsuper()หรือไม่ทราบว่ามีการใช้งานปัญหาเกี่ยวกับ mro อาจเกิดขึ้นซึ่งยากต่อการติดตาม
Bachsau

ฉันได้พบคำตอบที่นี่ทำให้เกิดความสับสนไม่ทางใดก็ทางหนึ่ง คุณจะอ้างอิงที่นี่จริง ๆแทน
matanster

คำตอบ:


709

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

ในตัวอย่างของคุณจะเรียกThird() First.__init__Python ค้นหาแต่ละคุณลักษณะในพาเรนต์ของคลาสเนื่องจากมีรายการจากซ้ายไปขวา __init__ในกรณีนี้เรากำลังมองหา ดังนั้นหากคุณกำหนด

class Third(First, Second):
    ...

งูใหญ่จะเริ่มต้นด้วยการมองหาที่Firstและถ้าไม่ได้มีแอตทริบิวต์แล้วมันจะดูที่FirstSecond

สถานการณ์นี้จะซับซ้อนมากขึ้นเมื่อการสืบทอดเริ่มข้ามเส้นทาง (ตัวอย่างเช่นหากFirstสืบทอดมาSecond) อ่านลิงค์ด้านบนเพื่อรับรายละเอียดเพิ่มเติม แต่โดยย่อ Python จะพยายามรักษาลำดับที่แต่ละคลาสจะปรากฏในรายการการสืบทอดโดยเริ่มจากคลาสย่อย

ตัวอย่างเช่นหากคุณมี:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

MRO จะเป็น [Fourth, Second, Third, First].

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

แก้ไขเพื่อเพิ่มตัวอย่างของ MRO ที่ไม่ชัดเจน:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

ควรThird's MRO เป็น[First, Second]หรือ[Second, First]? ไม่มีความคาดหวังที่ชัดเจนและ Python จะแจ้งข้อผิดพลาด

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

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

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)

12
มันน่าสนใจยิ่งขึ้น (และอาจทำให้สับสนมากขึ้น) เมื่อคุณเริ่มเรียก super () ใน First, Second และ Third [ pastebin.com/ezTyZ5Wa ]
gatoatigrado

52
ฉันคิดว่าการขาดสาย super call ในชั้นเรียนแรกเป็นปัญหาใหญ่จริง ๆ กับคำตอบนี้ โดยไม่ต้องพูดถึงวิธีการ / ทำไม thats ที่สำคัญการทำความเข้าใจกับคำถามที่หายไป
Sam Hartman

3
คำตอบนี้เป็นเพียงความผิด หากไม่มีสาย super () ในผู้ปกครองจะไม่มีอะไรเกิดขึ้น คำตอบของ @ lifeless คือคำตอบที่ถูกต้อง
Cerin

8
@Cerin จุดของตัวอย่างนี้คือการแสดงวิธีการสร้าง MRO ตัวอย่างไม่ได้มีวัตถุประสงค์เพื่อพิมพ์ "first \ nsecond \ third" หรืออะไรก็ตาม และการซ่อมบำรุงถูกต้องแน่นอน: สี่ .__ mro__ == (<คลาส ' หลัก .Fourth'> <คลาส ' หลัก .Second'> <คลาส ' หลัก .Third'> <คลาส ' หลัก .First'> < พิมพ์ 'object'>)
rbp

2
เท่าที่ฉันเห็นคำตอบนี้หายไปหนึ่งในคำถามของ OP ซึ่งคือ "และถ้าคุณต้องการเรียกใช้อีกคนหนึ่ง?" ฉันต้องการดูคำตอบของคำถามนี้ เราควรจะตั้งชื่อคลาสฐานอย่างชัดเจนหรือไม่
Ray

251

รหัสของคุณและคำตอบอื่น ๆ ล้วนเป็นรถบั๊กกี้ พวกเขาขาดการsuper()โทรในสองคลาสแรกที่จำเป็นสำหรับการทำ subclassing แบบร่วมมือเพื่อให้ทำงานได้

นี่คือรหัสที่แก้ไขแล้ว:

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

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

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

>>> Third()
second
first
third

90
จะทำอย่างไรถ้าคลาสเหล่านี้ต้องการพารามิเตอร์ต่าง ๆ เพื่อเริ่มต้นตนเอง
calfzhou

2
"subclassing แบบร่วมมือกัน"
Quant Metropolis

6
ด้วยวิธีนี้เมธอดinitของคลาสฐานทั้งสองจะถูกเรียกใช้งานในขณะที่ตัวอย่างดั้งเดิมเรียกเฉพาะinitแรกที่พบใน MRO ฉันเดาว่าเป็นนัยโดยคำว่า "subclassing แบบร่วมมือ" แต่การชี้แจงจะมีประโยชน์ ('ชัดเจนกว่าโดยปริยาย' คุณรู้;))
Quant Metropolis

1
ใช่ถ้าคุณกำลังส่งพารามิเตอร์ที่แตกต่างกันไปยังวิธีการที่เรียกว่าผ่าน super การใช้งานทั้งหมดของวิธีการที่จะขึ้น MRO ต่อวัตถุ () ต้องมีลายเซ็นที่เข้ากันได้ สิ่งนี้สามารถทำได้ผ่านพารามิเตอร์คำหลัก: ยอมรับพารามิเตอร์มากกว่าวิธีที่ใช้และไม่สนใจพารามิเตอร์เพิ่มเติม โดยทั่วไปถือว่าน่าเกลียดที่จะทำเช่นนี้และสำหรับกรณีส่วนใหญ่การเพิ่มวิธีการใหม่จะดีกว่า แต่initคือ (เกือบ?) ที่ไม่ซ้ำกันเป็นชื่อวิธีการพิเศษ แต่มีพารามิเตอร์ที่ผู้ใช้กำหนด
ไม่มีชีวิตชีวา

15
การออกแบบการสืบทอดหลายแบบนั้นแย่มากในงูใหญ่ คลาสฐานเกือบจำเป็นต้องรู้ว่าใครจะได้รับมันและจำนวนคลาสฐานอื่น ๆ ที่ได้รับจะมาและตามลำดับ ... มิฉะนั้น superจะล้มเหลวในการทำงาน (เพราะพารามิเตอร์ไม่ตรงกัน) หรือจะไม่เรียก บางส่วนของฐาน (เพราะคุณไม่ได้เขียนsuperในหนึ่งในฐานที่ทำลายลิงค์)!
นาวาซ

186

ฉันต้องการที่จะอธิบายรายละเอียดของคำตอบอย่างไร้ชีวิตชีวาเพราะเมื่อฉันเริ่มอ่านเกี่ยวกับวิธีการใช้ super () ในลำดับชั้นการสืบทอดหลายครั้งใน Python ฉันไม่ได้รับทันที

สิ่งที่คุณต้องเข้าใจว่าsuper(MyClass, self).__init__()ให้ต่อไป __init__วิธีการตามขั้นตอนวิธีการใช้วิธีการสั่งซื้อมติ (MRO) ในบริบทของการลำดับชั้นมรดกสมบูรณ์

ส่วนสุดท้ายนี้มีความสำคัญต่อการเข้าใจ ลองพิจารณาตัวอย่างอีกครั้ง:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

อ้างอิงจากบทความนี้เกี่ยวกับการแก้ไขวิธีการสั่งซื้อโดย Guido van Rossum คำสั่งการแก้ไข__init__จะถูกคำนวณ (ก่อน Python 2.3) โดยใช้ "การสำรวจเส้นทางจากซ้ายไปขวาก่อน":

Third --> First --> object --> Second --> object

หลังจากลบรายการที่ซ้ำกันทั้งหมดยกเว้นรายการสุดท้ายเราจะได้รับ:

Third --> First --> Second --> object

ดังนั้นให้ติดตามสิ่งที่เกิดขึ้นเมื่อเรายกตัวอย่างเช่นการที่ระดับเช่นThirdx = Third()

  1. ตาม MRO Third.__init__รัน
    • พิมพ์ Third(): entering
    • จากนั้นsuper(Third, self).__init__()เรียกใช้งานและ MRO ส่งคืนFirst.__init__ซึ่งถูกเรียก
  2. First.__init__ รัน
    • พิมพ์ First(): entering
    • จากนั้นsuper(First, self).__init__()เรียกใช้งานและ MRO ส่งคืนSecond.__init__ซึ่งถูกเรียก
  3. Second.__init__ รัน
    • พิมพ์ Second(): entering
    • จากนั้นsuper(Second, self).__init__()เรียกใช้งานและ MRO ส่งคืนobject.__init__ซึ่งถูกเรียก
  4. object.__init__ ดำเนินการ (ไม่มีคำสั่งพิมพ์ในรหัสที่นั่น)
  5. การดำเนินการกลับไปSecond.__init__ที่แล้วพิมพ์Second(): exiting
  6. การดำเนินการกลับไปFirst.__init__ที่แล้วพิมพ์First(): exiting
  7. การดำเนินการกลับไปThird.__init__ที่แล้วพิมพ์Third(): exiting

รายละเอียดนี้เป็นสาเหตุที่ทำให้อินสแตนซ์ Third () ส่งผลให้:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

อัลกอริทึม MRO ได้รับการปรับปรุงจาก Python 2.3 ขึ้นไปเพื่อให้ทำงานได้ดีในกรณีที่ซับซ้อน แต่ฉันเดาว่าการใช้ "การสำรวจเส้นทางจากซ้ายไปขวาขวา" + "การลบข้อมูลซ้ำที่คาดหวังสำหรับสุดท้าย" ยังคงใช้ได้ในกรณีส่วนใหญ่ (โปรด แสดงความคิดเห็นหากนี่ไม่ใช่กรณี) อย่าลืมอ่านโพสต์บล็อกโดย Guido!


6
ฉันยังไม่เข้าใจว่าทำไม: ภายในinitของ First super (First, self) .__ init __ () เรียกinitของ Second เพราะนั่นคือสิ่งที่ MRO สั่ง!
user389955

@ user389955 วัตถุที่สร้างขึ้นเป็นประเภทที่สามซึ่งมีวิธีการเริ่มต้นทั้งหมด ดังนั้นถ้าคุณสมมติว่า MRO สร้างรายการฟังก์ชั่น init ทั้งหมดตามลำดับที่เฉพาะเจาะจงด้วยการเรียก super ทุกครั้งคุณจะก้าวไปข้างหน้าหนึ่งก้าวจนกว่าจะถึงจุดจบ
Sreekumar R

15
ฉันคิดว่าขั้นตอนที่ 3 ต้องการคำอธิบายเพิ่มเติม: หากThirdไม่ได้รับมรดกจากSecondนั้นsuper(First, self).__init__จะโทรobject.__init__และหลังจากกลับมา "ครั้งแรก" จะถูกพิมพ์ แต่เป็นเพราะThirdสืบทอดมาจากทั้งสองFirstและSecondมากกว่าการโทรobject.__init__หลังจากFirst.__init__MRO สั่งการว่าการโทรครั้งสุดท้ายไปยังจะobject.__init__ถูกเก็บไว้และคำสั่งการพิมพ์ในFirstและSecondจะไม่ถึงจนกว่าจะobject.__init__กลับมา เนื่องจากSecondเป็นคนสุดท้ายที่จะเรียกobject.__init__มันกลับภายในก่อนที่จะกลับมาในSecond First
MountainDrew

1
ที่น่าสนใจ PyCharm ดูเหมือนจะรู้ทั้งหมดนี้ (คำแนะนำของการพูดคุยเกี่ยวกับการที่พารามิเตอร์ไปที่การโทรไปยังซูเปอร์. นอกจากนี้ยังมีความเชื่อในเรื่องของความแปรปรวนของปัจจัยการผลิตบางส่วนจึงตระหนักถึงความList[subclass]เป็นList[superclass]ถ้าsubclassเป็น subclass ของsuperclass( Listมาจากtypingโมดูลของ PEP 483 iirc).
Reb.Cabin

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

58

สิ่งนี้เรียกว่าปัญหาเพชรหน้ามีรายการใน Python แต่โดยย่อ Python จะเรียกเมธอดของซูเปอร์คลาสจากซ้ายไปขวา


นี่ไม่ใช่ปัญหาเพชร ปัญหาเพชรเกี่ยวข้องกับสี่คลาสและคำถามของ OP เกี่ยวข้องกับสามคลาสเท่านั้น
Ian Goodfellow

147
objectคือสี่
GP89

28

นี่คือวิธีที่ฉันแก้ไขปัญหาการมีหลายมรดกพร้อมกับตัวแปรที่แตกต่างกันสำหรับการเริ่มต้นและมี MixIn หลายรายการที่มีการเรียกใช้ฟังก์ชันเดียวกัน ฉันต้องเพิ่มตัวแปรเพื่อส่งผ่าน ** kwargs และเพิ่มอินเทอร์เฟซ MixIn เพื่อเป็นจุดสิ้นสุดสำหรับการเรียก super

นี่Aเป็นชั้นฐานขยายและBและCเรียน mixin fทั้งที่ให้ฟังก์ชั่น AและBทั้งสองคาดว่าพารามิเตอร์vในของพวกเขา__init__และคาดว่าC wฟังก์ชั่นใช้เวลาหนึ่งพารามิเตอร์ f สืบทอดจากทั้งสามคลาส คืออินเตอร์เฟซ mixin สำหรับและyQMixInFBC


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)

ฉันคิดว่านี่น่าจะเป็นคำถามและคำตอบที่แยกจากกันเนื่องจาก MRO เป็นหัวข้อที่มีขนาดใหญ่พอสำหรับตัวเองโดยไม่ต้องจัดการกับข้อโต้แย้งที่แตกต่างกันระหว่างฟังก์ชั่นที่มีการสืบทอด (มรดกหลายรายการเป็นกรณีพิเศษ)
ไม่มีชีวิตชีวา

8
ในทางทฤษฎีใช่ ในทางปฏิบัติแล้วสถานการณ์นี้เกิดขึ้นทุกครั้งที่ฉันได้พบกับมรดกเพชรในไพ ธ อนดังนั้นฉันจึงเพิ่มที่นี่ เนื่องจากนี่คือที่ที่ฉันไปทุกครั้งที่ฉันไม่สามารถหลีกเลี่ยงการสืบทอดเพชรได้อย่างหมดจด นี่คือลิงค์พิเศษสำหรับอนาคตฉัน: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
brent.payne

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

คำขอดึงไปยัง repo GitHub ที่มีชื่ออธิบายจะได้รับการชื่นชม
brent.payne

@ brent.payne ฉันคิดว่า @Arthur หมายความว่าวิธีการทั้งหมดของคุณอาศัยการใช้args/ kwargsแทนที่จะใช้พารามิเตอร์ที่มีชื่อ
สูงสุด

25

ฉันเข้าใจว่าไม่ได้ตอบsuper()คำถามโดยตรงแต่ฉันรู้สึกว่ามันเกี่ยวข้องพอที่จะแบ่งปัน

นอกจากนี้ยังมีวิธีเรียกโดยตรงแต่ละคลาสที่สืบทอดมา:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

ทราบเพียงว่าถ้าคุณทำมันด้วยวิธีนี้คุณจะต้องโทรหากันด้วยตนเองตามที่ผมค่อนข้างมั่นใจว่าFirstของ__init__()จะไม่ถูกเรียก


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

@Trilarion ใช่ฉันมั่นใจว่ามันจะไม่ อย่างไรก็ตามฉันไม่ทราบแน่ชัดและฉันไม่ต้องการพูดราวกับว่าฉันทำแม้ว่าจะเป็นไปได้ยากมาก นั่นเป็นประเด็นที่ดีเกี่ยวกับการobjectถูกเรียกสองครั้ง ฉันไม่ได้คิดอย่างนั้น ฉันแค่ต้องการทำให้จุดที่คุณเรียกชั้นเรียนผู้ปกครองโดยตรง
Seaux

น่าเสียดายที่ตัวแบ่งนี้ถ้าinitพยายามเข้าถึงวิธีการส่วนตัว :(
Erik Aronesty

21

ทั้งหมด

สมมติว่าทุกอย่างสืบทอดมาจากobject(คุณเป็นของคุณเองหากไม่มี) Python คำนวณลำดับการแก้ไขเมธอด (MRO) โดยยึดตามโครงสร้างการสืบทอดคลาสของคุณ The MRO ตอบสนอง 3 คุณสมบัติ:

  • เด็ก ๆ ของชั้นเรียนมาก่อนพ่อแม่ของพวกเขา
  • พ่อแม่ที่ยังเหลือมาก่อนพ่อแม่ที่ถูกต้อง
  • คลาสจะปรากฏเพียงครั้งเดียวใน MRO

หากไม่มีการสั่งซื้อดังกล่าวข้อผิดพลาดของ Python ผลงานภายในของสิ่งนี้คือ C3 Linerization ของตระกูลคลาส อ่านทั้งหมดได้ที่นี่: https://www.python.org/download/releases/2.3/mro/

ดังนั้นในตัวอย่างทั้งสองด้านล่างนี้คือ:

  1. เด็ก
  2. ซ้าย
  3. ขวา
  4. ผู้ปกครอง

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

ด้วยsuperวิธีแรกในแต่ละวิธี

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() ขาออก:

parent
right
left
child

ด้วยsuperวิธีสุดท้ายในแต่ละวิธี

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() ขาออก:

child
left
right
parent

ผมเห็นว่าคุณสามารถเข้าถึงLeftการใช้จากsuper() Childสมมติว่าผมต้องการที่จะเข้าถึงจากภายในRight Childมีวิธีเข้าถึงRightจากการChildใช้ super หรือไม่? หรือฉันควรโทรโดยตรงRightจากภายในsuper?
alpha_989

4
@ alpha_989 หากคุณต้องการเข้าถึงวิธีการเรียนเฉพาะเท่านั้นคุณควรอ้างอิงชั้นเรียนโดยตรงแทนที่จะใช้ super Super เป็นเรื่องเกี่ยวกับการตามสายโซ่แห่งการสืบทอดไม่ได้ไปสู่วิธีการเรียนเฉพาะ
Zags

1
ขอขอบคุณที่กล่าวถึง 'คลาสจะปรากฏเพียงครั้งเดียวใน MRO' นี่เป็นการแก้ไขปัญหาของฉัน ในที่สุดฉันก็เข้าใจว่ามรดกทำงานอย่างไร มีคนต้องการพูดถึงคุณสมบัติของ MRO!
Tushar Vazirani

18

เกี่ยวกับความคิดเห็นของ @ calfzhouคุณสามารถใช้งานได้ตามปกติ**kwargs:

ตัวอย่างการใช้งานออนไลน์

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

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

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

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


B1(a1=6, b1=5, b="hello", a=None)

ผลลัพธ์:

A None
B hello
A1 6
B1 5

คุณยังสามารถใช้พวกเขาในตำแหน่ง:

B1(5, 6, b="hello", a=None)

แต่คุณต้องจำ MRO มันทำให้สับสนจริงๆ

ฉันอาจจะน่ารำคาญนิดหน่อย แต่ฉันสังเกตเห็นว่าคนลืมทุกครั้งที่ใช้*argsและ**kwargsเมื่อพวกเขาแทนที่วิธีการในขณะที่มันเป็นหนึ่งในไม่กี่คนที่มีประโยชน์จริงๆและใช้สติของ 'ตัวแปรเวท' เหล่านี้


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

15

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

ตัวอย่าง:

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

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

ให้:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

การเรียกซูเปอร์คลาส__init__โดยตรงกับการกำหนดพารามิเตอร์โดยตรงนั้นดึงดูดมาก แต่ล้มเหลวหากมีการsuperเรียกใด ๆในซูเปอร์คลาสและ / หรือ MRO เปลี่ยนไปและคลาส A อาจถูกเรียกหลายครั้งขึ้นอยู่กับการใช้งาน

เพื่อสรุป: การสืบทอดแบบร่วมมือและพารามิเตอร์พิเศษและพิเศษสำหรับการเริ่มต้นไม่ได้ทำงานร่วมกันได้ดีนัก


5
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

ผลผลิตคือ

first 10
second 20
that's it

การโทรหาบุคคลที่สาม () หาตำแหน่งinit ที่กำหนดไว้ใน Third และเรียก super ในรูทีน invokes init ที่กำหนดไว้ใน First MRO = [แรกสอง] ตอนนี้เรียกร้องให้สุดในinitกำหนดไว้ในครั้งแรกจะยังคงค้นหา MRO และหาinitกำหนดไว้ในที่สองและการเรียกร้องใด ๆ ที่จะสุดจะตีวัตถุเริ่มต้นinit ฉันหวังว่าตัวอย่างนี้จะอธิบายแนวคิด

ถ้าคุณไม่โทรสุดยอดจาก First โซ่หยุดและคุณจะได้รับผลลัพธ์ต่อไปนี้

first 10
that's it

1
นั่นเป็นเพราะในชั้นเรียนแรกคุณเรียกว่า 'พิมพ์' ก่อนแล้วจึง 'ยอดเยี่ยม'
rocky qi

2
นั่นคือเพื่อแสดงให้เห็นถึงคำสั่งเรียก
Seraj Ahmad

4

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

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

โดยการเพิ่มฟังก์ชั่น super ()

super(First, self).__init__() #example for class First.

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

จากตัวอย่างด้านล่างคุณสามารถคัดลอกและวางแล้วลองเรียกใช้:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

มันทำงานอย่างไร อินสแตนซ์ที่ห้า () จะเป็นเช่นนี้ แต่ละขั้นตอนเปลี่ยนจากคลาสเป็นคลาสที่มีการเพิ่มฟังก์ชั่นพิเศษ

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

พบผู้ปกครองและมันจะไปต่อที่สามและสี่ !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

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

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

ผลลัพธ์ของโปรแกรมด้านบน:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

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


ขอบคุณสำหรับการสาธิตอย่างละเอียด!
Tushar Vazirani

3

ฉันต้องการเพิ่มสิ่งที่ @Visionscaper พูดที่ด้านบน:

Third --> First --> object --> Second --> object

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

linearisation (mro) ของคลาส C, L (C) คือ

  • คลาส C
  • บวกกับการรวม
    • linearisation ของพ่อแม่ P1, P2, .. = L (P1, P2, ... ) และ
    • รายการผู้ปกครองของ P1, P2, ..

การผสานเชิงเส้นทำได้โดยการเลือกคลาสทั่วไปที่ปรากฏเป็นส่วนหัวของรายการและไม่ใช่ส่วนท้ายตั้งแต่ลำดับสำคัญ (จะชัดเจนด้านล่าง)

การคำนวณเชิงเส้นที่สามสามารถคำนวณได้ดังนี้:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

ดังนั้นสำหรับการใช้งาน super () ในรหัสต่อไปนี้:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

จะเห็นได้ชัดว่าวิธีนี้จะได้รับการแก้ไขอย่างไร

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"

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

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

3

ใน python 3.5+ ที่สืบทอดนั้นดูดีและคาดเดาได้ดี กรุณาดูรหัสนี้:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

ขาออก:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

อย่างที่คุณเห็นมันเรียกว่า foo หนึ่งครั้งสำหรับแต่ละ chain ที่สืบทอดตามลำดับเดียวกันกับที่สืบทอด คุณจะได้รับการสั่งซื้อโดยการเรียกว่า mro :

Fourth -> Third -> First -> Second -> Base -> object


2

อาจมีบางสิ่งที่สามารถเพิ่มได้ตัวอย่างเล็ก ๆ ของ Django rest_framework และมัณฑนากร สิ่งนี้ให้คำตอบสำหรับคำถามโดยนัย: "ทำไมฉันถึงต้องการสิ่งนี้ต่อไป"

ดังที่กล่าวไว้: เราอยู่กับ Django rest_framework และเราใช้มุมมองทั่วไปและสำหรับแต่ละประเภทของวัตถุในฐานข้อมูลของเราเราพบว่าเรามีคลาสการดูหนึ่งที่ให้ GET และ POST สำหรับรายการของวัตถุและคลาสการดูอื่นที่ให้ GET , PUT และ DELETE สำหรับแต่ละวัตถุ

ตอนนี้ POST, PUT และ DELETE เราต้องการตกแต่งด้วย login_required ของ Django ขอให้สังเกตว่าวิธีนี้สัมผัสทั้งสองเรียน แต่ไม่ได้ทุกวิธีในชั้นเรียนทั้ง

วิธีแก้ปัญหาสามารถผ่านการสืบทอดหลายอย่าง

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

เช่นเดียวกันกับวิธีอื่น ๆ

ในรายการมรดกของการเรียนที่เป็นรูปธรรมของฉันฉันจะเพิ่มของฉันLoginToPostก่อนListCreateAPIViewและก่อนที่จะLoginToPutOrDelete RetrieveUpdateDestroyAPIViewชั้นเรียนที่เป็นรูปธรรมของฉันgetจะไม่ได้รับการตกแต่ง


1

โพสต์คำตอบนี้สำหรับการอ้างอิงในอนาคตของฉัน

งูหลามหลายมรดกควรใช้รูปแบบเพชรและฟังก์ชั่นลายเซ็นไม่ควรเปลี่ยนแปลงในรูปแบบ

    A
   / \
  B   C
   \ /
    D

ตัวอย่างโค้ดจะเป็น; -

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

นี่คือคลาส A object


1
Aควรนอกจากนี้ยัง__init__จะมีการเรียก Aไม่ได้ "ประดิษฐ์" วิธีการ__init__ดังนั้นจึงไม่สามารถสรุปได้ว่าคลาสอื่นอาจมีAMRO ก่อนหน้านี้ ชั้นเดียวที่มี__init__วิธีการไม่ได้ (และไม่ควร) โทรคือsuper().__init__ object
chepner

ใช่. นั่นคือเหตุผลที่ฉันเขียน A objectบางทีฉันคิดว่าฉันควรเขียนclass A (object) : แทน
Akhil Nadh PC

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