詳解Python元類
什麼是元類?
理解元類(metaclass)之前,我們先了解下Python中的OOP和類(Class)。
面向物件全稱 Object Oriented Programming 簡稱OOP,這種程式設計思想被大家所熟知。它是把物件作為一個程式的基本單元,把資料和功能封裝在裡面,能夠實現很好的複用性,靈活性和擴充套件性。OOP中有2個基本概念:類和物件:
1。 類是描述如何建立一個物件的程式碼段,用來描述具有相同的屬性和方法的物件的集合,它定義了該集合中每個物件所共有的屬性和方法
2。 物件是類的例項(Instance)。
我們舉個例子:
In
:
class
ObjectCreator
(
object
):
。。。
:
pass
。。。
:
In
:
my_object
=
ObjectCreator
()
In
:
my_object
而Python中的類並不是僅限於此:
In
:
(
ObjectCreator
)
<
class
‘
__main__
。
ObjectCreator
’>
ObjectCreator竟然可以被print,所以它的類也是物件!既然類是物件,你就能動態地建立它們,就像建立任何物件那樣。我在日常工作裡面就會有這種動態建立類的需求,比如在mock資料的時候,現在有個函式func接收一個引數:
In
:
def
func
(
instance
):
。。。
:
(
instance
。
a
,
instance
。
b
)
。。。
:
(
instance
。
method_a
(
10
))
。。。
:
正常使用起來傳入的instance是符合需求的(有a、b屬性和method_a方法),但是當我想單獨除錯func的時候,需要「造」一個,假如不用元類,應該是這樣寫:
In
:
def
generate_cls
(
a
,
b
):
。。。
:
class
Fake
(
object
):
。。。
:
def
method_a
(
self
,
n
):
。。。
:
return
n
。。。
:
Fake
。
a
=
a
。。。
:
Fake
。
b
=
b
。。。
:
return
Fake
。。。
:
In
:
ins
=
generate_cls
(
1
,
2
)()
In
:
ins
。
a
,
ins
。
b
,
ins
。
method_a
(
10
)
Out
:
(
1
,
2
,
10
)
你會發現這不算是「動態建立」的:
1。 類名(Fake)不方便改變
2。 要建立的類需要的屬性和方法越多,就要對應的加碼,不靈活。
我平時怎麼做呢:
In
:
def
method_a
(
self
,
n
):
。。。
:
return
n
。。。
:
In
:
ins
=
type
(
‘Fake’
,
(),
{
‘a’
:
1
,
‘b’
:
2
,
‘method_a’
:
method_a
})()
In
:
ins
。
a
,
ins
。
b
,
ins
。
method_a
(
10
)
Out
:
(
1
,
2
,
10
)
到了這裡,引出了type函式。本來它用來能讓你瞭解一個物件的型別:
In
:
type
(
1
)
Out
:
int
In
:
type
(
‘1’
)
Out
:
str
In
:
type
(
ObjectCreator
)
Out
:
type
In
:
type
(
ObjectCreator
())
Out
:
__main__
。
ObjectCreator
另外,type如上所說還可以動態地建立類:type可以把對於類的描述作為引數,並返回一個類。
用來建立類的東東就是「元類」,放張圖吧:
MyClass
=
type
(
‘MyClass’
,
(),
{})
這種用法就是由於type實際上是一個元類,作為元類的type在Python中被用於在後臺建立所有的類。在Python語言上有個說法「Everything is an object」。包整數、字串、函式和類。。。 所有這些都是物件。所有這些都是由一個類建立的:
In
:
age
=
35
In
:
age
。
__class__
Out
:
int
In
:
name
=
‘bob’
In
:
name
。
__class__
Out
:
str
。。。
現在,任何__class__中的特定__class__是什麼?
In
:
age
。
__class__
。
__class__
Out
:
type
In
:
name
。
__class__
。
__class__
Out
:
type
。。。
如果你願意,你可以把type稱為「類工廠」。type是Python中內建元類,當然,你也可以建立你自己的元類。
建立自己的元類
Python2建立類的時候,可以新增一個__metaclass__屬性:
class
Foo
(
object
):
__metaclass__
=
something
。。。
[
。。。
]
如果你這樣做,Python會使用元類來建立Foo這個類。Python會在類定義中尋找__metaclass__。如果找到它,Python會用它來建立物件類Foo。如果沒有找到它,Python將使用type來建立這個類。
在Python3中語法改變了一下:
class
Simple1
(
object
,
metaclass
=
something
。。。
):
[
。。。
]
本質上是一樣的。拿一個4年前寫分享的元類例子(就是為了推薦你來閱讀 ?[我的PPT:《Python高階程式設計》](dongweiming/Expert-Python) )吧:
class
HelloMeta
(
type
):
def
__new__
(
cls
,
name
,
bases
,
attrs
):
def
__init__
(
cls
,
func
):
cls
。
func
=
func
def
hello
(
cls
):
‘hello world’
t
=
type
。
__new__
(
cls
,
name
,
bases
,
attrs
)
t
。
__init__
=
__init__
t
。
hello
=
hello
return
t
class
New_Hello
(
object
):
__metaclass__
=
HelloMeta
New_Hello初始化需要新增一個引數,幷包含一個叫做hello的方法:
In
:
h
=
New_Hello
(
lambda
x
:
x
)
In
:
h
。
func
(
10
),
h
。
hello
()
hello
world
Out
:
(
10
,
None
)
PS: 這個例子只能運行於Python2。
在Python裡__new__方法建立例項,__init__負責初始化一個例項。對於type也是一樣的效果,只不過針對的是「類」,在上面的HelloMeta中只使用了__new__建立類,我們再感受一個使用__init__的元類:
In
:
class
HelloMeta2
(
type
):
。。。
:
def
__init__
(
cls
,
name
,
bases
,
attrs
):
。。。
:
super
(
HelloMeta2
,
cls
)
。
__init__
(
name
,
bases
,
attrs
)
。。。
:
attrs_
=
{}
。。。
:
for
k
,
v
in
attrs
。
items
():
。。。
:
if
not
k
。
startswith
(
‘__’
):
。。。
:
attrs_
[
k
]
=
v
。。。
:
setattr
(
cls
,
‘_new_dict’
,
attrs_
)
。。。
:
。。。
:
別往下看。思考下這樣創建出來的類有什麼特殊的地方?
我揭曉一下(這次使用Python 3語法):
In
:
class
New_Hello2
(
metaclass
=
HelloMeta2
):
。。。
:
a
=
1
。。。
:
b
=
True
In
:
New_Hello2
。
_new_dict
Out
:
{
‘a’
:
1
,
‘b’
:
True
}
In
:
h2
=
New_Hello2
()
In
:
h2
。
_new_dict
Out
:
{
‘a’
:
1
,
‘b’
:
True
}
有點明白麼?其實就是在建立類的時候把類的屬性迴圈了一遍把不是__開頭的屬性最後存在了_new_dict上。
什麼時候需要用元類?
日常的業務邏輯開發是不太需要使用到元類的,因為元類是用來攔截和修改類的建立的,用到的場景很少。我能想到最典型的場景就是 ORM。ORM就是「物件 關係 對映」的意思,簡單的理解就是把關係資料庫的一張表對映成一個類,一行記錄對映為一個物件。ORM框架中的Model只能動態定義,因為這個模式下這些關係只能是由使用者來定義,元類再配合描述符就可以實現ORM了,現在做個預告,未來我會分享「如何寫一個ORM」這個主題。
歡迎訂閱我的同名微信公眾號「Python之美」,也可以加入我的Python學習QQ群: 522012167/121435120
參考資料
1。 What is a metaclass in Python?
2。 Understanding Python metaclasses