2014-09-03 78 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

相关问题