大数跨境

Python 的秘密生活:super() 和方法解析顺序

Python 的秘密生活:super() 和方法解析顺序 索引目录
2025-12-10
1
导读:关注「索引目录」公众号,获取更多干货。为什么 super() 不表示“父类”(以及它真正的作用)蒂莫西茫然地盯着屏幕。

关注「索引目录」公众号,获取更多干货。

为什么 super() 不表示“父类”(以及它真正的作用)


蒂莫西茫然地盯着屏幕。他写的明明是简单的继承代码,但输出结果却完全不知所云。

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

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

class C(A):
    def process(self):
        print("C.process()")
        super().process()

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

d = D()
d.process()

他预期输出结果为:

D.process()
B.process()
A.process()

毕竟,D继承自B,而又B继承自A。继承就是这样运作的,对吧?

但当他运行代码时,却得到了以下结果:

D.process()
B.process()
C.process()
A.process()

“什么?!”蒂莫西惊呼道,吸引了图书馆另一头的玛格丽特的注意。“为什么会C.process()调用?类B又不继承自C!当B调用时super().process(),它应该调用A.process(),而不是C.process()!”

玛格丽特走了过来,脸上带着一丝了然的微笑。“啊,你发现了Python中最容易被误解的特性之一。你以为super()`__init__`的意思是‘调用我的父类’,对吧?”

“当然会!super()它调用的是超类——父类!”

“没错,”玛格丽特温和地说,“大家都是这么想的。但这完全错了。super()蒂莫西,它不会调用你的父类,而是调用方法解析顺序中的下一个类。”

“什么……什么?”

“来,”玛格丽特说着,打开一本厚厚的书,书名是《方法解析顺序:Python的隐藏路径》。“让我来给你展示一下Python究竟是如何解析方法的。”


方法决议令(MRO)

“Python 中的每个类,”玛格丽特开始说道,“都有一个名为‘方法解析顺序’的属性__mro__。它是一个元组,定义了 Python 在查找属性时遵循的确切顺序。”

她输入:

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

print(D.__mro__)

输出:

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

玛格丽特解释说:“这个元组就是 Python 搜索方法和属性的顺序。当你访问某个对象时d.process(),Python 会查找:”

  1. 首先是D
  2. 然后B
  3. 然后C
  4. 然后A
  5. 最后在object(所有类的根)

关键在于:super()它指的不是“我的父级”,而是“MRO中的下一级”

“MRO(维护、维修和大修)也会影响其他操作,”玛格丽特补充道。“例如,isinstance()还要issubclass()进行MRO巡检:”

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

obj = D()

# isinstance() checks if the object's class is in the MRO
print(isinstance(obj, D))  # True - D is in D's MRO
print(isinstance(obj, B))  # True - B is in D's MRO
print(isinstance(obj, C))  # True - C is in D's MRO
print(isinstance(obj, A))  # True - A is in D's MRO
print(isinstance(obj, object))  # True - object is always in MRO

# issubclass() checks if one class appears after another in the MRO
print(issubclass(D, B))  # True
print(issubclass(D, C))  # True
print(issubclass(D, A))  # True

# The MRO makes these checks simple and efficient
print(D.__mro__)
# All the classes where isinstance returns True!

蒂莫西仔细研究了MRO。“所以当B.process()调用时super().process(),它不会查看B的父类A。它会查看MRO中B之后的下一个类,也就是…… C!”

“没错!让我们一步一步地分析你最初的例子:”

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

class B(A):
    def process(self):
        print("B.process()")
        super().process()  # Next in MRO after B

class C(A):
    def process(self):
        print("C.process()")
        super().process()  # Next in MRO after C

class D(B, C):
    def process(self):
        print("D.process()")
        super().process()  # Next in MRO after D

# The MRO is: D -> B -> C -> A -> object
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

d = D()
d.process()

玛格丽特追踪了行刑过程:

1. d.process() starts in D
   - Prints "D.process()"
   - Calls super().process() → next in MRO is B

2. B.process() executes
   - Prints "B.process()"
   - Calls super().process() → next in MRO is C (not A!)

3. C.process() executes
   - Prints "C.process()"
   - Calls super().process() → next in MRO is A

4. A.process() executes
   - Prints "A.process()"
   - No super() call, done

玛格丽特强调说:“关键在于,它super()既不B知道也不关心它B继承自哪个实例。它只知道正在操作的实例A的 MRO——在本例中,就是一个实例。”D

蒂莫西瞪大了眼睛。“所以同一个方法B.process()会根据实例的类不同而调用不同的后续方法?”

“没错!看:”

# Same classes as before

# Instance of B directly
b = B()
print("MRO of B:", B.__mro__)
# (<class 'B'>, <class 'A'>, <class 'object'>)
b.process()
# B.process()
# A.process()

# Instance of D
d = D()
print("\nMRO of D:", D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
d.process()
# D.process()
# B.process()
# C.process()
# A.process()

“当你调用b.process()一个B实例时,MRO 是B -> A -> object,所以super()B调用中A。但是当你调用d.process()一个D实例时,MRO 是D -> B -> C -> A -> object,所以super()B调用中C!”


Python 如何构建 MRO:C3 线性化

“但是Python是如何确定这个顺序的呢?”蒂莫西问道,“为什么是MROD -> B -> C -> A而不是其他顺序?”

玛格丽特拿出一张图表。“Python 使用一种名为C3 线性化(也称为 C3 超类线性化)的算法。它有三个主要规则:

  1. 儿童优先于父母
    (本地优先顺序)
  2. 从左到右排序
    (来自继承列表)
  3. 单调性
    (保持与父级 MRO 相同的顺序)

单调性规则至关重要:如果 A 类出现在任何父类的 MRO 中,且出现在 B 类之前,那么 A 类必须出现在子类的 MRO 中,且出现在 B 类之前(除非在子类的直接继承列表中明确地覆盖)。

让我来演示一下它是如何为课堂构建MRO的D(B, C)

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

玛格丽特写出了算法:

Step 1: Start with the class itself
  L(D) = D + merge(L(B), L(C), [B, C])

Step 2: Expand the parent linearizations
  L(B) = B, A, object
  L(C) = C, A, object
  L(D) = D + merge([B, A, object], [C, A, object], [B, C])

Step 3: Apply merge algorithm
  - Take the first head that doesn't appear in any other tail
  - "Head" = first element of a list
  - "Tail" = everything after the first element

  merge([B, A, object], [C, A, object], [B, C]):
    First heads: B, C, B
    B is not in any tail → take B

  D, B + merge([A, object], [C, A, object], [C]):
    First heads: A, C, C
    A is in tail of [C, A, object] → skip
    C is not in any tail → take C

  D, B, C + merge([A, object], [A, object]):
    First heads: A, A
    A is not in any tail → take A

  D, B, C, A + merge([object], [object]):
    First head: object
    object is not in any tail → take object

  Final: D, B, C, A, object

“该算法确保了几个重要的特性,”玛格丽特解释说:

# Property 1: A class always comes before its parents
class Parent:
    pass

class Child(Parent):
    pass

print(Child.__mro__)
# (<class 'Child'>, <class 'Parent'>, <class 'object'>)
# Child before Parent ✓

# Property 2: Parents are in the order specified
class A:
    pass

class B:
    pass

class C(A, B):  # A before B
    pass

print(C.__mro__)
# (<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>)
# A appears before B ✓

# Property 3: A class's MRO is consistent with its parents' MROs
class D(B, A):  # B before A
    pass

print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'A'>, <class 'object'>)

“但是,”蒂莫西问道,“如果你创建的继承层次结构违反了这些规则,会发生什么呢?”

玛格丽特笑了。“问得好。Python 拒绝创建这样的类。”

class X:
    pass

class Y(X):
    pass

class Z(X, Y):  # Try to put X before Y, but Y inherits from X
    pass

# TypeError: Cannot create a consistent method resolution
# order (MRO) for bases X, Y

“算法声明中说‘X 必须在 Y 之前’ Z(X, Y),但同时又说‘Y 必须在 X 之前’(因为 YY继承自 X X)。这是不可能的,所以 Python 会抛出错误。”

“这里又出现了一个违反单调性的例子:”

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

# D's MRO: D -> B -> C -> A
# This respects that B.MRO has (B, A) and C.MRO has (C, A)

# But what if we try:
class E(C, B):
    pass

# E's MRO: E -> C -> B -> A
# This also works! Order reversed but still consistent

# However, this breaks:
class F(A, B):  # Try to put A before B, but B inherits from A
    pass

# TypeError: Cannot create a consistent method resolution
# order (MRO) for bases A, B
#
# We're saying "A before B" but B's MRO says "B before A"
# Inconsistent!

“C3 算法可以检测出这些不一致之处,并防止您创建不可能的层级结构。”


钻石问题

“现在让我们来看看为什么MRO如此重要,”玛格丽特一边说着,一边在石板上画了一张图:

A
   / \
  B   C
   \ /
    D

“这就是所谓的菱形问题。类 AD继承自类 BBC类 C,而类 B 又继承自类AC。问题是:当D类 A 调用所有类都实现的方法时,这些方法的调用顺序是什么?”

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__")
        super().__init__()

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

d = D()
# D.__init__
# B.__init__
# C.__init__
# A.__init__

print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

“注意一个关键点,”玛格丽特指出,“尽管两者BC继承自AA.__init__但只调用一次。这是有意为之。最小可行优化(MRO)确保每个类都恰好出现一次,并super()确保我们正确地遵循了MRO。”

“如果我们不用呢super()?”蒂莫西问道。

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

class B(A):
    def __init__(self):
        print("B.__init__")
        A.__init__(self)  # Direct call instead of super()

class C(A):
    def __init__(self):
        print("C.__init__")
        A.__init__(self)  # Direct call instead of super()

class D(B, C):
    def __init__(self):
        print("D.__init__")
        B.__init__(self)
        C.__init__(self)

d = D()
# D.__init__
# B.__init__
# A.__init__
# C.__init__
# A.__init__  ← Called twice!

“糟了!”玛格丽特惊呼道。“A.__init__它被调用了两次。在这个例子中,这似乎没什么大不了的,但想象一下,如果A它是用来打开文件、创建数据库连接或分配资源,那就会造成资源重复,甚至可能导致数据损坏。”

“MRO plussuper()通过确保每个类都以可预测的顺序被调用一次来解决这个问题。”


合作多重继承

玛格丽特解释说: “MRO 的真正威力super()在于设计能够协同工作的类。这需要一种特定的模式。”

她举了一个例子:

class LoggingMixin:
    def __init__(self, **kwargs):
        print(f"LoggingMixin.__init__")
        super().__init__(**kwargs)  # Pass remaining kwargs along

class ValidationMixin:
    def __init__(self, **kwargs):
        print(f"ValidationMixin.__init__")
        super().__init__(**kwargs)

class Base:
    def __init__(self, name):
        print(f"Base.__init__: name={name}")
        self.name = name

class Document(LoggingMixin, ValidationMixin, Base):
    def __init__(self, name, **kwargs):
        print(f"Document.__init__")
        super().__init__(name=name, **kwargs)

doc = Document("report.pdf", extra="value")
# Document.__init__
# LoggingMixin.__init__
# ValidationMixin.__init__
# Base.__init__: name=report.pdf

print(Document.__mro__)
# (<class 'Document'>, <class 'LoggingMixin'>, 
#  <class 'ValidationMixin'>, <class 'Base'>, <class 'object'>)

“请注意这里几个关键的模式,”玛格丽特指出:

模式 1:接受并传递 **kwargs

“除了最终的基类之外,每个类都接受**kwargs并传递这些参数super()。这使得参数能够流经整个 MRO 链。”

模式二:仅提取所需内容

class TimestampMixin:
    def __init__(self, timestamp=None, **kwargs):
        self.timestamp = timestamp or datetime.now()
        super().__init__(**kwargs)  # Pass the rest along

class AuthorMixin:
    def __init__(self, author=None, **kwargs):
        self.author = author or "Unknown"
        super().__init__(**kwargs)

class Base:
    def __init__(self, name):
        self.name = name

class Article(TimestampMixin, AuthorMixin, Base):
    def __init__(self, name, **kwargs):
        super().__init__(name=name, **kwargs)

article = Article(
    name="Python Deep Dive",
    author="Margaret",
    timestamp=datetime(2024, 1, 1)
)
print(f"{article.name} by {article.author} at {article.timestamp}")

每个 mixin 都会从其他 mixin 中提取参数**kwargs,并将剩余的参数传递给下一个 mixin。这个过程会一直持续,直到所有参数都被消耗掉为止。

模式 3:始终调用 super()

“链中的每个类都必须调用super().__init__(),即使它认为自己没有父类。记住,遵循实例super()的最小可复现关系(MRO),而不是你在代码中看到的类层次结构。”

class Mixin:
    def __init__(self, **kwargs):
        print("Mixin.__init__")
        # Even though Mixin doesn't explicitly inherit from anything,
        # we still call super() to continue the MRO chain
        super().__init__(**kwargs)

class Base:
    def __init__(self, value):
        print(f"Base.__init__: value={value}")
        self.value = value

class Combined(Mixin, Base):
    def __init__(self, value, **kwargs):
        super().__init__(value=value, **kwargs)

obj = Combined(value=42)
# Mixin.__init__
# Base.__init__: value=42

蒂莫西皱起了眉头。“但是如果我把使用某种技术的课程super()和不使用这种技术的课程混在一起呢?”

“一片混乱,”玛格丽特冷冷地说。

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

class B(A):
    def __init__(self):
        print("B.__init__")
        A.__init__(self)  # Direct call - breaks the chain!

class C(A):
    def __init__(self):
        print("C.__init__")
        super().__init__()  # Uses super()

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

d = D()
# D.__init__
# B.__init__
# A.__init__  ← Chain stops here! C is never called

“因为直接B调用A而不是使用super(),导致 MRO 链被破坏。C.__init__即使它位于 MRO 中,也从未被调用。规则是:要么层次结构中的所有类都使用super(),要么都不使用。 ”


super() 不带参数(Python 3 的神奇之处)

“你可能已经注意到了,”玛格丽特说,“我们一直super()没有使用任何参数。在 Python 2 中,你必须这样写super(CurrentClass, self)。Python 3 如何知道要使用哪个类和实例呢?”

蒂莫西想了想。“某种……魔法?”

“闭包,”玛格丽特笑着说。“这是对 Python 闭包机制的巧妙运用。当一个类被创建时,Python 会将一个名为 `close` 的特殊闭包变量绑定__class__到任何使用该闭包的方法上super()。”

class Example:
    def method(self):
        print(super())
        # Python transforms this to:
        # print(super(__class__, self))
        # where __class__ is bound at class creation time

# Let's peek at the closure
import inspect

print(Example.method.__code__.co_freevars)
# ('__class__',)

# The method has a closure that captures the class
print(Example.method.__closure__)
# (<cell at 0x...: type object at 0x...>,)

当你super()不带任何参数调用 Python 时:

  1. __class__
    在函数闭包中查找(在类创建时绑定)
  2. 从局部作用域获取self(或对于类方法)。cls
  3. 有效地呼叫super(__class__, self)

引用__class__是在类创建时绑定的,而不是在调用时绑定的,这就是为什么即使类被重命名也能正常工作的原因:

class Original:
    def method(self):
        return super()

# Rename the class
Renamed = Original

obj = Renamed()
print(obj.method())
# <super: <class 'Original'>, <Renamed object>>
# Still knows the original class name!

# Even if we delete the original name binding
Temp = Original
del Original
obj = Temp()
obj.method()  # Still works! __class__ was bound at class creation

但有些情况下,你仍然需要明确的论证:”

class A:
    def method(self):
        return "A.method"

class B(A):
    def method(self):
        return "B.method"

class C(B):
    def method(self):
        return "C.method"

    def skip_parent(self):
        """Skip B and go directly to A in the MRO"""
        # Normal super() would call B.method
        result_b = super().method()  # Calls B.method

        # But we can explicitly skip B by using super(B, self)
        # This starts the search AFTER B in the MRO
        result_a = super(B, self).method()  # Calls A.method

        return f"Normal: {result_b}, Skipped: {result_a}"

obj = C()
print(obj.skip_parent())
# Normal: B.method, Skipped: A.method

# Another case: calling super() outside a method
class D(A):
    # This doesn't work - no __class__ closure
    # external_super = super()  # RuntimeError: super(): no arguments

    def method(self):
        # But this works - inside a method with __class__ closure
        return super()

# Also useful: super() in a function that's assigned to a class later
def external_method(self):
    # No __class__ closure here
    # return super().method()  # RuntimeError

    # Must use explicit form
    return super(D, self).method()

D.external = external_method

现实世界模式:混合

“让我们看看真正的框架是如何使用多重继承和 MRO 的,”玛格丽特说着,打开了一个 Django 模型参考文档。

from datetime import datetime

# Mixin classes provide discrete functionality
class TimestampMixin:
    """Adds created_at and updated_at fields"""
    def __init__(self, **kwargs):
        self.created_at = datetime.now()
        self.updated_at = datetime.now()
        super().__init__(**kwargs)

    def touch(self):
        """Update the updated_at timestamp"""
        self.updated_at = datetime.now()

class SoftDeleteMixin:
    """Adds soft delete functionality"""
    def __init__(self, **kwargs):
        self.is_deleted = False
        self.deleted_at = None
        super().__init__(**kwargs)

    def delete(self):
        """Soft delete - mark as deleted instead of removing"""
        self.is_deleted = True
        self.deleted_at = datetime.now()

    def restore(self):
        """Restore a soft-deleted record"""
        self.is_deleted = False
        self.deleted_at = None

class AuditMixin:
    """Adds audit trail functionality"""
    def __init__(self, **kwargs):
        self.created_by = kwargs.pop('created_by', None)
        self.updated_by = kwargs.pop('updated_by', None)
        super().__init__(**kwargs)

    def update_audit(self, user):
        """Update audit information"""
        self.updated_by = user
        if hasattr(self, 'touch'):
            self.touch()

# Base model class
class Model:
    """Base class for all models"""
    def __init__(self, id=None):
        self.id = id

    def save(self):
        print(f"Saving {self.__class__.__name__} with id={self.id}")

# Combine mixins to create feature-rich models
class BlogPost(AuditMixin, TimestampMixin, SoftDeleteMixin, Model):
    """A blog post with full audit trail, timestamps, and soft delete"""
    def __init__(self, id, title, content, **kwargs):
        self.title = title
        self.content = content
        super().__init__(id=id, **kwargs)

# Create a blog post
post = BlogPost(
    id=1,
    title="Understanding super()",
    content="It's all about the MRO!",
    created_by="Margaret"
)

print(f"Post: {post.title}")
print(f"Created: {post.created_at}")
print(f"By: {post.created_by}")
print(f"Deleted: {post.is_deleted}")

# Use mixin functionality
post.delete()
print(f"After delete - Deleted: {post.is_deleted}")

post.restore()
print(f"After restore - Deleted: {post.is_deleted}")

# Check the MRO
print("\nMRO:")
for cls in BlogPost.__mro__:
    print(f"  {cls.__name__}")
# BlogPost
# AuditMixin
# TimestampMixin
# SoftDeleteMixin
# Model
# object

“这种模式在 Django 中随处可见,”玛格丽特解释说。“Mixin 让你无需深层的继承层次结构就能组合功能。每个 mixin 都专注于一个方面,你可以根据需要将它们组合起来。”


常见陷阱和调试

“让我来向你们展示最常见的错误,”玛格丽特说着,打开了题为“当MRO出错时”的章节。

陷阱一:方法签名不一致

class A:
    def process(self, data):
        print(f"A: {data}")

class B(A):
    def process(self, data, extra):  # Different signature!
        print(f"B: {data}, {extra}")
        super().process(data)

class C(A):
    def process(self, data):
        print(f"C: {data}")
        super().process(data)

class D(B, C):
    def process(self, data, extra):
        super().process(data, extra)

# d = D()
# d.process("test", "extra")  # TypeError!
# C.process() doesn't accept 'extra' parameter

解决方案:**kwargs用于处理不同的签名:

class A:
    def process(self, data, **kwargs):
        print(f"A: {data}")

class B(A):
    def process(self, data, extra=None, **kwargs):
        print(f"B: {data}, {extra}")
        super().process(data, **kwargs)

class C(A):
    def process(self, data, **kwargs):
        print(f"C: {data}")
        super().process(data, **kwargs)

class D(B, C):
    def process(self, data, extra=None, **kwargs):
        super().process(data, extra=extra, **kwargs)

d = D()
d.process("test", extra="value")
# B: test, value
# C: test
# A: test

陷阱 2:在某些方法中调用 super() 方法

class Mixin1:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)  # Good - calls super()
        self.mixin1_setup = True

class Mixin2:
    def __init__(self, **kwargs):
        # Bad - no super() call!
        self.mixin2_setup = True

class Base:
    def __init__(self):
        self.base_setup = True

class Combined(Mixin1, Mixin2, Base):
    def __init__(self):
        super().__init__()

obj = Combined()
print(hasattr(obj, 'mixin1_setup'))  # True
print(hasattr(obj, 'mixin2_setup'))  # True
print(hasattr(obj, 'base_setup'))    # False - Base.__init__ never called!

陷阱 3:假设父类关系

class Payment:
    def process(self):
        print("Processing payment...")
        return True

class CreditCardPayment(Payment):
    def process(self):
        print("Validating credit card...")
        # Assumption: super() will call Payment.process()
        return super().process()

class SecurePayment:
    def process(self):
        print("Applying security checks...")
        return True  # Oops, doesn't call super()!

class SecureCreditCard(CreditCardPayment, SecurePayment):
    pass

# What's the MRO?
print(SecureCreditCard.__mro__)
# SecureCreditCard -> CreditCardPayment -> SecurePayment -> Payment -> object

payment = SecureCreditCard()
result = payment.process()
# Validating credit card...
# Applying security checks...
# (Payment.process() is never called!)

“问题在于它SecurePayment.process()不打电话super()。链条到此就断了。”

调试工具:MRO可视化

def print_mro(cls, indent=0):
    """Print the MRO as a tree"""
    print("  " * indent + cls.__name__)

def trace_super_calls(cls):
    """Show the complete super() chain for a class"""
    print(f"super() chain for {cls.__name__}:")
    for i, klass in enumerate(cls.__mro__):
        print(f"  {i}. {klass.__name__}")
        if hasattr(klass, '__init__'):
            # Check if __init__ calls super()
            import inspect
            source = inspect.getsource(klass.__init__)
            has_super = 'super()' in source
            print(f"     __init__ calls super(): {has_super}")

# Example usage
class A:
    def __init__(self):
        super().__init__()

class B(A):
    def __init__(self):
        super().__init__()

class C(A):
    def __init__(self):
        super().__init__()

class D(B, C):
    def __init__(self):
        super().__init__()

trace_super_calls(D)

何时不应使用多重继承

“多重继承很强大,”玛格丽特告诫说,“但它并不总是正确的工具。”

在以下情况下使用多重继承:

  • 你需要组合正交行为(mixin)。
  • 这些混合元素没有重叠的属性。
  • 所有班级都与……合作super()
  • MRO 符合直觉。

避免多重继承的情况:

  • 类中存在相互冲突的方法实现
  • 你继承自多个具体类(而非 mixin)。
  • 继承层次结构不明确
  • 构图会更简单
# Instead of this:
class FileLogger:
    def log(self, msg):
        with open('log.txt', 'a') as f:
            f.write(msg)

class ConsoleLogger:
    def log(self, msg):
        print(msg)

class DualLogger(FileLogger, ConsoleLogger):  # Conflict!
    pass

# Consider this:
class Logger:
    def __init__(self, *loggers):
        self.loggers = loggers

    def log(self, msg):
        for logger in self.loggers:
            logger.log(msg)

file_logger = FileLogger()
console_logger = ConsoleLogger()
dual_logger = Logger(file_logger, console_logger)

super()“即使有MRO ,组合通常也比继承更好。”


高级模式:带有 MRO 的抽象基类

“再举一个更高级的例子,”玛格丽特说着,拿出了最后一个例子。“抽象基类结合混入(mixin)可以创建强大且类型安全的架构:”

from abc import ABC, abstractmethod

class StorageBackend(ABC):
    """Abstract base class defining storage interface"""
    @abstractmethod
    def save(self, key, value):
        pass

    @abstractmethod
    def load(self, key):
        pass

# Cannot instantiate abstract class
try:
    storage = StorageBackend()
except TypeError as e:
    print(f"Error: {e}")
    # Error: Can't instantiate abstract class StorageBackend with abstract methods load, save

class CompressionMixin:
    """Mixin that adds compression to storage"""
    def save(self, key, value, **kwargs):
        compressed = self._compress(value)
        return super().save(key, compressed, **kwargs)

    def load(self, key, **kwargs):
        compressed = super().load(key, **kwargs)
        return self._decompress(compressed)

    def _compress(self, data):
        return f"compressed({data})"

    def _decompress(self, data):
        return data.replace("compressed(", "").rstrip(")")

class EncryptionMixin:
    """Mixin that adds encryption to storage"""
    def save(self, key, value, **kwargs):
        encrypted = self._encrypt(value)
        return super().save(key, encrypted, **kwargs)

    def load(self, key, **kwargs):
        encrypted = super().load(key, **kwargs)
        return self._decrypt(encrypted)

    def _encrypt(self, data):
        return f"encrypted({data})"

    def _decrypt(self, data):
        return data.replace("encrypted(", "").rstrip(")")

class DictStorage(StorageBackend):
    """Concrete storage implementation using dict"""
    def __init__(self):
        self.storage = {}

    def save(self, key, value):
        print(f"DictStorage.save: {key} = {value}")
        self.storage[key] = value

    def load(self, key):
        value = self.storage[key]
        print(f"DictStorage.load: {key} = {value}")
        return value

# Now DictStorage can be instantiated - it implements all abstract methods
storage = DictStorage()  # Works!

# Compose features with mixins
class SecureStorage(EncryptionMixin, CompressionMixin, DictStorage):
    """Storage with both encryption and compression"""
    pass

# Check the MRO
print("MRO:", [cls.__name__ for cls in SecureStorage.__mro__])
# ['SecureStorage', 'EncryptionMixin', 'CompressionMixin', 'DictStorage', 
#  'StorageBackend', 'ABC', 'object']

# Use it
storage = SecureStorage()
storage.save("key1", "secret data")
# DictStorage.save: key1 = encrypted(compressed(secret data))

loaded = storage.load("key1")
# DictStorage.load: key1 = encrypted(compressed(secret data))
print(f"Loaded: {loaded}")
# Loaded: secret data

“请注意数据在MRO中的流动方式:

保存:

  1. EncryptionMixin.save()
    加密 → 调用super()
  2. CompressionMixin.save()
    压缩 → 调用super()
  3. DictStorage.save()
    存储加密压缩数据

加载中:

  1. DictStorage.load()
    检索加密压缩数据
  2. CompressionMixin.load()
    解压缩 → 返回给调用者
  3. EncryptionMixin.load()
    解密 → 返回调用者

每个 mixin 都会在两个方向上添加自己的行为。这就是协作式多重继承的威力!


结论:MRO 精通

蒂莫西合上笔记本,之前的困惑被理解所取代。“所以,super()它指的不是‘我的父类’,而是‘MRO中的下一个类’。而MRO是通过C3线性化计算出来的,以确保顺序的一致性和可预测性。”

“正是如此,”玛格丽特点点头。“需要记住的关键原则:

  1. 每个类别都有一个由C3线性化确定的MRO。
  2. super()遵循
    实例的最小可复现规则 (MRO),而不是类层次结构。
  3. (在合作层级结构中)始终要调用super()
    以延续链式调用
  4. 用于**kwargs
    通过链式传递参数
  5. Mixins 应该是正交的
    ,并且专注于单一问题。
  6. cls.__mro__
    调试时请检查 MRO 。
  7. isinstance()还要issubclass()走一走MRO

MRO 使 Python 的多重继承能够可靠地工作。没有它,菱形继承将会一片混乱。有了它,你可以用简单的 mixin 构建复杂的行为。

“最后一点,”玛格丽特补充道,“super()它还会以有趣的方式与描述符和属性进行交互。当你super()在属性的 getter 中调用它时,需要注意递归问题。但这属于高级主题,它建立在描述符协议和 MRO 的基础之上。”

蒂莫西用全新的视角审视了他最初那个令人困惑的例子:

class D(B, C):
    def process(self):
        super().process()  # Not "parent" - it's "next in D's MRO"

# When called on a D instance, the MRO is:
# D -> B -> C -> A -> object
# So super() in each class calls the NEXT one in THIS order

“现在一切都说得通了。这种行为并不神秘——它遵循着清晰、可预测的算法。”

“没错,”玛格丽特笑着说。“现在你明白了Python最复杂的功能之一。说到复杂的功能,”她眼中闪过一丝光芒,补充道,“你有没有想过类本身是如何创建的?class语法又是如何运作的?”

蒂莫西瞪大了眼睛。“你是说……元类吗?”

“的确如此,蒂莫西。不过,”玛格丽特说着,站起身,把那本书放回书架,“那是另一个故事了。以后有机会再深入探讨Python的对象模型。”

“经历了多次继承和MRO(抵押贷款审查官)之后,”蒂莫西笑着说,“我想我已经准备好应对任何事情了。”

玛格丽特笑了。“我们走着瞧,蒂莫西。我们走着瞧……”


要点总结

  1. super()调用 MRO 中的下一个类
    ,而不是父类。
  2. MRO 是使用 C3 线性化方法,
    并采用一致、可预测的规则计算得出的。
  3. 在军事审查办公室(MRO)中,每个职业都只出现一次。
  4. 协作式多重继承需要super()
    在每个方法中调用。
  5. 用于**kwargs
    通过继承链传递参数
  6. Mixins 能够组合
    正交行为
  7. Python 3 会super()自动确定类和实例
  8. 使用cls.__mro__以下方法调试 MRO 问题:cls.mro()
  9. 避免
    在组合关系清晰的情况下进行多重继承。
  10. 合作层次结构中的所有类都
    必须一致地使用super()

《Python的秘密生活》下一篇:“元类:创建类的类”


讨论问题

  1. 多重继承何时适用,而组合继承何时适用?
  2. 流行的框架(Django、Flask)是如何使用 mixin 的?
  3. 深度MRO链对性能有何影响?
  4. 它如何super()处理属性和描述符?
  5. 能否创建一份违反规则的维护、维修和评估报告(MRO)?会发生什么?

关注「索引目录」公众号,获取更多干货。


【声明】内容源于网络
0
0
索引目录
索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
内容 444
粉丝 0
索引目录 索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
总阅读544
粉丝0
内容444