Python 類與元類的深度挖掘 II

來源:互聯網
上載者:User
我們向上回溯一層,看看類對象本身是如何產生的。

我們知道 type() 方法可以查看一個對象的類型,或者說判斷這個對象是由那個類產生的:

print(type(12))print(type('python'))
<class 'int'><class 'str'>
class A:    passprint(type(A))
<class 'type'>

通過這段代碼可以看出,類對象 A 是由type() 產生的,也就是說 type 也可以用來產生新的對象,而且產生的是類對象,因此它是所有類對象的類:

print(type.__doc__)
type(object_or_name, bases, dict)type(object) -> the object's typetype(name, bases, dict) -> a new type

class 定義類的文法實際上轉化為 type(name, bases, dict),其中 name 參數為類的名字,bases 為繼承父類的元組,dict 為類的屬性和方法:

class A:    pass# 實際上等於B = type('A', (), {})print(A.__name__ == B.__name__)
True

理論上說這就是元類的意義,但從實際的角度出發顯然使用 class 文法更方便、合理,而元類的實際意義則是通過繼承 type 類來構造一個新的元類,並進行特定的操作以產生具有特定行為的類對象。這樣看來它的本質與普通的類對象沒有差異,只不過繼承的是 type 類。

在產生執行個體時是通過調用 __init__ 方法進行初始化的,而實際上在此之前會先調用 __new__ 方法用於建立執行個體,再通過 __init__ 初始化,就好像 __new__ 負責聲明變數,而 __init__ 負責對聲明的變數進行初始化一樣。這裡有一個規則是 __new__(cls,) 的傳回值必須是 cls 參數的執行個體,否則 __init__ 將不會觸發,例如在 enum.Enum 的定義中,由於枚舉類型是單例模式,因此在定義 __new__ 的時候沒有返回其執行個體,也就不會進行初始化:

class Enum:    def __new__(cls, value):        print(cls, value)        return value    def __init__(self):        print("Will not be called!")e = Enum(1)
<class '__main__.Enum'> 1

通常情況下自己定義 __new__ 需要通過調用父類的 __new__ 方法建立一個 cls 的執行個體,同樣在定義元類的時候則是調用上面提到的 type 的用法(因為元類繼承自 type):

class MetaEnum(type):    def __new__(metaclass, name, base, attrs):        print("Metaclass: {}\nName: {}\nParents: {}\nAttributes: {}".format(metaclass, name, base, attrs))        return super().__new__(metaclass, name, base, attrs)
class Enum(metaclass=MetaEnum):    # Python 2.7 中定義元類的方法是使用 __metaclass__ 變數    # [PEP 3115](https://www.python.org/dev/peps/pep-3115/)    # 將 Python 3.0 以後文法改為 class Cls(metaclass=Meta)    test = 0
Metaclass: <class '__main__.MetaEnum'>Name: EnumParents: ()Attributes: {'__qualname__': 'Enum', '__module__': '__main__', 'test': 0}

此時我們再來看 Enum 的類,已經不再是 type 而是其元類 MetaEnum:

type(Enum)
__main__.MetaEnum

除了 __new__ 方法之外,PEP 3115 還定義了 __prepare__ 屬性,用於設定初始化的命名空間(即 type 的第 3 個參數),還是以 enum.Enum 為例,我們需要限制枚舉類型中屬性名稱不得重複使用,則可以通過元類限制類的行為

# 定義新的字典類,在賦值新的 dict[k] = v 時# 檢查 k 是否重複class _EnumDict(dict):    def __init__(self):        super().__init__()        self.members = []    def __setitem__(self, k, v):        if k in self.members:            raise TypeError("Attempted to reuse key: '{}'".format(k))        else:            self.members.append(k)            super().__setitem__(k, v)class MetaEnum(type):    @classmethod    def __prepare__(metaclass, cls, bases):        return _EnumDict()    def __new__(metaclass, name, base, attrs):        return super().__new__(metaclass, name, base, attrs)class Enum(metaclass=MetaEnum):    passclass Color(Enum):    try:        red = 1        red = 2    except TypeError:# 這裡沒有使用 as err: 的原因是?        print("TypeError catched")
TypeError catched

Python 中一切皆為對象,所有的對象都是某一類的執行個體,或是某一元類的執行個體,type 是自己的元類也是自己的執行個體:

總結

元類在 Python 中屬於比較深層的黑魔法,在一般的日常應用中可能並不常用,但理解其背後的原理對於理解 Python 物件導向編程以及一切皆為對象的理念很有協助;如果你需要對類進行深度改造,至少要知道從何入手。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.