【问题标题】:Final classes in Python 3.x- something Guido isn't telling me?Python 3.x 中的最终课程——Guido 没有告诉我什么?
【发布时间】:2021-07-22 19:56:30
【问题描述】:

这个问题建立在许多假设之上。如果一个假设是错误的,那么整个事情就会失败。我对 Python 还比较陌生,刚刚进入好奇/探索阶段。

据我了解,Python 不支持创建不能被子类化的类(final 类)。但是,在我看来,Python 中的 bool 类不能被子类化。当考虑 bool 类的意图时,这是有道理的(因为 bool 只应该有两个值:true 和 false),我对此很满意。我想知道的是如何这个类被标记为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

相关问题: Why I can't extend bool in Python?

【问题讨论】:

    标签: python inheritance


    【解决方案1】:

    您只能通过 C API 执行此操作。清除类型对象的tp_flagsPy_TPFLAGS_BASETYPE 位。

    例如,bool 不能被子类化 (cpython-code on github):

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

    但是int可以(cpython-code on github):

    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 */
        ...
    };
    

    因为Py_TPFLAGS_BASETYPE-bit 设置在tp_flags

    【解决方案2】:

    Final@final 类型现在在 typing_extensions 中可用。

    我写了一篇文章,几乎涵盖了这种新类型的所有部分:https://sobolevn.me/2018/07/real-python-contants

    类的一些例子:

    from typing_extensions import final
    
    @final
    class HRBusinessUnit(AbstractBusinessUnit):
        def grant_permissions(self) -> None:
            self.api.do_some_hr_stuff()
    
    
    class SubHRBusinessUnit(HRBusinessUnit):  # mypy will raise an error
        def grant_permissions(self) -> None:
            self.api.do_some_it_stuff()
    

    还有常量:

    from typing_extensions import Final
    
    DAYS_IN_A_WEEK: Final = 7
    DAYS_IN_A_WEEK = 8  # mypy will raise an error
    

    我们还有一个小库来编写final 类,这些类也在运行时进行检查! https://github.com/wemake-services/final-class

    from final_class import final
    
    
    @final
    class Example(object):  # You won't be able to subclass it!
        ...
    
    
    class Error(Example):  # Raises `TypeError`
        ...
    

    特点:

    • 没有元类冲突
    • 没有运行时开销
    • 无依赖关系
    • 包括类型提示
    • 设计得尽可能简单

    【讨论】:

    • 安装一个只提供一个装饰器来设置 __init_subclass__ 的整个库是过度设计的。顺便说一句,一个简单的Example.__init_subclass__ = lambda: None 使Example 再次成为子类。
    • @RichardNeumann 在 Python 中没有干净的方法来创建一个在派生类中不能以某种方式撤消的最终类。因此,您的第二个陈述是事实,但无关紧要。 __init_subclass__ 应该明确地调用 super。
    • typing.final 不是一回事,它是关于 steriods 的文档,一种静态分析工具,而不是对子类化的运行时强制限制。作为一个不同的用例,这并不是真正的“功能”。
    • > 我们还有一个小库来编写最终类,这些类也在运行时进行检查! github.com/wemake-services/final-class
    【解决方案3】:

    在 Python 3.6 中,您应该在不使用元类的情况下阻止子类化:

    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 之类的程序完成,这些程序是可选的。

    【讨论】:

      【解决方案4】:

      您可以很容易地从 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
      

      【讨论】:

        猜你喜欢
        • 2014-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-16
        • 1970-01-01
        相关资源
        最近更新 更多