如何创建抽象类属性(可能是只读的)

Pro*_*o Q 5 python abstract-class exception class-properties

我花了很多时间研究这个,但没有一个答案看起来像我想要的那样.

我有一个带有类属性的抽象类,我希望每个子类都被强制实现

class AbstractFoo():
    forceThis = 0
Run Code Online (Sandbox Code Playgroud)

所以,当我这样做

class RealFoo(AbstractFoo):
    pass
Run Code Online (Sandbox Code Playgroud)

它会抛出一个错误,告诉我在实现之前它无法创建类forceThis.

我怎样才能做到这一点?

(我不希望该属性是只读的,但如果这是唯一的解决方案,我会接受它.)

对于一个类方法,我发现我可以做到

from abc import ABCMeta, abstractmethod

class AbstractFoo(metaclass=ABCMeta):
    @classmethod
    @abstractmethod
    def forceThis():
        """This must be implemented"""
Run Code Online (Sandbox Code Playgroud)

以便

class RealFoo(AbstractFoo):
    pass
Run Code Online (Sandbox Code Playgroud)

至少抛出错误 TypeError: Can't instantiate abstract class EZ with abstract methods forceThis

(虽然它不强制forceThis成为一种类方法.)

如何为类属性弹出类似的错误?

Pro*_*o Q 2

我根据之前发布的解决方案提出了一个解决方案。(谢谢@Daniel Roseman 和@martineau)

我创建了一个名为 ABCAMeta 的元类(最后一个“A”代表“属性”)。

班级有两种工作方式。

  1. 仅使用 ABCAMeta 作为元类的类必须有一个名为 的属性,required_attributes该属性应包含您想要该类的未来子类所需的所有属性的名称列表

  2. 父类元类为 ABCAMeta 的类必须具有其父类指定的所有必需属性。

例如:

class AbstractFoo(metaclass=ABCAMeta):
    required_attributes = ['force_this']

class RealFoo(AbstractFoo):
    pass
Run Code Online (Sandbox Code Playgroud)

会抛出错误:

NameError: Class 'RealFoo' has not implemented the following attributes: 'force_this'

正是我想要的。

from abc import ABCMeta

class NoRequirements(RuntimeError):
        def __init__(self, message):
            RuntimeError.__init__(self, message)

class ABCAMeta(ABCMeta):
    def __init__(mcls, name, bases, namespace):
        ABCMeta.__init__(mcls, name, bases, namespace)

    def __new__(mcls, name, bases, namespace):
        def get_requirements(c):
            """c is a class that should have a 'required_attributes' attribute
            this function will get that list of required attributes or
            raise a NoRequirements error if it doesn't find one.
            """

            if hasattr(c, 'required_attributes'):
                return c.required_attributes
            else:
                raise NoRequirements(f"Class '{c.__name__}' has no 'required_attributes' property")

        cls = super().__new__(mcls, name, bases, namespace)
        # true if no parents of the class being created have ABCAMeta as their metaclass
        basic_metaclass = True
        # list of attributes the class being created must implement
        # should stay empty if basic_metaclass stays True
        reqs = []
        for parent in bases:
            parent_meta = type(parent)
            if parent_meta==ABCAMeta:
                # the class being created has a parent whose metaclass is ABCAMeta
                # the class being created must contain the requirements of the parent class
                basic_metaclass=False
                try:
                    reqs.extend(get_requirements(parent))
                except NoRequirements:
                    raise
        # will force subclasses of the created class to define
        # the attributes listed in the required_attributes attribute of the created class
        if basic_metaclass:
            get_requirements(cls) # just want it to raise an error if it doesn't have the attributes
        else:
            missingreqs = []
            for req in reqs:
                if not hasattr(cls, req):
                    missingreqs.append(req)
            if len(missingreqs)!=0:
                raise NameError(f"Class '{cls.__name__}' has not implemented the following attributes: {str(missingreqs)[1:-1]}")
        return cls
Run Code Online (Sandbox Code Playgroud)

有任何改进建议欢迎在评论中提出。