2014-09-03 40 views
3

我有一種情況,我想強制每個繼承自某個(抽象)類的每個類來實現一個方法。這是我通常使用@abstractmethod實現的。然而,考慮到多重繼承的這種情況:強制在所有繼承類中實現一個方法

from abc import ABCMeta, abstractmethod 
class A(object): 
    __metaclass__ = ABCMeta 

    @abstractmethod 
    def very_specific_method(self): 
     pass 

class B(A): 
    def very_specific_method(self): 
     print 'doing something in B' 

class C(B): 
    pass 

我要強制C實現方法爲好。我希望直接或間接繼承A的每個類都被迫實施該方法。這可能嗎?

澄清:我希望這適用於特定的方法,而不是所有的抽象方法。抽象方法應該繼續工作,但也許應該創建一個新的裝飾器,指示不同類型的方法。

備註:我在問題中使用了abc,因爲這似乎與問題最相關。我瞭解抽象方法通常如何工作並定期使用它們。這是一種不同的情況,我不介意如果它不通過abc。

+0

也許你可以扔在基類一個NotImplementedException。 – user3557327 2014-09-03 17:04:59

+1

@ user3557327 - 不能,因爲'B'的'very_specific_method'不會調用基類。 – mgilson 2014-09-03 17:05:33

+0

我的不好,我沒有注意到C擴展了B而不是A.你可以檢查一個方法是否在一個類中用''very_specific_method'在變量(C)'中實現。我不太瞭解元類,但使用它們你可以在創建類時檢查它。 – user3557327 2014-09-03 17:11:51

回答

3

ABCMeta修改後的版本應該做的伎倆。

這裏不是檢查方法__isabstractmethod__只能在基類中設置爲True我們可以檢查這是在類的MRO中,如果它在MRO中的任何類中找到並且它不存在於當前類中,那麼我們可以將其添加到集合abstracts

from abc import ABCMeta, abstractmethod 
from _weakrefset import WeakSet 

class EditedABCMeta(ABCMeta): 

    def __new__(mcls, name, bases, namespace): 
     cls = type.__new__(mcls, name, bases, namespace) 
     # Compute set of abstract method names 
     abstracts = set(name 
        for name, value in namespace.items() 
        if getattr(value, "__isabstractmethod__", False)) 

     for base in cls.__mro__: 
      for name, value in base.__dict__.items(): 
       if getattr(value, "__isabstractmethod__", False) and name not in cls.__dict__: 
        abstracts.add(name) 

     cls.__abstractmethods__ = frozenset(abstracts) 
     # Set up inheritance registry 
     cls._abc_registry = WeakSet() 
     cls._abc_cache = WeakSet() 
     cls._abc_negative_cache = WeakSet() 
     cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter 
     return cls 

class A(object): 
    __metaclass__ = EditedABCMeta 

    @abstractmethod 
    def veryspecificmethod(self): 
     pass 

class B(A): 
    def veryspecificmethod(self): 
     print 'doing something in B' 

    @abstractmethod 
    def foo(self): 
     print 'foo from B' 

class C(B): 
    def foo(self): 
     pass 

class D(C, B): 
    pass 

if __name__ == '__main__': 
    for cls in (C, D): 
     try: 
      cls().veryspecificmethod 
     except TypeError as e: 
      print e.message 
    print '-'*20 
    for cls in (C, D): 
     try: 
      cls().foo 
     except TypeError as e: 
      print e.message 

輸出:

Can't instantiate abstract class C with abstract methods veryspecificmethod 
Can't instantiate abstract class D with abstract methods foo, veryspecificmethod 
-------------------- 
Can't instantiate abstract class C with abstract methods veryspecificmethod 
Can't instantiate abstract class D with abstract methods foo, veryspecificmethod 

編輯:

添加特殊裝飾@enforcedmethod,能滿足您的要求,而不會影響@abstractmethod

from abc import ABCMeta, abstractmethod 

def enforcedmethod(func): 
    func.__enforcedmethod__ = True 
    return func 

class EditedABCMeta(ABCMeta): 

    def __call__(cls, *args, **kwargs): 

     enforcedmethods = set() 
     for base in cls.__mro__: 
      for name, value in base.__dict__.items(): 
       if getattr(value, "__enforcedmethod__", False) and name not in cls.__dict__: 
        enforcedmethods.add(name) 
     if enforcedmethods: 
      raise TypeError("Can't instantiate abstract class {} " 
          "with enforced methods {}".format(
           cls.__name__, ', '.join(enforcedmethods))) 
     else: 
      return super(EditedABCMeta, cls).__call__(*args, **kwargs) 

class A(object): 
    __metaclass__ = EditedABCMeta 

    @enforcedmethod 
    def veryspecificmethod(self): 
     pass 
    @abstractmethod 
    def simplemethod(self): 
     pass 

class B(A): 
    def veryspecificmethod(self): 
     print 'doing something in B' 
    def simplemethod(self): 
     pass 

class C(B): 
    pass 

class D(C): 
    def veryspecificmethod(self): 
     print 'doing something in D' 

輸出:

>>> D().veryspecificmethod() 
doing something in D 
>>> C().veryspecificmethod() 

Traceback (most recent call last): 
    File "<pyshell#23>", line 1, in <module> 
    C().veryspecificmethod() 
    File "C:\Python27\so.py", line 19, in __call__ 
    cls.__name__, ', '.join(enforcedmethods))) 
TypeError: Can't instantiate abstract class C with enforced methods veryspecificmethod 
+0

感謝您的回答。但是,請您看看這個問題的解釋嗎?我正在尋找一種不會搞亂抽象方法的方式,而是增加了一種不同的方法。 – Korem 2014-09-03 18:42:31

+0

@Korem檢查編輯,可能這就是你要找的。 – 2014-09-03 19:04:36

+0

爲什麼'返回超級(ABCMeta,cls).__調用__(* args,** kwargs)'而不是'返回超級(EditedABCMeta,cls).__ call __(* args,** kwargs)'? – Korem 2014-09-03 19:16:15

3

我敢肯定,這不是一個好主意,但我認爲你可以做到這一點。檢查出的ABCMeta implementation靈感:

from abc import ABCMeta 

def always_override(func): 
    func._always_override = True 
    return func 

class always_override_property(property): 
    _always_override = True 

class CrazyABCMeta(ABCMeta): 
    def __new__(mcls, name, bases, namespace): 
     cls = super(ABCMeta, mcls).__new__(mcls, name, bases, namespace) 

     abstracts = set() 
     # first, get all abstracts from the base classes 
     for base in bases: 
      abstracts.update(getattr(base, "_all_always_override", set())) 

     all_abstracts = abstracts.copy() 
     # Now add abstracts from this class and remove abstracts that this class defines 
     for name, value in namespace.items(): 
      always_override = getattr(value, '_always_override', False) 
      if always_override: 
       abstracts.add(name) 
       all_abstracts.add(name) 
      elif name in abstracts: 
       abstracts.remove(name) 

     cls._all_always_override = frozenset(all_abstracts) 
     cls._always_override = frozenset(abstracts) 
     return cls 

    def __call__(cls, *args, **kwargs): 
     if cls._always_override: 
      raise TypeError(
       'The following methods/properties must ' 
       'be overridden {}'.format(cls._all_always_override)) 
     return super(CrazyABCMeta, cls).__call__(*args, **kwargs) 

# # # # # # # # # # # 
# TESTS! 
# # # # # # # # # # # 
class A(object): 
    __metaclass__ = CrazyABCMeta 

    @always_override 
    def foo(self): 
     pass 

    @always_override_property 
    def bar(self): 
     pass 

class B(A): 
    def foo(self): 
     pass 
    bar = 1 

class C(B): 
    pass 

class D(C): 
    pass 

class E(D): 
    def foo(self): 
     pass 

    @property 
    def bar(self): 
     return 6 

for cls in (B, E): 
    cls() 
    print ("Pass {}".format(cls.__name__)) 

for cls in (C, D): 
    try: 
     print cls() 
    except TypeError: 
     print ("Pass {}".format(cls.__name__)) 
+1

[\ _ \ _ abstractmethods \ _ \ _和AttributeError](http://stackoverflow.com/q/24914584)正是我想到的。 – 2014-09-03 17:35:38

相關問題