2013-01-19 51 views
18

我的Python應用程序包含許多抽象類和實現。例如:確定Python類是否爲Abstract Base Class或具體

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. 

...但MessageDisplayFriendlyMessageDisplay是抽象類,並試圖實例化一個會導致一個錯誤:

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

如何檢查是否a給定的類對象是一個(不可證實的)抽象類?

+0

Ref。對於偶然的讀者,ABC:http://docs.python.org/2/library/abc.html – 2013-01-19 05:17:02

回答

19
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 
3

抽象類及其具體實現具有__abstractmethods__屬性,其中包含尚未實現的抽象方法和屬性的名稱。這種行爲在PEP 3199描述:

Implementation: The @abstractmethod decorator sets the function attribute __isabstractmethod__ to the value True . The ABCMeta.__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 flag Py_TPFLAGS_ABSTRACT could be used to speed up this 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 

或者更簡潔地說:

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 
-1

您可以使用_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) 
+0

's/filepath/filename'。但是這顯然是錯的,對吧?它直接失敗了一個沒有方法的類,並且如果我簡單地決定創建一個像'def bluh(self):pass'這樣的函數。 – mmgp

+0

創造性的解決方案,但這也會在我的完整項目上失敗,因爲幾個抽象方法的實現(通過'super'調用),而不僅僅是'pass'。 –

相關問題