深度分析 Python 元類 abc
元類背景
我們先看 Python 下實現單例模式的一種寫法:
class Singleton(type): def __init__(cls, *args, **kwargs): cls._instance = None super().__init__(*args, **kwargs) def __call__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__call__(*args, **kwargs)return cls._instance
class Test(metaclass=Singleton): def test(self): print("test") test1 = Test() test2 = Test() print(id(test1), id(test2))
測試結果,很明顯功能是實現了,兩次例項化對應的物件為同一個:
>>> test1 = Test() ... test2 = Test() >>> test1 <__main__.Test object at 0x10aa13d68> >>> test2<__main__.Test object at 0x10aa13d68> >>> id(test1) 4473306472 >>> id(test2) 4473306472
沒錯,這種方式就是用元類(metaclass)控制類的例項化。
元類的祕密
通過元類實現單例模式我們已經對元類有個基本的認識,下面我們就看看元類到底是什麼?
1. 初識元類
type 兩種使用方式:
type(object)
獲取物件型別。type(name, bases, dict)
建立類物件。
>>> class A(object): ...pass ... >>> A <class '__main__.A'> >>> A() <__main__.A object at 0x108a4ac50> >>> type("B", (object, ), dict()) <class '__main__.B'> >>> type("B", (object, ), dict())() <__main__.B object at 0x108a0eba8>
上面的例子可以發現 type 可以初始化類物件,功能與類定義的方式一致。所以 type(元類)是可以實現類/物件初始化。也就是說:“元類就是建立類的類”。
>>> type(int) <class 'type'> >>> type(str) <class 'type'> >>> type(bytes) <class 'type'>
Python 的基礎資料型別的型別都指向 type。在 Python中,內建的類type
是元類。
Python中 定義新的元類是通過向類定義提供關鍵字引數metaclass
就可以使用這個新的元類。就像下面一樣定義與使用元類:
class NewType(type): pass class A(metaclass=NewType): pass
所以在 Python 中我們一定要區分object
與type
。兩者不是一個東西卻又有著千絲萬縷的聯絡。
下面可以看一下 wiki 中的一個很有意思的例子:
r = object c = type class M(c): pass class A(metaclass=M): pass class B(A): pass b = B()
最重要的就是:
object
類是所有類的祖先。type
元類是所有元類的祖先。
也就是說所有物件(包括 type )都是繼承自object
。所有物件(包括 object )的型別都源與 type (元類)。如下程式碼所示:
>>> type(object) <class 'type'> >>> isinstance(object, type) True
最後再貼一張圖理解一下這兩者的關係:
2. 元類的作用
元類可以干預類的建立。比如 Python 標準庫庫中就有一個元類abc.ABCMeta
,該元類的作用可以定義抽象類,類似 Java 中abstract class
。下面我們就看看它的使用:
class Base(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Http(Base, abc.ABC): pass
我們測試一下程式碼:
>>> Base() Traceback (most recent call last): File "<input>", line 1, in <module> TypeError: Can't instantiate abstract class Base with abstract methods read, write >>> Http() Traceback (most recent call last): File "<input>", line 1, in <module> TypeError: Can't instantiate abstract class Http with abstract methods read, write
發現抽象類是不能例項化的。子類必須實現抽象方法才能對類進行例項化,我們修改子類程式碼:
>>> class Http(Base, abc.ABC): ... ... def read(self): ... pass ... ... def write(self): ... pass ... ... def open(self): ... print(" open method ") ... >>> Http().open() open method
可以看到Http
繼承Base
並且實現抽象方法是沒有任何問題的。
3. 自定義元類
如下在實現對類物件的快取的功能。
建立一個類的物件時,如果之前使用同樣引數建立過這個物件,那就返回它的快取引用。
import weakref class Cached(type): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__cache = weakref.WeakValueDictionary() def __call__(self, *args): if args in self.__cache: return self.__cache[args] else: obj = super().__call__(*args) self.__cache[args] = obj return obj # Example class Spam(metaclass=Cached): def __init__(self, name): print('Creating Spam({!r})'.format(name)) self.name = name
驗證一下功能:
>>> a = Spam('Guido') Creating Spam('Guido') >>> b = Spam('Diana') Creating Spam('Diana') >>> c = Spam('Guido') # Cached >>> a is b False >>> a is c # Cached value returned True
下面看一下普通方式是如何實現的:
import weakref _spam_cache = weakref.WeakValueDictionary() class Spam: def __init__(self, name): self.name = name def get_spam(name): if name not in _spam_cache: s = Spam(name) _spam_cache[name] = s else: s = _spam_cache[name] return s