Подтвердить что ты не робот

Определите, является ли класс Python абстрактным базовым классом или бетоном

Приложение My 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.

... но MessageDisplay и FriendlyMessageDisplay являются абстрактными классами, и попытка создания экземпляра может привести к ошибке:

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

Как я могу проверить, является ли данный объект класса (неистинным) абстрактным классом?

4b9b3361

Ответ 1

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

Ответ 2

Абстрактные классы и их конкретные реализации имеют атрибут __abstractmethods__, содержащий имена абстрактных методов и свойств, которые не были реализованы. Это поведение описано в PEP 3199:

Реализация: Декоратор @abstractmethod устанавливает атрибут функции __isabstractmethod__ в значение True. Метод ABCMeta.__new__ вычисляет атрибут type __abstractmethods__ как набор всех имен методов, у которых есть атрибут __isabstractmethod__, значение которого истинно. Он делает это, комбинируя атрибуты __abstractmethods__ базовых классов, добавляя имена всех методов в новый класс dict, которые имеют истинный атрибут __isabstractmethod__ и удаляют имена всех методов в новом классе dict, t имеет истинный атрибут __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

Ответ 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)