سؤال

ما الفرق بين:

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

و:

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

رأيت super يتم استخدامها كثيرًا في الفصول الدراسية ذات الميراث الواحد فقط.أستطيع أن أرى سبب استخدامك له في الميراث المتعدد ولكني غير واضح بشأن مزايا استخدامه في هذا النوع من المواقف.

هل كانت مفيدة؟

المحلول

وفوائد super() في الميراث واحد ضئيلة - في الغالب، لم يكن لديك إلى رمز القرص الثابت اسم الفئة الأساسية في كل أسلوب يستخدم أساليب الأم

.

ومع ذلك، فإنه يكاد يكون من المستحيل استخدام متعددة الميراث دون super(). وهذا يشمل التعابير الشائعة مثل mixins، واجهات، فئات مجردة، وما إلى ذلك يمتد إلى التعليمات البرمجية التي تمتد لك في وقت لاحق. إذا كان شخص ما أراد بعد ذلك إلى كتابة فئة التي امتدت Child وmixin، فإن مدوناتها لا تعمل بشكل صحيح.

نصائح أخرى

ماهو الفرق؟

SomeBaseClass.__init__(self) 

يعني الاتصال SomeBaseClass__init__.بينما

super(Child, self).__init__()

يعني استدعاء الحد __init__ من الطبقة الأم التي تليها Child في ترتيب تحليل الأسلوب (MRO) الخاص بالمثيل.

إذا كان المثيل عبارة عن فئة فرعية من الطفل، فقد يكون هناك أصل مختلف يأتي بعد ذلك في MRO.

وأوضح ببساطة

عندما تكتب فصلًا دراسيًا، فأنت تريد أن تتمكن الفصول الأخرى من استخدامه. super() يسهل على الفصول الأخرى استخدام الفصل الذي تكتبه.

وكما يقول بوب مارتن، فإن التصميم الجيد يسمح لك بتأجيل اتخاذ القرار لأطول فترة ممكنة.

super() يمكن تمكين هذا النوع من الهندسة المعمارية.

عندما يقوم فصل آخر بتصنيف الفصل الذي كتبته، فمن الممكن أيضًا أن يكون وراثًا من فئات أخرى.ويمكن أن تحتوي تلك الفئات على __init__ الذي يأتي بعد هذا __init__ بناءً على ترتيب الفئات لحل الطريقة.

بدون super من المحتمل أن تقوم بترميز أصل الفصل الذي تكتبه (كما يفعل المثال).هذا يعني أنك لن تتصل بالآخر __init__ في MRO، وبالتالي لن تتمكن من إعادة استخدام الكود الموجود فيه.

إذا كنت تكتب الكود الخاص بك للاستخدام الشخصي، فقد لا تهتم بهذا التمييز.ولكن إذا كنت تريد أن يستخدم الآخرون التعليمات البرمجية الخاصة بك، فاستخدم super هو الشيء الوحيد الذي يتيح مرونة أكبر لمستخدمي الكود.

بايثون 2 مقابل 3

يعمل هذا في Python 2 و 3:

super(Child, self).__init__()

هذا يعمل فقط في بيثون 3:

super().__init__()

إنه يعمل بدون وسائط عن طريق الانتقال لأعلى في إطار المكدس والحصول على الوسيطة الأولى للطريقة (عادةً self لطريقة المثال أو cls لطريقة الفصل - ولكن يمكن أن تكون أسماء أخرى) والعثور على الفصل (على سبيل المثال. Child) في المتغيرات الحرة (يتم البحث عنها بالاسم __class__ كمتغير إغلاق حر في الطريقة).

أفضّل عرض طريقة الاستخدام المتوافقة super, ، ولكن إذا كنت تستخدم Python 3 فقط، فيمكنك استدعاؤه بدون وسائط.

المراوغة مع التوافق إلى الأمام

ماذا يعطيك؟بالنسبة للميراث الفردي، فإن الأمثلة الواردة في السؤال متطابقة عمليًا من وجهة نظر التحليل الثابت.ومع ذلك، باستخدام 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

يفشل استخدام الطفل غير الفائق في حقن التبعية لأن الطفل الذي تستخدمه قام بترميز الطريقة ليتم استدعاؤها بعد خاصته:

>>> 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

معالجة تعليق

لماذا في العالم سيكون هذا مفيدًا؟

تقوم بايثون بتحويل شجرة الميراث المعقدة عبر خوارزمية الخطية C3 لإنشاء أمر تحليل الطريقة (MRO).

نريد أن يتم البحث عن الأساليب بهذا الترتيب.

للحصول على طريقة محددة في أحد الوالدين للعثور على الطريقة التالية بهذا الترتيب بدون super, ، لا بد من ذلك

  1. احصل على mro من نوع المثيل
  2. ابحث عن النوع الذي يحدد الطريقة
  3. ابحث عن النوع التالي باستخدام الطريقة
  4. ربط هذه الطريقة واستدعائها بالوسائط المتوقعة

ال UnsuperChild لا ينبغي أن يكون الوصول إليها InjectMe.لماذا لا يكون الاستنتاج "تجنب الاستخدام دائمًا super"؟ما الذي أفتقده هنا؟

ال UnsuperChild يفعل لا الوصول الى InjectMe.انها UnsuperInjector الذي لديه حق الوصول إلى InjectMe - ومع ذلك لا يمكن استدعاء طريقة تلك الفئة من الطريقة التي ورثت منها UnsuperChild.

تهدف كلا الفئتين الفرعيتين إلى استدعاء أسلوب يحمل نفس الاسم الذي يأتي بعد ذلك في MRO، والذي قد يكون كذلك آخر فئة لم يكن على علم بها عندما تم إنشاؤها.

الذي بدون super يقوم بترميز الطريقة الأصلية الخاصة به - وبالتالي قام بتقييد سلوك طريقته، ولا يمكن للفئات الفرعية إدخال وظائف في سلسلة الاستدعاء.

الواحد مع super يتمتع بمرونة أكبر.يمكن اعتراض سلسلة الاستدعاء الخاصة بالطرق وحقن الوظائف.

قد لا تحتاج إلى هذه الوظيفة، ولكن قد تحتاج إلى فئات فرعية من التعليمات البرمجية الخاصة بك.

خاتمة

دائما يستخدم super للإشارة إلى الفئة الأصل بدلاً من ترميزها بشكل ثابت.

ما تنوي فعله هو الإشارة إلى الفئة الأصل التالية في السطر، وليس على وجه التحديد الفئة التي ترى الطفل يرث منها.

عدم استخدام super يمكن وضع قيود غير ضرورية على مستخدمي التعليمات البرمجية الخاصة بك.

ولا كل هذا يفترض أن الفئة الأساسية هي فئة الطراز الجديد؟

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

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

ولن تعمل في بيثون 2. class A يجب أن يكون على النمط الجديد، أي بمعنى: class A(object)

لقد لعبت قليلا مع super(), ، وقد أدركنا أنه يمكننا تغيير ترتيب الاتصال.

على سبيل المثال، لدينا الهيكل الهرمي التالي:

    A
   / \
  B   C
   \ /
    D

في هذه الحالة MRO سيكون 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. القصة الداخلية للفصول الدراسية ذات النمط الجديد

عند يدعو super() لحل إلى الإصدار أحد الوالدين من classmethod، طريقة سبيل المثال، أو staticmethod، ونحن نريد لتمرير الطبقة الحالية نطاقها نحن في مثل الوسيطة الأولى، والتي تشير إلى الوالدين نطاق نحاول حل ل، ونتيجة لالحجة الثانية موضع اهتمام للإشارة إلى الكائن الذي نحاول تطبيق ذلك مجالا ل.

والنظر في 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 مع staticmethod

ومنها مثلا. باستخدام 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__() لاتخاذ كأول المعلمة في إشارة إلى فئة الدعوة، فمن <م> لا تنفيذها في بيثون كما classmethod، بل staticmethod. وهذا هو، في اشارة الى فئة لديها لتمريرها صراحة كما الوسيطة الأولى عندما تدعو __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__() حكم عام هو staticmethod، سوف super(A, cls).__new__ أيضا إرجاع staticmethod ويحتاج إلى أن يتم توفير كل الحجج صراحة، بما في ذلك الإشارة إلى وجوه insterest، في هذه الحالة 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- classmethod يمكن استدعاؤها من الطبقة مباشرة ويأخذ كمعلمة أولى إشارة إلى فئة.

# 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 إلى حل للنسخة الأم من ذلك، ونحن نريد لتمرير الطبقة الطفل الحالية كما الوسيطة الأولى التي تشير إلى الوالدين نطاق نحاول حل ل، وموضع اهتمام كما الوسيطة الثانية التي تشير إلى كائن نريد أن تطبق ذلك مجالا ل، التي بشكل عام هو إشارة إلى فئة الأطفالنفسه أو واحدة من الفئات الفرعية.

super(B, cls_or_subcls)

و3- super(B, cls) دعوة يحل إلى نطاق A وينطبق ذلك على cls. منذ alternate_constructor() هو classmethod و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)
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.

بعض الإجابات الرائعة هنا، لكنها لا تتناول كيفية الاستخدام super() في حالة وجود فئات مختلفة في التسلسل الهرمي لها توقيعات مختلفة ...خاصة في حالة __init__

للإجابة على هذا الجزء والقدرة على الاستخدام الفعال super() أقترح قراءة إجابتي 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

والعديد من الإجابات كبيرة، ولكن للمتعلمين البصرية: يتيح أولا استكشاف بحجج إلى super، ومن ثم الاستغناء عنها.

وتخيل ثيريس على jack مثيل إنشاؤها من Jack الصف، الذي لديه سلسلة الميراث كما هو مبين باللون الأخضر في الصورة. الدعوة:

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

وسوف تستخدم MRO (الطريقة قرار بالدفع) من jack (شجرة الميراث في ترتيب معين)، وسيبدأ البحث من Jack. لماذا يمكن للمرء أن توفر الفئة الأصل؟ حسنا إذا بدأنا البحث من jack سبيل المثال، فإنه العثور على أسلوب مثيل، وبيت القصيد هو العثور على طريقة والديه.

وإذا كان أحد لا تقدم الحجج إلى super، في مثل الوسيطة الأولى مرت في غير فئة self، والحجة الثانية مرت في غير self. وتحسب لصناعة السيارات في هذه لك في Python3.

ولكن نقول نحن لا ترغب في استخدام طريقة Jack، وبدلا من تمرير في Jack، ونحن يمكن للصدر في Jen لبدء البحث صعودا للأسلوب من Jen.

وانه يبحث طبقة واحدة في وقت واحد (بعرض لا عمق)، على سبيل المثال إذا Adam والطريقة المطلوبة، واحدة من Sue سيتم العثور Sue وكلا لأول مرة.

إذا Cain وSue سواء كانت طريقة المطلوبة، سيتم استدعاء أسلوب Cain أولا. وهذا يتوافق في التعليمات البرمجية إلى:

Class Jen(Cain, Sue):

وMRO هو من اليسار إلى اليمين.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top