Python中的abc模組

有關心情發表於2019-02-05

Python中的abc模組



前言

《抽象基類(ABC)》中,基於C++講述抽象基類。儘管Python設計上以鴨子型別為主,但仍有抽象基類(ABC)的一席之地,它被封裝在了abc模組中供程式設計師使用。

abc模組有以下兩個主要功能:

  • 某種情況下,判定某個物件的型別,如:isinstance(a, Sized)
  • 強制子類必須實現某些方法,即ABC類的派生類

判斷型別

當我們判斷一個物件是否存在某個方法時,可以使用內建方法hasattr()

class A(object):
    def __len__(self):
        pass

if __name__ == "__main__":
    a = A()
    print("存在__len__方法" if hasattr(a, "__len__") else "沒有__len__方法")

# 輸出:
存在__len__方法

但在Python中,使用hasattr()並非優雅解法,這裡建議isinstance()。

abc模組中定義了Sized類,利用Sized可以判斷一個物件裡是否存在__len__方法,即:可否對這個物件使用len()函式。

from collections.abc import Sized

class A(object):
    def __len__(self):
        pass

if __name__ == "__main__":
    a = A()
    print("存在__len__方法" if isinstance(a, Sized) else "沒有__len__方法")

# 輸出:
存在__len__方法

isinstance實現原理

讓我們看看Sized類的原始碼:

class Sized(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __len__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            return _check_methods(C, "__len__")
        return NotImplemented

Sized類改寫了__subclasshook__魔法方法,使其可以通過isinstance()判斷物件是否含有__len__方法。同時,這個類必須基於元類abc.ABCMeta。我們也可以依葫蘆畫瓢,實現一個用來判斷物件是否存在greet()函式的類,儘管並不嚴謹:

import abc

class A(metaclass=abc.ABCMeta):
    @classmethod
    def __subclasscheck__(cls, subclass):
	    # 存在greet()返回True,不存在返回False
        if hasattr(subclass, "greet"):
            return True
        return False

class B(object):
    def greet(self):  # 定義了greet()方法
        pass

class C(object):  # 沒有greet()方法
    pass

class D(B):  # 繼承自B類,因此繼承了greet()方法
    pass

if __name__ == "__main__":
    b = B()
    c = C()
    d = D()

    print(isinstance(b, A))  # True
    print(isinstance(c, A))  # False
    print(isinstance(d, A))  # True

注意,此時A類可以被例項化,因為它還不是抽象基類。

實現ABC類

C++中利用純虛擬函式實現抽象基類,Python中寫法如下:

import abc

class A(metaclass=abc.ABCMeta):
	# 利用裝飾器修飾greet()
    @abc.abstractmethod
    def greet(self):
        print("hell world")

if __name__ == "__main__":
    a = A()

直譯器如期拋錯:

TypeError: Can't instantiate abstract class A with abstract methods greet

這是因為A類現在就是一個抽象基類了,不可以被例項化,同時,它的子類還必須實現greet()方法,否則例項化子類時直譯器也要報錯:

import abc

class A(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def greet(self):
        pass

class B(A):
    def greet(self):
        pass

class C(A):
    pass

if __name__ == "__main__":
    b = B()  # 正常例項化
    c = C()  # 直譯器拋錯

# 輸出:
# C類中沒有定義greet()方法導致的報錯
Traceback (most recent call last):
  File "xxx", line xxx, in <module>
    c = C()
TypeError: Can't instantiate abstract class C with abstract methods greet

其他基類

abc模組中還有實現了其他抽象基類,可以用來判斷型別或是繼承方法,這裡不做詳述了:

__all__ = ["Awaitable", "Coroutine",
           "AsyncIterable", "AsyncIterator", "AsyncGenerator",
           "Hashable", "Iterable", "Iterator", "Generator", "Reversible",
           "Sized", "Container", "Callable", "Collection",
           "Set", "MutableSet",
           "Mapping", "MutableMapping",
           "MappingView", "KeysView", "ItemsView", "ValuesView",
           "Sequence", "MutableSequence",
           "ByteString",
           ]

檔案所在路徑:…lib\_collocetions_abc.py

總結

  • abc模組中定義的類兼顧了繼承抽象基類與鴨子型別的設計方式。你既可以通過繼承Sized來擁有__len__方法,此時instance(物件, Sized)返回True;也可以在自己設計的類中實現__len__instance(物件, Sized)仍然返回True。

  • 對抽象基類來說,需要用到裝飾器 @abc.abstractmethod;對於鴨子型別來說,需要重寫__subclasshook__魔法方法。

  • 以上都是基於元類abc.ABCMeta實現的。順便提醒,注意import abcfrom collections import abc各自的區別。

相關文章