Определите, является ли класс 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
Как я могу проверить, является ли данный объект класса (неистинным) абстрактным классом?
Ответы
Ответ 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)