为什么定义新类时有时会调用该类继承自的对象的__init__()函数?

4
我想了解在Python中声明从父类继承的新类时实际发生了什么。
下面是一个非常简单的代码片段:
# inheritance.py
class Foo():
    def __init__(self, *args, **kwargs):
        print("Inside foo.__init__")
        print(args)
        print(kwargs)


class Bar(Foo):
    pass

print("complete")

如果我运行这个程序,没有错误,并且输出结果跟我预期的一样。

❯ python inheritance.py                                                                                                                                                                               
complete

这是一段有明显错误的脚本,我继承了Foo()的一个实例而不是Foo类。
# inheritance.py
class Foo():
    def __init__(self, *args, **kwargs):
        print("Inside foo.__init__")
        print(f"{args=}")
        print(f"{kwargs=}\n")

foo = Foo()
class Bar(foo):            <---- This is wrong
    pass

print("complete")

这段代码可以正常运行,但我不明白为什么会调用Foo.__init__() 两次

以下是输出内容:
❯ python inheritance.py
Inside foo.__init__        <--- this one I expected
args=()
kwargs={}

Inside foo.__init__       <--- What is going on here...?
args=('Bar', (<__main__.Foo object at 0x10f190b10>,), {'__module__': '__main__', '__qualname__': 'Bar'})
kwargs={}

complete

第8行,我使用没有参数的方式实例化了Foo(),这正是我所期望的。但是在第9行,调用了Foo.__init__并传递了通常会传递给type()以生成新类的参数。
我大致上能理解发生了什么:class Bar(...) 是生成一个新类的代码,因此在某个时候需要调用type("Bar", ...),但是:
  1. 这到底是如何发生的?
  2. 为什么从Foo()的实例进行继承会导致调用Foo.__init__("Bar", <tuple>, <dict>)
  3. 为什么不直接调用type("Bar", <tuple>, <dict>)

凭直觉来看,这似乎是为了允许 元类 而发生的事情。而不是使用 Foo.__init__("Bar", <tuple>, <dict>),调用 type(Foo)("Bar", <tuple>, <dict>)。如果没有 元类,则 type(Foo)type - Axe319
1个回答

6

Python 使用 foo 来确定 Bar 所使用的元类 (metaclass)。由于没有明确指定元类,因此必须确定“最终派生元类”。一个基类的元类是其类型;通常type。但在这种情况下,唯一基类 "class" 的类型是 Foo,所以它成为了最终派生元类。因此,Bar 的元类就是 Foo

class Bar(foo):
    pass

被视作

class Bar(metaclass=Foo):
    pass

这意味着通过Foo调用来创建Bar:
Bar = Foo('Bar', (foo,), {})

请注意,现在BarFoo的实例,而不是类型。是的,class语句并不一定会创建一个类。

1
我本来想这样做,但似乎这只是证明Foo的实例不可调用,这可能不会让任何人感到惊讶。仅提到BarFoo的一个实例就足够了。 - chepner
1
类型错误是'Foo' object is not callable(即Foo的一个实例),而不是对象Foo不可调用。 (错误消息可能可以更清晰;它不是Python 3.11中引入的改进错误消息之一。) - chepner
2
@DiMithras 因为 type 本身会检查你所声称的基类是否实际上是类(或至少具有与 type 兼容的元类)。使用 class Bar(foo, metaclass=type): pass 也会得到相同的错误。 - chepner
1
我稍微修改了答案,以便提到创建Bar(而不是类型Bar),因为答案的主要观点是class语句不会创建类型。class语句不会创建类型;它们提供了一种声明性语法,用于一系列方法调用,这些调用通常但不一定会导致类型的创建。 - chepner
1
从 https://docs.python.org/3/reference/datamodel.html#customizing-class-creation 开始。type.__call__ 的实现可以在 CPython 的 C 源代码中找到,但对于大多数情况而言,假设它是这样的 def __call__(self, cls, *args, **kwargs): obj = cls.__new__(cls, *args, **kwargs); if isinstance(obj, cls): obj.__init__(*args, **kwargs); return obj 即可。 - chepner
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接