40

我的 Python 应用程序包含许多抽象类和实现。例如:

import abc
import datetime

class MessageDisplay(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractproperty
    def display(self, message):
        pass

class FriendlyMessageDisplay(MessageDisplay):
    def greet(self):
        hour = datetime.datetime.now().timetuple().tm_hour

        if hour < 7:
            raise Exception("Cannot greet while asleep.")
        elif hour < 12:
            self.display("Good morning!")
        elif hour < 18:
            self.display("Good afternoon!")
        elif hour < 20:
            self.display("Good evening!")
        else:
            self.display("Good night.")

class FriendlyMessagePrinter(FriendlyMessageDisplay):
    def display(self, message):
        print(message)

FriendlyMessagePrinter是我们可以使用的具体类...

FriendlyMessagePrinter().greet()
Good night.

...但是MessageDisplayandFriendlyMessageDisplay是抽象类,尝试实例化一个会导致错误:

TypeError: Can't instantiate abstract class MessageDisplay with abstract methods say

如何检查给定的类对象是否是(不可实例化的)抽象类?

4

3 回答 3

52
import inspect
print(inspect.isabstract(object))                  # False
print(inspect.isabstract(MessageDisplay))          # True
print(inspect.isabstract(FriendlyMessageDisplay))  # True
print(inspect.isabstract(FriendlyMessagePrinter))  # False

这会检查TPFLAGS_IS_ABSTRACT类对象中是否设置了内部标志,因此它不会像您的实现那样容易被愚弄:

class Fake:
    __abstractmethods__ = 'bluh'

print(is_abstract(Fake), inspect.isabstract(Fake)) # True, False
于 2013-01-19T03:54:17.633 回答
9

抽象类及其具体实现有一个__abstractmethods__属性,其中包含尚未实现的抽象方法和属性的名称。PEP 3199中描述了这种行为:

实现:装饰@abstractmethod器将函数属性__isabstractmethod__设置为 value True。该ABCMeta.__new__方法将 type 属性计算为具有值为 true__abstractmethods__的属性的所有方法名称的集合。__isabstractmethod__它通过组合__abstractmethods__基类的属性、添加新类 dict 中具有 true__isabstractmethod__属性的所有方法的名称以及删除新类 dict 中不具有 true__isabstractmethod__属性的所有方法的名称来做到这一点。如果结果__abstractmethods__集非空,则该类被认为是抽象的,并且尝试实例化它会引发 TypeError。(如果这是在 CPython 中实现的,则可以使用内部标志Py_TPFLAGS_ABSTRACT来加速此检查。)

所以在具体的类中,这个属性要么不存在,要么是一个空集。这很容易检查:

def is_abstract(cls):
    if not hasattr(cls, "__abstractmethods__"):
        return False # an ordinary class
    elif len(cls.__abstractmethods__) == 0:
        return False # a concrete implementation of an abstract class
    else:
        return True # an abstract class

或者更简洁地说:

def is_abstract(cls):
    return bool(getattr(cls, "__abstractmethods__", False))
print(is_abstract(object))                 # False
print(is_abstract(MessageDisplay))         # True
print(is_abstract(FriendlyMessageDisplay)) # True
print(is_abstract(FriendlyMessagePrinter)) # False
于 2013-01-19T03:38:35.613 回答
-3

您可以使用_ast模块执行此操作。例如,如果您的示例代码在其中,您可以使用和作为参数foo.py调用此函数。"foo.py""FriendlyMessagePrinter"

def is_abstract(filepath, class_name):
    astnode = compile(open(filename).read(), filename, 'exec', _ast.PyCF_ONLY_AST)
    for node in astnode.body:
        if isinstance(node, _ast.ClassDef) and node.name == class_name:
            for funcdef in node.body:
                if isinstance(funcdef, _ast.FunctionDef):
                    if any(not isinstance(n, _ast.Pass) for n in funcdef.body):
                        return False
            return True
    print 'class %s not found in file %s' %(class_name, filepath)
于 2013-01-19T04:05:48.933 回答