實例分析Python3 MRO 和 super()

原文鏈接:https://www.linuxexam.net/2020/04/python3-mro-and-super.html

1 MRO vs DFLR

Attribute searching is the core concept for Python classes. The searching order is important as it determines which class's attributes are used ( the one first searched is used ).

 

1.1 DFLR is used for Python2's classic classes 

In Python2's classic classes, the searching method is called DFLR ( Depth First, Left to Right ). Let's take an example to make it easier to understand.

 

Classes B and C inherit from A, while class D inherits from B and C.

 

Figure 1

 

The DFLR order is D, B, A, C, A

Please note that A appears twice on the list.

1.2 MRO is used for Python3

In Python3, every class's most top parent must be 'object'. So if a class has more than one parent class, 'object' is always listed multiple times on DFLR. 

 

This will cause two issues:

  • The performance suffers from multiple scanning object class. 
  • The object class's attributes have higher priority than any other classes on the right.

To solve these issues, Python3 replaces DFLR with MRO. MRO stands for Method Resolution Order. Despite its name, MRO is used for any attributes besides methods.

 

MRO is a simple improvement to DFLR. It just removes all but the last occurrence of duplicated classes in the DFLR list. Let's still use the example in Figure 1.

 

The DFLR list is D, B, A, C, A

The corresponding MRO list is D, B, A, C, A.

MRO can be easily accessed by class.__mro__ or class.mro().

>>> class A: pass
...
>>> class B(A): pass
...
>>> class C(A): pass
...
>>> class D(B,C): pass
...
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
>>> D.mro()
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

2 super() can invoke non-superclass 

super() is a builtin provided by Python3 for users to utilize MRO. Usually, python progammers explicitly call parent class's method with class name.

 

cat super1.py

class A:

    def __init__(self):

        print('A.__init__()')

class B(A):

    def __init__(self):

        print("B.__init__()")

        A.__init__(self)

x = B()

 

python3 super1.py

B.__init__()

A.__init__()

 

This seems a little bit noisy. If class A changes its name, we have to modify two places in class B accordingly.

 

Like Java, Python3 provides a super() builtin for this.

 

cat super2.py

 

class A:

    def __init__(self):

        print('A.__init__()')

class B(A):

    def __init__(self):

        print("B.__init__()")

        super().__init__()

x = B()

 

Everything looks perfect now.

But super() is not that simple. It dynamically calculates the invoked method based on MRO, which means the super() here does NOT always invoke class A.  A real example would be much easier for us to understand what this means.

 

cat super3.py

#!/usr/bin/python3

 

class A:

    def __init__(self):

        print('A.__init__()')

 

class B(A):

    def __init__(self):

        print("B.__init__()")

        super().__init__()

 

class C(A):

    def __init__(self):

        print("C.__init__()")

 

class D(B,C): pass

 

obj = D()

 

python3 super3.py

B.__init__()

C.__init__()

 

Here we can see that the "super()" in class B actually invoked class C rather than its real superclass A.

 

"super()" here calculates the invoked class based on two facts, the class D's MRO and current class B.

 

class D's MRO is D, B, C, A, object.

super() will invoke the class after B in D's MRO, which is C.

 

super() actually accepts parameters as 

 

super(current_class, instance_object)

 

Inside it

  • gets the MRO of instance_object.__class__
  • gets the next class in MRO after current_class

 

cat super4.py

 

class A:

    def __init__(self):

        print('A.__init__()')

 

class B(A):

    def __init__(self):

        print("B.__init__()")

        super( __class__, self).__init__()

 

class C(A):

    def __init__(self):

        print("C.__init__()")

 

class D(B,C): pass

 

obj = D()

 

super(B, obj).__init__()

 

python3 super4.py

B.__init__()

C.__init__()

C.__init__()

 

3 super() is not that good

super() seems good at first look, but it can cause lots of issues. MRO order may not be what you really want when invoking a method.

 

More debates about super() can be found in the section "The super Built-in Function: For better or worse" of Learning Python (5th Edition).

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章