10

python有什么类似于密封类的东西吗?我相信它在 java 中也被称为 final 类。

换句话说,在python中,我们可以标记一个类,使其永远不能被继承或扩展吗?python有没有考虑过有这样的功能?为什么?

免责声明

实际上试图理解为什么密封类甚至存在。在这里回答(以及在许多许多许多许多许多很多其他地方)根本不能满足我,所以我试图从不同的角度来看。请避免对这个问题的理论答案,并专注于标题!或者,如果你坚持,至少请给出一个非常好的和实用的 csharp 密封类的例子,指出如果它被解封会破坏什么。

我不是这两种语言的专家,但我对这两种语言都有一点了解。就在昨天在 csharp 上编码时,我知道了密封类的存在。现在我想知道python是否有与此等价的东西。我相信它的存在是有充分理由的,但我真的不明白。

4

4 回答 4

14

您可以使用元类来防止子类化:

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 Foo:
    __metaclass__ = Final

class Bar(Foo):
    pass

给出:

>>> class Bar(Foo):
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __new__
TypeError: type 'Foo' is not an acceptable base type

__metaclass__ = Final条线使Foo班级“密封”。

请注意,您将在 .NET 中使用密封类作为性能度量;因为不会有任何子类化方法可以直接解决。Python 方法查找的工作方式非常不同,在方法查找方面,使用像上面示例这样的元类没有优势或劣势。

于 2013-05-15T11:48:32.783 回答
1

Python确实有无法扩展的类,例如boolor NoneType

>>> class ExtendedBool(bool):
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type 'bool' is not an acceptable base type

但是,不能从 Python 代码创建此类类。(在 CPython C API 中,它们是通过不设置Py_TPFLAGS_BASETYPE标志来创建的。)

Python 3.6 将引入__init_subclass__特殊方法;从中引发错误将阻止创建子类。对于旧版本,可以使用元类。

尽管如此,限制类使用的最“Pythonic”方式是记录不应该如何使用它。

于 2016-09-16T13:44:47.047 回答
1

在我们谈论 Python 之前,让我们先谈谈“密封”:

我也听说过 .Net 密封/Java final/C++ 完全非虚拟类的优势在于性能。我是从 Microsoft 的 .Net 开发人员那里听说的,所以也许是真的。如果您正在构建一个重度使用、高性能敏感的应用程序或框架,您可能希望将少数类密封在或接近真实的、已分析的瓶颈处。特别是您在自己的代码中使用的类。

对于大多数软件应用程序,将其他团队作为框架/库/API 的一部分使用的类密封起来有点……奇怪。

主要是因为无论如何,任何密封类都有一个简单的解决方法。

我教授“基本测试驱动开发”课程,在这三种语言中,我建议这种密封类的使用者将其包装在具有完全相同方法签名的委托代理中,但它们是可覆盖的(虚拟),因此开发人员可以为这些缓慢的、不确定的或引起副作用的外部依赖项创建测试替身。

[警告:下面的蛇皮意为幽默。激活您的幽默感子程序阅读。我确实意识到有些情况下需要密封/最终。]

代理(不是测试代码)有效地解封(重新虚拟化)类,导致 v-table 查找和可能效率较低的代码(除非编译器优化器足以内联委托)。优点是您可以有效地测试自己的代码,每月为生活、呼吸人类节省数周的调试时间(与为您的应用程序节省几百万微秒相比)...... [免责声明:这只是一个 WAG。是的,我知道,您的应用程序很特别。;-]

所以,我的建议:(1) 相信你的编译器的优化器,(2) 停止创建你构建的不必要的密封/最终/非虚拟类,以便 (a) 在一个不太可能的地方维持每一微秒的性能无论如何你的瓶颈(键盘,互联网......),或者(b)对你团队的“初级开发人员”产生某种误导的编译时间约束(是的......我也看到了)。

哦,(3)先写测试。;-)

好的,是的,也总是存在链接时模拟(例如 TypeMock)。你得到了我。来吧,封你的课。什么。

回到 Python:存在 hack 而不是关键字这一事实可能反映了 Python 的纯虚拟特性。这不是“自然的”。

顺便说一句,我来这个问题是因为我有完全相同的问题。在我极具挑战性和现实性的遗留代码实验室的 Python 端口上工作,我想知道 Python 是否有像 seal 或 final 这样可恶的关键字(我在 Java、C# 和 C++ 课程中将它们用作单元测试的挑战)。显然它没有。现在我必须为未经测试的 Python 代码找到同样具有挑战性的东西。嗯……

于 2018-08-09T22:02:13.580 回答
0

与密封类的目的相似并且对减少内存使用有用(使用 __slots__?)是__slots__防止猴子修补类的属性。因为当元类被调用时,再把a放入类已经__new__来不及了,我们必须在第一个可能的时间点,即在. 此外,这会提前一点抛出 TypeError。使用 mcs 进行 isinstance 比较消除了对元类名称本身进行硬编码的必要性。缺点是所有未开槽的属性都是只读的。因此,如果我们想在初始化期间或之后设置特定的属性,就必须专门对它们进行开槽。这是可行的,例如通过使用将槽作为参数的动态元类。__slots____prepare__

def Final(slots=[]):
    if "__dict__" in slots:
        raise ValueError("Having __dict__ in __slots__ breaks the purpose")
    class _Final(type):
        @classmethod
        def __prepare__(mcs, name, bases, **kwargs):   
            for b in bases:
                if isinstance(b, mcs):
                    msg = "type '{0}' is not an acceptable base type"
                    raise TypeError(msg.format(b.__name__))

            namespace = {"__slots__":slots}
            return namespace
    return _Final

class Foo(metaclass=Final(slots=["_z"])):
    y = 1    
    def __init__(self, z=1):       
        self.z = 1

    @property
    def z(self):
        return self._z

    @z.setter
    def z(self, val:int):
        if not isinstance(val, int):
            raise TypeError("Value must be an integer")
        else:
            self._z = val                

    def foo(self):
        print("I am sealed against monkey patching")

覆盖 foo.foo 的尝试将抛出AttributeError: 'Foo' object attribute 'foo' is read-only并尝试添加 foo.x 将抛出AttributeError: 'Foo' object has no attribute 'x'。继承时会破坏的限制能力__slots__,但由于 Foo 具有元类 Final,因此您不能从它继承。当dictslot中时它也会被破坏,所以我们抛出一个 ValueError 以防万一。总而言之,为开槽属性定义 setter 和 getter 可以限制用户覆盖它们的方式。

foo = Foo()
# attributes are accessible
foo.foo()
print(foo.y)
# changing slotted attributes is possible
foo.z = 2

# %%
# overwriting unslotted attributes won't work
foo.foo = lambda:print("Guerilla patching attempt")
# overwriting a accordingly defined property won't work
foo.z = foo.foo
# expanding won't work
foo.x = 1
# %% inheriting won't work
class Bar(Foo):
    pass

在这方面, Foo 不能被继承或扩展。缺点是所有属性都必须显式插入,或者仅限于只读类变量。

于 2019-06-25T19:54:04.507 回答