नई शैली की कक्षाओं में विधि रिज़ॉल्यूशन ऑर्डर (MRO)?


94

पायथन इन ए नटशेल (द्वितीय संस्करण) पुस्तक में एक उदाहरण है जो
पुरानी शैली की कक्षाओं का उपयोग करके प्रदर्शित करता है कि क्लासिक रिज़ॉल्यूशन ऑर्डर में तरीकों को कैसे हल किया
जाता है और नए ऑर्डर के साथ यह कैसे अलग है।

मैंने नई शैली में उदाहरण को फिर से लिखने के द्वारा एक ही उदाहरण की कोशिश की, लेकिन परिणाम पुराने शैली वर्गों के साथ प्राप्त किए गए से अलग नहीं है। उदाहरण को चलाने के लिए मैं जिस अजगर संस्करण का उपयोग कर रहा हूं वह 2.5.2 है। नीचे उदाहरण है:

class Base1(object):  
    def amethod(self): print "Base1"  

class Base2(Base1):  
    pass

class Base3(object):  
    def amethod(self): print "Base3"

class Derived(Base2,Base3):  
    pass

instance = Derived()  
instance.amethod()  
print Derived.__mro__  

कॉल instance.amethod()प्रिंट करता है Base1, लेकिन आउटपुट की नई शैली के साथ एमआरओ की मेरी समझ के अनुसार आउटपुट होना चाहिए था Base3। कॉल Derived.__mro__प्रिंट:

(<class '__main__.Derived'>, <class '__main__.Base2'>, <class '__main__.Base1'>, <class '__main__.Base3'>, <type 'object'>)

मुझे यकीन नहीं है कि नई शैली की कक्षाओं के साथ एमआरओ की मेरी समझ गलत है या मैं एक मूर्खतापूर्ण गलती कर रहा हूं जिसका मैं पता नहीं लगा पा रहा हूं। कृपया एमआरओ की बेहतर समझ में मेरी मदद करें।

जवाबों:


183

विरासत बनाम नई शैली की कक्षाओं के लिए रिज़ॉल्यूशन ऑर्डर के बीच महत्वपूर्ण अंतर तब होता है जब एक ही पूर्वज वर्ग "भोले", गहराई-पहले दृष्टिकोण में एक से अधिक बार होता है - उदाहरण के लिए, "हीरे की विरासत" मामले पर विचार करें:

>>> class A: x = 'a'
... 
>>> class B(A): pass
... 
>>> class C(A): x = 'c'
... 
>>> class D(B, C): pass
... 
>>> D.x
'a'

यहाँ, विरासत-शैली, रिज़ॉल्यूशन ऑर्डर D - B - A - C - A: है, इसलिए Dx को देखते समय, A इसे हल करने के लिए रिज़ॉल्यूशन ऑर्डर में पहला आधार है, जिससे सी। में परिभाषा छिपाई जा सकती है:

>>> class A(object): x = 'a'
... 
>>> class B(A): pass
... 
>>> class C(A): x = 'c'
... 
>>> class D(B, C): pass
... 
>>> D.x
'c'
>>> 

यहाँ, नई शैली, आदेश है:

>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, 
    <class '__main__.A'>, <type 'object'>)

साथ Aकेवल एक बार और उसके उपवर्गों की सब के बाद संकल्प क्रम में आने के लिए, मजबूर तो ओवरराइड (यानी, सदस्य की सी ओवरराइड कि x) वास्तव में समझदारी से काम करते हैं।

यह एक कारण है कि पुरानी शैली की कक्षाओं से बचा जाना चाहिए: "हीरे की तरह" पैटर्न के साथ कई विरासत बस उनके साथ समझदारी से काम नहीं करती हैं, जबकि यह नई शैली के साथ करता है।


2
"(पूर्वज वर्ग] A [है] को केवल एक बार और उसके सभी उपवर्गों के बाद संकल्प क्रम में आने के लिए मजबूर किया जाता है, ताकि ओवरराइड (यानी, सदस्य के एक्स के ओवरराइड) वास्तव में समझदारी से काम लें।" - एपिफनी! इस वाक्य के लिए धन्यवाद, मैं अपने सिर में फिर से एमआरओ कर सकता हूं। \ o / बहुत बहुत धन्यवाद।
एस्टीस

23

पायथन की विधि संकल्प क्रम वास्तव में हीरे के पैटर्न को समझने की तुलना में अधिक जटिल है। इसे वास्तव में समझने के लिए, सी 3 रैखिककरण पर एक नज़र डालें । मैंने पाया है कि ऑर्डर ट्रैक करने के लिए तरीकों का विस्तार करते समय प्रिंट स्टेटमेंट का उपयोग करना वास्तव में मदद करता है। उदाहरण के लिए, आपको क्या लगता है कि इस पैटर्न का आउटपुट क्या होगा? (ध्यान दें: 'X' को दो क्रॉसिंग एज माना जाता है, नोड नहीं और ^ सुपर को कॉल करने वाले तरीकों को दर्शाता है ())

class G():
    def m(self):
        print("G")

class F(G):
    def m(self):
        print("F")
        super().m()

class E(G):
    def m(self):
        print("E")
        super().m()

class D(G):
    def m(self):
        print("D")
        super().m()

class C(E):
    def m(self):
        print("C")
        super().m()

class B(D, E, F):
    def m(self):
        print("B")
        super().m()

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


#      A^
#     / \
#    B^  C^
#   /| X
# D^ E^ F^
#  \ | /
#    G

क्या आपको ABDCEFG मिला?

x = A()
x.m()

एक त्रुटि के बहुत परीक्षण के बाद, मैं सी 3 रेखीयकरण की एक अनौपचारिक ग्राफ सिद्धांत व्याख्या निम्नानुसार आया: (कोई मुझे बताएं कि क्या यह गलत है।)

इस उदाहरण पर विचार करें:

class I(G):
    def m(self):
        print("I")
        super().m()

class H():
    def m(self):
        print("H")

class G(H):
    def m(self):
        print("G")
        super().m()

class F(H):
    def m(self):
        print("F")
        super().m()

class E(H):
    def m(self):
        print("E")
        super().m()

class D(F):
    def m(self):
        print("D")
        super().m()

class C(E, F, G):
    def m(self):
        print("C")
        super().m()

class B():
    def m(self):
        print("B")
        super().m()

class A(B, C, D):
    def m(self):
        print("A")
        super().m()

# Algorithm:

# 1. Build an inheritance graph such that the children point at the parents (you'll have to imagine the arrows are there) and
#    keeping the correct left to right order. (I've marked methods that call super with ^)

#          A^
#       /  |  \
#     /    |    \
#   B^     C^    D^  I^
#        / | \  /   /
#       /  |  X    /   
#      /   |/  \  /     
#    E^    F^   G^
#     \    |    /
#       \  |  / 
#          H
# (In this example, A is a child of B, so imagine an edge going FROM A TO B)

# 2. Remove all classes that aren't eventually inherited by A

#          A^
#       /  |  \
#     /    |    \
#   B^     C^    D^
#        / | \  /  
#       /  |  X    
#      /   |/  \ 
#    E^    F^   G^
#     \    |    /
#       \  |  / 
#          H

# 3. For each level of the graph from bottom to top
#       For each node in the level from right to left
#           Remove all of the edges coming into the node except for the right-most one
#           Remove all of the edges going out of the node except for the left-most one

# Level {H}
#
#          A^
#       /  |  \
#     /    |    \
#   B^     C^    D^
#        / | \  /  
#       /  |  X    
#      /   |/  \ 
#    E^    F^   G^
#               |
#               |
#               H

# Level {G F E}
#
#         A^
#       / |  \
#     /   |    \
#   B^    C^   D^
#         | \ /  
#         |  X    
#         | | \
#         E^F^ G^
#              |
#              |
#              H

# Level {D C B}
#
#      A^
#     /| \
#    / |  \
#   B^ C^ D^
#      |  |  
#      |  |    
#      |  |  
#      E^ F^ G^
#            |
#            |
#            H

# Level {A}
#
#   A^
#   |
#   |
#   B^  C^  D^
#       |   |
#       |   |
#       |   |
#       E^  F^  G^
#               |
#               |
#               H

# The resolution order can now be determined by reading from top to bottom, left to right.  A B C E D F G H

x = A()
x.m()

आपको अपना दूसरा कोड सही करना चाहिए: आपने वर्ग "I" को पहली पंक्ति के रूप में रखा है और सुपर का उपयोग भी किया है, इसलिए यह सुपर क्लास "G" को ढूंढ रहा है, लेकिन "I" प्रथम श्रेणी है इसलिए यह कभी भी "G" वर्ग को नहीं खोज पाएगा क्योंकि वहाँ कोई "G" ऊपरी "I" नहीं है। कक्षा "I" को "G" और "F" :)
Aaditya Ura

उदाहरण कोड गलत है। superतर्क की आवश्यकता है।
डैनी

2
सुपर क्लास परिभाषा के अंदर () को तर्कों की आवश्यकता नहीं है। Https://docs.python.org/3/library/functions.html#super
बेन

आपका ग्राफ सिद्धांत अनावश्यक रूप से जटिल है। चरण 1 के बाद, दाईं ओर (किसी भी विरासत सूची में) बाईं ओर कक्षाओं से किनारों को डालें और फिर एक टोपोलॉजिकल सॉर्ट करें और आपका काम हो गया।
केविन

@ केविन मुझे नहीं लगता कि यह सही है। मेरे उदाहरण के बाद, ACDBEFGH एक वैध टोपोलॉजिकल प्रकार नहीं होगा? लेकिन यह संकल्प आदेश नहीं है।
बेन

5

आपको मिलने वाला परिणाम सही है। के आधार वर्ग बदलने का प्रयास करें Base3करने के लिए Base1और क्लासिक कक्षाओं के लिए एक ही पदानुक्रम के साथ तुलना करें:

class Base1(object):
    def amethod(self): print "Base1"

class Base2(Base1):
    pass

class Base3(Base1):
    def amethod(self): print "Base3"

class Derived(Base2,Base3):
    pass

instance = Derived()
instance.amethod()


class Base1:
    def amethod(self): print "Base1"

class Base2(Base1):
    pass

class Base3(Base1):
    def amethod(self): print "Base3"

class Derived(Base2,Base3):
    pass

instance = Derived()
instance.amethod()

अब यह आउटपुट:

Base3
Base1

अधिक जानकारी के लिए यह स्पष्टीकरण पढ़ें ।


1

आप उस व्यवहार को देख रहे हैं क्योंकि विधि संकल्प गहराई-पहले है, चौड़ाई-प्रथम नहीं। दरवेश की विरासत जैसी दिखती है

         Base2 -> Base1
        /
Derived - Base3

इसलिए instance.amethod()

  1. Base2 की जाँच करता है, एमेथोड नहीं ढूँढता है।
  2. बेस 2 को बेस 1 से विरासत में मिला है, और बेस 1 की जांच करता है। बेस 1 में एक है amethod, इसलिए इसे बुलाया जाता है।

इसमें परिलक्षित होता है Derived.__mro__Derived.__mro__जब आप खोजे जा रहे विधि को पाते हैं, तो बस इसे रोक दें।


मुझे संदेह है कि मुझे जवाब के रूप में "बेस 1" मिलता है क्योंकि विधि संकल्प गहराई-पहले है, मुझे लगता है कि गहराई-पहले दृष्टिकोण की तुलना में इसके लिए अधिक है। डेनिस का उदाहरण देखें, अगर यह गहराई पहले ओ / पी "बेस 1" होना चाहिए था। साथ ही आपके द्वारा दिए गए लिंक में पहले उदाहरण का संदर्भ लें, वहां दिखाया गया एमआरओ यह भी बताता है कि विधि का रिज़ॉल्यूशन केवल गहराई-पहले क्रम में ट्रेस करके निर्धारित नहीं किया गया है।
सप्तम डिक

क्षमा करें एमआरओ पर दस्तावेज़ का लिंक डेनिस द्वारा प्रदान किया गया है। कृपया जांचें कि, मैंने गलत समझा कि आपने मुझे python.org का लिंक प्रदान किया है।
सतेश

4
यह आम तौर पर गहराई-पहले है, लेकिन एलेक्स द्वारा समझाया गया हीरे की तरह विरासत को संभालने के लिए स्मार्ट हैं।
जैमेसन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.