您當前的位置:首頁 > 收藏

詳解Python元類

作者:由 小明 發表于 收藏時間:2017-11-08

什麼是元類?

理解元類(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

print

ObjectCreator

<

class

__main__

ObjectCreator

’>

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

In

def

func

instance

):

。。。

print

instance

a

instance

b

。。。

print

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可以把對於類的描述作為引數,並返回一個類。

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

詳解Python元類

詳解Python元類

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

):

print

‘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

標簽: __  type  Python  元類  建立