强制在所有继承类中实现方法

时间:2014-09-03 17:02:27

标签: python python-2.7 abstract

我有一种情况,我想强制执行从某个(抽象)类继承的每个类来实现一个方法。这是我通常使用@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完成的。

2 个答案:

答案 0 :(得分: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 :(得分: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