Python相当于.Net的密封类

时间:2013-05-15 11:46:52

标签: python .net class sealed

python是否有类似密封类的东西?我相信它在java中也被称为final class。

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

免责声明

实际上试图理解why sealed classes甚至存在。 Answer here(以及manymanymanymanymanyreally many其他地方)并不能让我满意所以,所以我试图从不同的角度看。请避免对这个问题的理论答案,并专注于标题!或者,如果你坚持,至少请给csharp中一个密封类的一个非常好的和实用的例子,指出如果它是未密封的话会破坏大的时间。 功能

我不是两种语言的专家,但我确实知道两种语言。就在昨天,在使用csharp进行编码时,我了解了密封类的存在。现在我想知道python是否有相同的东西。我相信它的存在是有充分理由的,但我真的没有得到它。 功能

4 个答案:

答案 0 :(得分:11)

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

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方法查找的工作方式非常不同,在方法查找时,使用类似上面例子的元类没有任何优点或缺点。

答案 1 :(得分:1)

Python 包含无法扩展的类,例如boolNoneType

>>> 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”方法是记录不应该如何使用它。

答案 2 :(得分:1)

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

我也听说过.Net密封/ Java final / C ++完全非虚拟类的优点是性能。我从Microsoft的.Net开发人员那里听到了,所以也许是对的。如果您要构建使用频繁,对性能敏感的应用程序或框架,则可能需要在实际的概要分析瓶颈处或附近密封少数几个类。特别是您在自己的代码中使用的类。

对于大多数软件应用程序,密封其他团队在框架/库/ API中使用的类是有点...怪异的。

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

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

[警告:下面的蛇纹意为幽默。 阅读并激活您的幽默感子程序。我确实意识到在某些情况下需要密封/定案。]

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

因此,我的建议:(1)信任编译器的优化器,(2)停止创建所构建的不必要的密封/最终/非虚拟类,以便(a)在某个地方获得每一微秒的性能可能不是您的瓶颈(键盘,互联网...),还是(b)对您团队中的“初级开发人员”造成了某种误导的编译时约束(是的...我已经看到,太)。

哦,然后(3)首先编写测试。 ;-)

好的,是的,也总是存在链接时嘲笑(例如TypeMock)。你懂我继续吧,密封你的课。 Whatevs。

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

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

答案 3 :(得分:0)

__slots__属性的作用类似于密封类,可用于减少内存使用量(Usage of __slots__?),可防止猴子修补类。因为当调用元类__new__时,将__slots__放入类太晚了,所以我们必须在第一个可能的时间点,即在__prepare__期间将其放入名称空间。此外,这会提前抛出TypeError。使用mcs进行isinstance比较,消除了对元类名称本身进行硬编码的必要。缺点是所有未插入插槽的属性都是只读的。因此,如果我们想在初始化期间或以后设置特定的属性,则必须对它们进行专门的插入。这是可行的,例如通过使用以槽为参数的动态元类。

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,因此您不能从中继承。当 dict 位于插槽中时,它也会被破坏,因此我们抛出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无法被继承或扩展。缺点是所有属性都必须显式插入或限制为只读类变量。