2015-04-05 57 views
3

这是我想要的设置: A应该是一个抽象基类,其抽象方法f()是一个静态的&。乙方应自A.要求继承: 1.你不应该能够实例化一个 2.您应不能够实例B,除非它实现了一个静态的F()在Python中,如何强制抽象方法在子类上是静态的?

this问题获得灵感,我已经尝试了几种方法。利用这些定义:

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被使用在this答案建议的方式来定义。以下是我尝试的一些操作以及不期望的结果。

使用类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() 

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

回答

7

你不能做你想要的只是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> 

我能够构建即使两个foobarConcrete()是简单的属性。

ABCMeta元类仅跟踪有多少个对象,__isabstractmethod__属性为true;当从元类创建类时(调用ABCMeta.__new__cls.__abstractmethods__属性随后设置为frozenset对象,并且所有名称仍然是抽象的。

type.__new__然后测试那个frozenset并抛出TypeError如果您尝试创建一个实例。

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

这意味着当定义时,您会抛出异常,但不是实例。为了达到这个目的,你需要在你的ABCMeta子类中添加一个__call__方法,然后根据你自己的__new__方法收集的信息来抛出异常:哪些类型是错误的;与目前ABCMetatype.__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>