標籤:

詳解Python元類

什麼是元類?

理解元類(metaclass)之前,我們先了解下Python中的OOP和類(Class)。

面向對象全稱 Object Oriented Programming 簡稱OOP,這種編程思想被大家所熟知。它是把對象作為一個程序的基本單元,把數據和功能封裝在裡面,能夠實現很好的復用性,靈活性和擴展性。OOP中有2個基本概念:類和對象:

1. 類是描述如何創建一個對象的代碼段,用來描述具有相同的屬性和方法的對象的集合,它定義了該集合中每個對象所共有的屬性和方法

2. 對象是類的實例(Instance)。

我們舉個例子:

In : class ObjectCreator(object):n...: passn...:nnIn : my_object = ObjectCreator()nnIn : my_objectn

而Python中的類並不是僅限於此:

In : print(ObjectCreator)n<class __main__.ObjectCreator>n

ObjectCreator竟然可以被print,所以它的類也是對象!既然類是對象,你就能動態地創建它們,就像創建任何對象那樣。我在日常工作裡面就會有這種動態創建類的需求,比如在mock數據的時候,現在有個函數func接收一個參數:

In : def func(instance):n...: print(instance.a, instance.b)n...: print(instance.method_a(10))n...:n

正常使用起來傳入的instance是符合需求的(有a、b屬性和method_a方法),但是當我想單獨調試func的時候,需要「造」一個,假如不用元類,應該是這樣寫:

In : def generate_cls(a, b):n...: class Fake(object):n...: def method_a(self, n):n...: return nn...: Fake.a = an...: Fake.b = bn...: return Faken...:nnIn : ins = generate_cls(1, 2)()nnIn : ins.a, ins.b, ins.method_a(10)nOut: (1, 2, 10)n

你會發現這不算是「動態創建」的:

1. 類名(Fake)不方便改變

2. 要創建的類需要的屬性和方法越多,就要對應的加碼,不靈活。

我平時怎麼做呢:

In : def method_a(self, n):n...: return nn...: nIn : ins = type(Fake, (), {a: 1, b: 2, method_a: method_a})()nnIn : ins.a, ins.b, ins.method_a(10)nOut: (1, 2, 10)n

到了這裡,引出了type函數。本來它用來能讓你了解一個對象的類型:

In : type(1)nOut: intnnIn : type(1)nOut: strnnIn : type(ObjectCreator)nOut: typennIn : type(ObjectCreator())nOut: __main__.ObjectCreatorn

另外,type如上所說還可以動態地創建類:type可以把對於類的描述作為參數,並返回一個類。

用來創建類的東東就是「元類」,放張圖吧:

MyClass = type(MyClass, (), {})n

這種用法就是由於type實際上是一個元類,作為元類的type在Python中被用於在後台創建所有的類。在Python語言上有個說法「Everything is an object」。包整數、字元串、函數和類... 所有這些都是對象。所有這些都是由一個類創建的:

In : age = 35nIn : age.__class__nOut: intnnIn : name = bobnIn : name.__class__nOut: strn...n

現在,任何__class__中的特定__class__是什麼?

In : age.__class__.__class__nOut: typennIn : name.__class__.__class__nOut: typen...n

如果你願意,你可以把type稱為「類工廠」。type是Python中內建元類,當然,你也可以創建你自己的元類。

創建自己的元類

Python2創建類的時候,可以添加一個__metaclass__屬性:

class Foo(object):n __metaclass__ = something...n [...]n

如果你這樣做,Python會使用元類來創建Foo這個類。Python會在類定義中尋找__metaclass__。如果找到它,Python會用它來創建對象類Foo。如果沒有找到它,Python將使用type來創建這個類。

在Python3中語法改變了一下:

class Simple1(object, metaclass=something...):n [...]n

本質上是一樣的。拿一個4年前寫分享的元類例子(就是為了推薦你來閱讀 ??[我的PPT:《Python高級編程》](dongweiming/Expert-Python) )吧:

class HelloMeta(type):n def __new__(cls, name, bases, attrs):n def __init__(cls, func):n cls.func = funcn def hello(cls):n print hello worldn t = type.__new__(cls, name, bases, attrs)n t.__init__ = __init__n t.hello = hellon return tn nclass New_Hello(object):n __metaclass__ = HelloMetan

New_Hello初始化需要添加一個參數,並包含一個叫做hello的方法:

In : h = New_Hello(lambda x: x)nnIn : h.func(10), h.hello()nhello worldnOut: (10, None)n

PS: 這個例子只能運行於Python2。

在Python里__new__方法創建實例,__init__負責初始化一個實例。對於type也是一樣的效果,只不過針對的是「類」,在上面的HelloMeta中只使用了__new__創建類,我們再感受一個使用__init__的元類:

In : class HelloMeta2(type):n...: def __init__(cls, name, bases, attrs):n...: super(HelloMeta2, cls).__init__(name, bases, attrs)n...: attrs_ = {}n...: for k, v in attrs.items():n...: if not k.startswith(__):n...: attrs_[k] = vn...: setattr(cls, _new_dict, attrs_)n...:n...:n

別往下看。思考下這樣創建出來的類有什麼特殊的地方?

我揭曉一下(這次使用Python 3語法):

In : class New_Hello2(metaclass=HelloMeta2):n...: a = 1n...: b = TruennIn : New_Hello2._new_dictnOut: {a: 1, b: True}nnIn : h2 = New_Hello2()nnIn : h2._new_dictnOut: {a: 1, b: True}n

有點明白么?其實就是在創建類的時候把類的屬性循環了一遍把不是__開頭的屬性最後存在了_new_dict上。

什麼時候需要用元類?

日常的業務邏輯開發是不太需要使用到元類的,因為元類是用來攔截和修改類的創建的,用到的場景很少。我能想到最典型的場景就是 ORM。ORM就是「對象 關係 映射」的意思,簡單的理解就是把關係資料庫的一張表映射成一個類,一行記錄映射為一個對象。ORM框架中的Model只能動態定義,因為這個模式下這些關係只能是由使用者來定義,元類再配合描述符就可以實現ORM了,現在做個預告,未來我會分享「如何寫一個ORM」這個主題。

歡迎訂閱我的同名微信公眾號「Python之美」,也可以加入我的Python學習QQ群: 522012167/121435120

參考資料

1. What is a metaclass in Python?

2. Understanding Python metaclasses


推薦閱讀:

第十六章 API例子:用Python驅動Firefox採集網頁數據
初學python--認識裝飾器
python的descriptor的意圖是什麼,想知道python當初弄出功能的意圖?困擾我好久了。
Python 的 Metaclass 有沒有什麼好的 Best Practice 可以學習?
拆代碼學演算法之用python實現KNN過程詳解

TAG:Python |