1. 程式人生 > 其它 >深度分析 Python 元類 abc

深度分析 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 中我們一定要區分objecttype。兩者不是一個東西卻又有著千絲萬縷的聯絡。

下面可以看一下 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