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

How to create an abstract class attribute (potentially read-only)

我花了很多时间研究这个问题,但 none 的答案似乎符合我的意愿。

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

class AbstractFoo():
    forceThis = 0

所以当我这样做时

class RealFoo(AbstractFoo):
    pass

它抛出一个错误告诉我在我实现 forceThis.

之前它无法创建 class

我该怎么做?

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

对于 class 方法,我发现我可以做到

from abc import ABCMeta, abstractmethod

class AbstractFoo(metaclass=ABCMeta):
    @classmethod
    @abstractmethod
    def forceThis():
        """This must be implemented"""

所以

class RealFoo(AbstractFoo):
    pass

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

(虽然它不会强制 forceThis 成为 class 方法。)

如何让 class 属性弹出类似的错误消息?

您可以通过定义自己的元类来做到这一点。类似于:

 class ForceMeta(type):
     required = ['foo', 'bar']

     def __new__(mcls, name, bases, namespace):
         cls = super().__new__(mcls, name, bases, namespace)
         for prop in mcls.required:
            if not hasattr(cls, prop):
               raise NotImplementedError('must define {}'.format(prop))
         return cls

现在您可以将其用作您自己的元类类:

class RequiredClass(metaclass=ForceMeta):
     foo = 1

这将引发错误 'must define bar'。

虽然您可以使用 metaclass 做一些非常相似的事情,如@Daniel Roseman 的 中所示,但也可以使用 class 装饰器来完成。它们有几个优点是当定义 class 时会发生错误,而不是在创建一个实例时发生错误,并且在 Python 2 和 3 中指定它们的语法是相同的. 有些人也觉得它们更简单易懂

def check_reqs(cls):
    requirements = 'must_have',

    missing = [req for req in requirements if not hasattr(cls, req)]
    if missing:
        raise NotImplementedError(
            'class {} did not define required attribute{} named {}'.format(
                cls.__name__, 's' if len(missing) > 1 else '',
                ', '.join('"{}"'.format(name) for name in missing)))
    return cls

@check_reqs
class Foo(object):  # OK
    must_have = 42

@check_reqs
class Bar(object):  # raises a NotImplementedError
    pass

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

我创建了一个名为 ABCAMeta 的元class(最后一个 'A' 代表 'Attributes')。

class有两种工作方式。

  1. A class 仅使用 ABCAMeta 作为元class 必须有一个名为 required_attributes 的 属性 应包含名称列表您想要在 class

  2. 的未来子 class 上需要的所有属性
  3. 父级元class为ABCAMeta的class必须具有其父级class(es)指定的所有必需属性。

例如:

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

class RealFoo(AbstractFoo):
    pass

会抛出错误:

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

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