2015-04-05 22 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>