interfaces - como hacer una clase abstracta en python
Determine si una clase de Python es una clase base abstracta o concreta (3)
Las clases abstractas y sus implementaciones concretas tienen un atributo __abstractmethods__
contiene los nombres de los métodos abstractos y las propiedades que no se han implementado. Este comportamiento se describe en PEP 3199 :
Implementación: el decorador
@abstractmethod
establece el atributo de función__isabstractmethod__
en el valorTrue
. El métodoABCMeta.__new__
calcula el atributo de tipo__abstractmethods__
como el conjunto de todos los nombres de métodos que tienen un atributo__isabstractmethod__
cuyo valor es verdadero. Lo hace combinando los atributos__abstractmethods__
de las clases base, agregando los nombres de todos los métodos en el nuevo__isabstractmethod__
clase que tienen un verdadero atributo__isabstractmethod__
y eliminando los nombres de todos los métodos en el nuevo dictado de clase que no tienen un verdadero__isabstractmethod__
atributo. Si el conjunto__abstractmethods__
resultante no está vacío, la clase se considera abstracta e intenta crear una instancia de TypeError. (Si esto se implementara en CPython, se podría utilizar un indicador internoPy_TPFLAGS_ABSTRACT
para acelerar esta comprobación).
Entonces, en clases concretas, este atributo o no existirá o será un conjunto vacío. Esto es fácil de verificar:
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
O más sucintamente:
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
Mi aplicación Python contiene muchas clases abstractas e implementaciones. Por ejemplo:
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
es una clase concreta que podemos usar ...
FriendlyMessagePrinter().greet()
Good night.
... pero MessageDisplay
y FriendlyMessageDisplay
son clases abstractas y al intentar crear una instancia se generará un error:
TypeError: Can''t instantiate abstract class MessageDisplay with abstract methods say
¿Cómo puedo verificar si un objeto de clase dado es una clase abstracta (no insustituible)?
Podrías hacer esto con el módulo _ast
. Por ejemplo, si su código de ejemplo estuviera en foo.py
, podría invocar esta función con "foo.py"
y "FriendlyMessagePrinter"
como argumentos.
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)
import inspect
print(inspect.isabstract(object)) # False
print(inspect.isabstract(MessageDisplay)) # True
print(inspect.isabstract(FriendlyMessageDisplay)) # True
print(inspect.isabstract(FriendlyMessagePrinter)) # False
Esto comprueba que el indicador interno TPFLAGS_IS_ABSTRACT
está establecido en el objeto de clase, por lo que no se puede engañar tan fácilmente como su implementación:
class Fake:
__abstractmethods__ = ''bluh''
print(is_abstract(Fake), inspect.isabstract(Fake)) # True, False