Python 3.x中的最终类 - Guido没告诉我什么?

42 投票
4 回答
4723 浏览
提问于 2025-04-15 22:39

这个问题建立在很多假设之上。如果其中一个假设错了,那整个问题就不成立了。我对Python还比较陌生,刚刚进入好奇和探索的阶段。

我了解到,Python不支持创建不能被继承的类(也就是final类)。不过,我发现Python中的bool类似乎是不能被继承的。考虑到bool类的目的(因为bool只应该有两个值:真和假),这也说得通,我对此很满意。我想知道的是,这个类是怎么被标记为final的。

所以我的问题是:Guido是怎么做到让bool类不能被继承的呢?

>>> class TestClass(bool):
        pass

Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    class TestClass(bool):
TypeError: type 'bool' is not an acceptable base type

相关问题: 为什么我不能在Python中扩展bool?

4 个回答

17

你应该阻止子类化,而不使用像这样的 metaclass:

class SomeBase:

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if cls is not SomeBase:
            raise TypeError("SomeBase does not support polymorphism.  Use composition over inheritance.")


class Derived(SomeBase):
    pass

在 Python 3.8 中,你还应该使用 final 装饰器来引发类型检查错误:

from typing import final


@final
class SomeBase:
    ...

类型检查是由像 MyPy 这样的程序完成的,这些程序是可选的。

19

你只能通过C语言的接口来做到这一点。需要清除类型对象的 Py_TPFLAGS_BASETYPE 这个标志位。

举个例子,bool 类型是不能被继承的(在github上的cpython代码):

PyTypeObject PyBool_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "bool",
    ...
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                         /* tp_flags */
    ...
};

但是 int 类型是可以被继承的(在github上的cpython代码):

PyTypeObject PyLong_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "int",                                      /* tp_name */
    ...
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
        Py_TPFLAGS_LONG_SUBCLASS,               /* tp_flags */
    ...
};

这是因为 tp_flags 中设置了 Py_TPFLAGS_BASETYPE 这个标志位。

56

你可以很简单地在Python 3.x中模拟出相同的效果:

class Final(type):
    def __new__(cls, name, bases, classdict):
        for b in bases:
            if isinstance(b, Final):
                raise TypeError("type '{0}' is not an acceptable base type".format(b.__name__))
        return type.__new__(cls, name, bases, dict(classdict))

class C(metaclass=Final): pass

class D(C): pass

这段代码会产生以下输出:

Traceback (most recent call last):
  File "C:\Temp\final.py", line 10, in <module>
    class D(C): pass
  File "C:\Temp\final.py", line 5, in __new__
    raise TypeError("type '{0}' is not an acceptable base type".format(b.__name__))
TypeError: type 'C' is not an acceptable base type

撰写回答