防止实例化抽象 class

Prevent instantiation of an abstract class

在 Java 中,我们可以通过将 class 设为抽象 class 来防止其实例化。我认为 python 的行为方式相同。但令我惊讶的是,我发现我可以创建一个抽象的对象 class:

from abc import ABCMeta
class Foo(metaclass=ABCMeta):
    pass
Foo()

为什么 python 允许这种情况,我该如何防止这种情况?

Python 用于“同意的成年人”——如果需要(或模块成员资格),您可以通过项目内的命名约定将 class 标记为抽象。然而,做一个硬性的“无法实例化”的抽象是可行的 class - 但正如问题的评论者所建议的那样,这不会增加项目本身的安全性或良好实践。

所以,要保留ABC抽象classes的剩余机制,可以继承ABCMetaclass,用它来修饰__new__方法,这样就不会了实例化 - 否则,只需执行相同的操作,但改为从 type 继承。

换句话说,下面的代码将 __new__ 方法包装在 classes 上作为元 class 创建。当该方法是 运行 时,它会检查它正在实例化的 class 是否是用 ABC 元本身标记的 class,如果是,它会引发类型错误。

    class ReallyAbstract(ABCMeta):
        def __new__(metacls, name, bases, namespace):
            outter_cls = super().__new__(metacls, name, bases, namespace)
            for bases in outter_cls.__mro__:
                if getattr(getattr(bases, "__new__", None), "_abstract", False):
                    # Base class already marked as abstract. No need to do anything else
                    return outter_cls
            original_new = getattr(outter_cls, "__new__")
            if getattr(original_new, "_abstract", False):
                # If we get a method that has already been wrapped
                # just return it unchanged.
                # TODO: if further classes on the hierarhy redfine __new__
                return outter_cls
    
            def __new__(cls, *args, **kw):
                if cls is outter_cls:
                    raise TypeError
                return original_new(cls, *args, **kw)
            __new__._abstract = True
            outter_cls.__new__ = __new__
            return outter_cls

在控制台上:

    In [7]: class A(metaclass=ReallyAbstract):
        ...:     pass
        ...: 
    
    In [7]: A()
       TypeError                                 Traceback (most recent call last)
    <ipython-input-7-...> in <module>()
    ----> 1 A()
    
    ....

为了完整起见 - 如果 ABCMeta 在 Python 中包含至少一个“抽象方法”,则它们不可实例化。就像其他 O.O。在更多静态语言中强制执行的功能,我们的想法是按照惯例拥有它。但是,是的,我同意,既然他们开始着手创建一个 AbstractClass 机制,它的行为应该不会那么令人惊讶,这意味着默认情况下不应该实例化。