5

我花了很多时间研究这个,但似乎没有一个答案能如我所愿。

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

class AbstractFoo():
    forceThis = 0

所以当我这样做时

class RealFoo(AbstractFoo):
    pass

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

我怎样才能做到这一点?

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

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

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成为一个类方法。)

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

4

3 回答 3

4

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

 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

这将引发错误“必须定义栏”。

于 2017-05-04T18:44:50.310 回答
2

I came up with a solution based on those posted earlier. (Thank you @Daniel Roseman and @martineau)

I created a metaclass called ABCAMeta (the last 'A' stands for 'Attributes').

The class has two ways of working.

  1. A class which just uses ABCAMeta as a metaclass must have a property called required_attributes which should contain a list of the names of all the attributes you want to require on future subclasses of that class

  2. A class whose parent's metaclass is ABCAMeta must have all the required attributes specified by its parent class(es).

For example:

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

class RealFoo(AbstractFoo):
    pass

will throw an error:

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

Exactly how I wanted.

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

Any suggestions for improvement are welcome in the comments.

于 2017-05-08T23:37:17.340 回答
0

尽管您可以使用元类做一些非常相似的事情,如@Daniel Roseman 的回答中所示,但也可以使用类装饰器来完成。它们的几个优点是在定义类时会发生错误,而不是在创建类的实例时发生,并且指定它们的语法在 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
于 2017-05-06T12:24:26.313 回答