我們向上回溯一層,看看類別物件本身是如何產生的。
我們知道 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 type type(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__ 的定義中,由於枚舉類型是單例模式,因此定義 __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)除了__Hall__.Metao __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): pass class Color(Enum): try: red = 1 red = 2 except TypeError:# 这里没有使用 as err: 的原因是? print("TypeError catched")
TypeError catched
元類在Python 中屬於比較深層的黑魔法,在一般的日常應用中可能並不常用,但理解其背後的原理對於理解Python 面向對象的編程以及一切皆為對象的編程理念很有幫助;如果你需要對類別進行深度改造,至少要知道從何入手。