My Python application contains many abstract classes and implementations. For example:
我的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
is a concrete class that we can use...
FriendlyMessagePrinter是一个我们可以使用的具体类......
FriendlyMessagePrinter().greet()
Good night.
...but MessageDisplay
and FriendlyMessageDisplay
are abstract classes and attempting to instantiate one would result in an error:
...但MessageDisplay和FriendlyMessageDisplay是抽象类,尝试实例化一个会导致错误:
TypeError: Can't instantiate abstract class MessageDisplay with abstract methods say
How can I check if a given class object is an (uninstantiatable) abstract class?
如何检查给定的类对象是否是(不可实例化的)抽象类?
3 个解决方案
#1
21
import inspect
print(inspect.isabstract(object)) # False
print(inspect.isabstract(MessageDisplay)) # True
print(inspect.isabstract(FriendlyMessageDisplay)) # True
print(inspect.isabstract(FriendlyMessagePrinter)) # False
This checks that the internal flag TPFLAGS_IS_ABSTRACT
is set in the class object, so it can't be fooled as easily as your implementation:
这将检查内部标志TPFLAGS_IS_ABSTRACT是否在类对象中设置,因此不能像实现那样容易上当。
class Fake:
__abstractmethods__ = 'bluh'
print(is_abstract(Fake), inspect.isabstract(Fake)) # True, False
#2
3
Abstract classes and their concrete implementations have an __abstractmethods__
attribute containing the names of abstract methods and properties that have not been implemented. This behaviour is described in PEP 3199:
抽象类及其具体实现具有__abstractmethods__属性,该属性包含尚未实现的抽象方法和属性的名称。 PEP 3199中描述了此行为:
Implementation: The
@abstractmethod
decorator sets the function attribute__isabstractmethod__
to the valueTrue
. TheABCMeta.__new__
method computes the type attribute__abstractmethods__
as the set of all method names that have an__isabstractmethod__
attribute whose value is true. It does this by combining the__abstractmethods__
attributes of the base classes, adding the names of all methods in the new class dict that have a true__isabstractmethod__
attribute, and removing the names of all methods in the new class dict that don't have a true__isabstractmethod__
attribute. If the resulting__abstractmethods__
set is non-empty, the class is considered abstract, and attempts to instantiate it will raise TypeError. (If this were implemented in CPython, an internal flagPy_TPFLAGS_ABSTRACT
could be used to speed up this check.)实现:@abstractmethod装饰器将函数属性__isabstractmethod__设置为值True。 ABCMeta .__ new__方法将类型属性__abstractmethods__计算为具有__isabstractmethod__属性且值为true的所有方法名称的集合。它通过组合基类的__abstractmethods__属性,添加新类dict中具有true __isabstractmethod__属性的所有方法的名称,并删除新类dict中没有true的所有方法的名称来实现__isabstractmethod__属性。如果生成的__abstractmethods__集合为非空,则该类被视为抽象,并尝试实例化它将引发TypeError。 (如果这是在CPython中实现的,可以使用内部标志Py_TPFLAGS_ABSTRACT加速此检查。)
So in concrete classes, this attribute either will not exist or will be an empty set. This is easy to check:
因此,在具体类中,此属性将不存在或将为空集。这很容易检查:
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
Or more succinctly:
或者更简洁:
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
-1
You could do this with the _ast
module. For example, if your example code were in foo.py
you could invoked this function with "foo.py"
and "FriendlyMessagePrinter"
as arguments.
您可以使用_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)
#1
21
import inspect
print(inspect.isabstract(object)) # False
print(inspect.isabstract(MessageDisplay)) # True
print(inspect.isabstract(FriendlyMessageDisplay)) # True
print(inspect.isabstract(FriendlyMessagePrinter)) # False
This checks that the internal flag TPFLAGS_IS_ABSTRACT
is set in the class object, so it can't be fooled as easily as your implementation:
这将检查内部标志TPFLAGS_IS_ABSTRACT是否在类对象中设置,因此不能像实现那样容易上当。
class Fake:
__abstractmethods__ = 'bluh'
print(is_abstract(Fake), inspect.isabstract(Fake)) # True, False
#2
3
Abstract classes and their concrete implementations have an __abstractmethods__
attribute containing the names of abstract methods and properties that have not been implemented. This behaviour is described in PEP 3199:
抽象类及其具体实现具有__abstractmethods__属性,该属性包含尚未实现的抽象方法和属性的名称。 PEP 3199中描述了此行为:
Implementation: The
@abstractmethod
decorator sets the function attribute__isabstractmethod__
to the valueTrue
. TheABCMeta.__new__
method computes the type attribute__abstractmethods__
as the set of all method names that have an__isabstractmethod__
attribute whose value is true. It does this by combining the__abstractmethods__
attributes of the base classes, adding the names of all methods in the new class dict that have a true__isabstractmethod__
attribute, and removing the names of all methods in the new class dict that don't have a true__isabstractmethod__
attribute. If the resulting__abstractmethods__
set is non-empty, the class is considered abstract, and attempts to instantiate it will raise TypeError. (If this were implemented in CPython, an internal flagPy_TPFLAGS_ABSTRACT
could be used to speed up this check.)实现:@abstractmethod装饰器将函数属性__isabstractmethod__设置为值True。 ABCMeta .__ new__方法将类型属性__abstractmethods__计算为具有__isabstractmethod__属性且值为true的所有方法名称的集合。它通过组合基类的__abstractmethods__属性,添加新类dict中具有true __isabstractmethod__属性的所有方法的名称,并删除新类dict中没有true的所有方法的名称来实现__isabstractmethod__属性。如果生成的__abstractmethods__集合为非空,则该类被视为抽象,并尝试实例化它将引发TypeError。 (如果这是在CPython中实现的,可以使用内部标志Py_TPFLAGS_ABSTRACT加速此检查。)
So in concrete classes, this attribute either will not exist or will be an empty set. This is easy to check:
因此,在具体类中,此属性将不存在或将为空集。这很容易检查:
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
Or more succinctly:
或者更简洁:
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
-1
You could do this with the _ast
module. For example, if your example code were in foo.py
you could invoked this function with "foo.py"
and "FriendlyMessagePrinter"
as arguments.
您可以使用_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)