Python 類與元類的深度挖掘 II

上一篇解決了經過調用類對象生成實例對象過程當中可能遇到的命名空間相關的一些問題,此次咱們向上回溯一層,看看類對象自己是如何產生的。python

咱們知道 type() 方法能夠查看一個對象的類型,或者說判斷這個對象是由那個類產生的:編程

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

經過這段代碼能夠看出,類對象 A 是由type() 產生的,也就是說 type 也能夠用來產生新的對象,並且產生的是類對象,所以它是全部類對象的類:app

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

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

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

print(A.__name__ == B.__name__)
True

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

在生成實例時是經過調用 __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: Enum
Parents: ()
Attributes: {'__qualname__': 'Enum', '__module__': '__main__', 'test': 0}

此時咱們再來看 Enum 的類,已經再也不是 type 而是其元類 MetaEnum圖片

type(Enum)
__main__.MetaEnum

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

# 定義新的字典類,在賦值新的 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):
    pass

class Color(Enum):
    try:
        red = 1
        red = 2
    except TypeError:# 這裏沒有使用 as err: 的緣由是?
        print("TypeError catched")
TypeError catched

Python 中一切皆爲對象,全部的對象都是某一類的實例,或是某一元類的實例,type 是本身的元類也是本身的實例:it

metaclass

總結

元類在 Python 中屬於比較深層的黑魔法,在通常的平常應用中可能並不經常使用,但理解其背後的原理對於理解 Python 面向對象編程以及一切皆爲對象的理念頗有幫助;若是你須要對類進行深度改造,至少要知道從何入手。

參考

  1. What is a metaclass in Python

pyhub

相關文章
相關標籤/搜索