3

这是我想要的设置:A 应该是具有静态和抽象方法 f() 的抽象基类。B 应该从 A 继承。要求:1.您不应该能够实例化 A 2.您不应该能够实例化 B,除非它实现了静态 f()

这个问题中获得灵感,我尝试了几种方法。有了这些定义:

class abstractstatic(staticmethod):
    __slots__ = ()
    def __init__(self, function):
        super(abstractstatic, self).__init__(function)
        function.__isabstractmethod__ = True
    __isabstractmethod__ = True

class A:
    __metaclass__ = abc.ABCMeta
    @abstractstatic
    def f():
        pass

class B(A):
    def f(self):
        print 'f'

class A2:
    __metaclass__ = abc.ABCMeta
    @staticmethod
    @abc.abstractmethod
    def f():
        pass

class B2(A2):
    def f(self):
        print 'f'

这里 A2 和 B2 是使用通常的 Python 约定定义的, A & B 是使用答案中建议的方式定义的。以下是我尝试的一些操作以及不想要的结果。

A/B 类:

>>> B().f()
f
#This should have thrown, since B doesn't implement a static f()

对于 A2/B2 类:

>>> A2()
<__main__.A2 object at 0x105beea90>
#This should have thrown since A2 should be an uninstantiable abstract class

>>> B2().f()
f
#This should have thrown, since B2 doesn't implement a static f()

由于这些方法都没有给我想要的输出,我该如何实现我想要的?

4

1 回答 1

8

你不能做你想做的事ABCMeta。ABC 强制不进行任何类型检查,仅强制存在具有正确名称的属性。

举个例子:

>>> from abc import ABCMeta, abstractmethod, abstractproperty
>>> class Abstract(object):
...     __metaclass__ = ABCMeta
...     @abstractmethod
...     def foo(self): pass
...     @abstractproperty
...     def bar(self): pass
... 
>>> class Concrete(Abstract):
...     foo = 'bar'
...     bar = 'baz'
... 
>>> Concrete()
<__main__.Concrete object at 0x104b4df90>

Concrete()即使两者都是简单的属性foo,我也能够构建。bar

元类仅跟踪属性为 trueABCMeta的剩余对象数量;__isabstractmethod__当从元类(ABCMeta.__new__被调用)创建一个类时,该cls.__abstractmethods__属性被设置为一个frozenset对象,其所有名称仍然是抽象的。

type.__new__然后对其进行测试并在您尝试创建实例时frozenset抛出 a 。TypeError

您必须在这里生成自己的 __new__方法;子类ABCMeta并在新__new__方法中添加类型检查。该方法应该__abstractmethods__在基类上查找集合,在 MRO 中找到具有该__isabstractmethod__属性的相应对象,然后对当前类属性进行类型检查。

这意味着您在定义class时会抛出异常,而不是实例。为此,您需要向子类添加一个__call__方法,ABCMeta并根据您自己的__new__方法收集的有关哪些类型错误的信息抛出异常;ABCMeta与目前做什么和做什么类似的两阶段过程type.__new__。或者,更新__abstractmethods__类上的集合以添加任何已实现但类型错误的名称,然后让它type.__new__抛出异常。

以下实现采取了最后的策略;__abstractmethods__如果实现的类型不匹配(使用映射) ,则添加名称:

from types import FunctionType

class ABCMetaTypeCheck(ABCMeta):
    _typemap = {  # map abstract type to expected implementation type
        abstractproperty: property,
        abstractstatic: staticmethod,
        # abstractmethods return function objects
        FunctionType: FunctionType,
    }
    def __new__(mcls, name, bases, namespace):
        cls = super(ABCMetaTypeCheck, mcls).__new__(mcls, name, bases, namespace)
        wrong_type = set()
        seen = set()
        abstractmethods = cls.__abstractmethods__
        for base in bases:
            for name in getattr(base, "__abstractmethods__", set()):
                if name in seen or name in abstractmethods:
                    continue  # still abstract or later overridden
                value = base.__dict__.get(name)  # bypass descriptors
                if getattr(value, "__isabstractmethod__", False):
                    seen.add(name)
                    expected = mcls._typemap[type(value)]
                    if not isinstance(namespace[name], expected):
                        wrong_type.add(name)
        if wrong_type:
            cls.__abstractmethods__ = abstractmethods | frozenset(wrong_type)
        return cls

使用此元类,您将获得预期的输出:

>>> class Abstract(object):
...     __metaclass__ = ABCMetaTypeCheck
...     @abstractmethod
...     def foo(self): pass
...     @abstractproperty
...     def bar(self): pass
...     @abstractstatic
...     def baz(): pass
... 
>>> class ConcreteWrong(Abstract):
...     foo = 'bar'
...     bar = 'baz'
...     baz = 'spam'
... 
>>> ConcreteWrong()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class ConcreteWrong with abstract methods bar, baz, foo
>>> 
>>> class ConcreteCorrect(Abstract):
...     def foo(self): return 'bar'
...     @property
...     def bar(self): return 'baz'
...     @staticmethod
...     def baz(): return  'spam'
... 
>>> ConcreteCorrect()
<__main__.ConcreteCorrect object at 0x104ce1d10>
于 2015-04-05T09:01:50.477 回答