新聞中心
如果你看過(guò)比較優(yōu)秀的 Python 開(kāi)源框架,肯定見(jiàn)到過(guò)元類(lèi)的身影。例如,在一個(gè)類(lèi)中定義了類(lèi)屬性 __metaclass__,這就說(shuō)明這個(gè)類(lèi)使用了元類(lèi)來(lái)創(chuàng)建。

公司主營(yíng)業(yè)務(wù):成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。成都創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。成都創(chuàng)新互聯(lián)推出海西免費(fèi)做網(wǎng)站回饋大家。
那元類(lèi)的實(shí)現(xiàn)原理究竟是怎樣的?使用元類(lèi)能幫我們?cè)陂_(kāi)發(fā)中解決什么樣的問(wèn)題?
這篇文章,我們就來(lái)看一下 Python 元類(lèi)的來(lái)龍去脈。
什么是元類(lèi)?
我們都知道,定義一個(gè)類(lèi),然后調(diào)用它的構(gòu)造方法,就可以初始化出一個(gè)實(shí)例出來(lái),就像下面這樣:
- class Person(object)
- def __init__(name):
- self.name = name
- p = Person('zhangsan')
那你有沒(méi)有想過(guò),我們平時(shí)定義的類(lèi),它是如何創(chuàng)建出來(lái)的?
別著急,我們先來(lái)看一個(gè)例子:
- >>> a = 1 # 創(chuàng)建a的類(lèi)是int a是int的實(shí)例
- >>> a.__class__
- >>> b = 'abc' # 創(chuàng)建b的類(lèi)是str b是str的實(shí)例
- >>> b.__class__
- >>> def c(): # 創(chuàng)建c的類(lèi)是function 方法c是function的實(shí)例
- ... pass
- >>> c.__class__
- >>> class D(object): # 創(chuàng)建d的類(lèi)是D d是D的實(shí)例
- ... pass
- >>> d.__class__
在這個(gè)例子中,我們定義了 int、str、function、class,然后分別調(diào)用了它們的__class__ 方法,這個(gè) __class__ 方法可以返回實(shí)例是如何創(chuàng)建出來(lái)的。
從方法返回的結(jié)果我們可以看到:
- 創(chuàng)建整數(shù) a 的類(lèi)是 int,也就是說(shuō) a 是 int 的一個(gè)實(shí)例
- 創(chuàng)建字符串 b 的類(lèi)是 str,也就是說(shuō) b 是 str 的一個(gè)實(shí)例
- 創(chuàng)建函數(shù) c 的類(lèi)是 function,也就是說(shuō) c 是 function 的一個(gè)實(shí)例
- 創(chuàng)建實(shí)例 d 的類(lèi)是 class,也就是說(shuō) d 是 class 的一個(gè)實(shí)例
除了這些之外,我們?cè)陂_(kāi)發(fā)中使用到的例如 list、dict 也類(lèi)似,你可以測(cè)試觀察一下結(jié)果。
現(xiàn)在我們已經(jīng)得知,創(chuàng)建這些實(shí)例的類(lèi)是 int、str、function、class,那進(jìn)一步思考一下,這些類(lèi)又是怎么創(chuàng)建出來(lái)的呢?
同樣地,我們也調(diào)用這些類(lèi)的 __class__ 方法,觀察結(jié)果:
- >>> a = 1
- >>> a.__class__.__class__
- >>>
- >>> b = 'abc'
- >>> b.__class__.__class__
- >>>
- >>> def c():
- ... pass
- >>> c.__class__.__class__
- >>>
- >>> class D(object):
- ... pass
- >>> d = D()
- >>> d.__class__.__class__
從結(jié)果我們可以看到,創(chuàng)建這些類(lèi)的類(lèi),都是 type,所以 type 就是創(chuàng)建所有類(lèi)的「元類(lèi)」。也就是說(shuō),元類(lèi)的作用就是用來(lái)創(chuàng)建類(lèi)的。
你可以這樣理解:
- 元類(lèi) -> 類(lèi)
- 類(lèi) -> 實(shí)例
用偽代碼表示,就是下面這樣:
- klass = MetaClass() # 元類(lèi)創(chuàng)建類(lèi)
- obj = klass() # 類(lèi)創(chuàng)建實(shí)例
是不是很有意思?
在這里,你也可以感受一下這句話的含義:Python 中一切皆對(duì)象!
無(wú)論是普通類(lèi)型、方法、實(shí)例,還是類(lèi),都可以統(tǒng)一看作對(duì)象,它們的起源就是元類(lèi)。
其實(shí),在 Python 中,使用 type 方法,我們可就以創(chuàng)建出一個(gè)類(lèi),type 方法的語(yǔ)法如下:
- type(class_name, (base_class, ...), {attr_key: attr_value, ...})
例如,像下面這樣,我們使用 type 方法創(chuàng)建 MyClass 類(lèi),并且讓它繼承 object:
- >>> A = type('MyClass', (object, ), {}) # type創(chuàng)建一個(gè)類(lèi),繼承object
- >>> A
- >>> A()
- <__main__.MyClass object at 0x10d905950>
我們還可以使用 type 創(chuàng)建一個(gè)包含屬性和方法的類(lèi):
- >>> def foo(self):
- ... return 'foo'
- ...
- >>> name = 'zhangsan'
- >>>
- # type 創(chuàng)建類(lèi)B 繼承object 包含 name 屬性和 foo 方法
- >>> B = type('MyClass', (object, ), {'name': name, 'foo': foo})
- >>> B.name # 打印 name 屬性
- 'zhangsan'
- >>> print B().foo() # 調(diào)用 foo 方法
- foo
通過(guò) type 方法創(chuàng)建的類(lèi),和我們自己定義一個(gè)類(lèi),在使用上沒(méi)有任何區(qū)別。
其實(shí),除了使用 type 方法創(chuàng)建一個(gè)類(lèi)之外,我們還可以使用類(lèi)屬性 __metaclass__ 創(chuàng)建一個(gè)類(lèi),這就是下面要講的「自定義元類(lèi)」。
自定義元類(lèi)
我們可以使用類(lèi)屬性 __metaclass__ 把一個(gè)類(lèi)的創(chuàng)建過(guò)程,轉(zhuǎn)交給其它地方,可以像下面這樣寫(xiě):
- class A(object):
- __metaclass__ = ... # 這個(gè)類(lèi)的創(chuàng)建轉(zhuǎn)交給其他地方
- pass
這個(gè)例子中,我們先定義了類(lèi) A,然后定義了一個(gè)類(lèi)屬性 __metaclass__,這個(gè)屬性表示創(chuàng)建類(lèi) A 的過(guò)程,轉(zhuǎn)交給其它地方處理。
那么,這個(gè)類(lèi)屬性 __metaclass__ 需要怎么寫(xiě)呢?
其實(shí),它可以是一個(gè)方法,也可以是一個(gè)類(lèi)。
用方法創(chuàng)建類(lèi)
如果類(lèi)屬性 __metaclass__ 賦值的是一個(gè)方法,那么創(chuàng)建類(lèi)的過(guò)程,就交給了一個(gè)方法來(lái)執(zhí)行。
- def create_class(name, bases, attr):
- print 'create class by method...'
- # 什么事都沒(méi)做 直接用type創(chuàng)建了一個(gè)類(lèi)
- return type(name, bases, attr)
- class A(object):
- # 創(chuàng)建類(lèi)的過(guò)程交給了一個(gè)方法
- __metaclass__ = create_class
- # Output:
- # create class by method ...
我們定義了 create_class 方法,然后賦值給 __metaclass__,那么類(lèi) A 被創(chuàng)建時(shí),就會(huì)調(diào)用 create_class 方法。
而 create_class 方法中的邏輯,就是我們上面所講到的,使用 type 方法創(chuàng)建出一個(gè)類(lèi),然后返回。
用類(lèi)創(chuàng)建類(lèi)
明白了用方法創(chuàng)建類(lèi)之后,我們來(lái)看一下用類(lèi)來(lái)創(chuàng)建另一個(gè)類(lèi)。
- class B(type):
- # 必須定義 __new__ 方法 返回一個(gè)類(lèi)
- def __new__(cls, name, bases, attr):
- print 'create class by B ...'
- return type(name, bases, attr)
- class A(object):
- # 創(chuàng)建類(lèi)的過(guò)程交給了B
- __metaclass__ = B
- # Output:
- # create class by B ...
在這個(gè)例子中,我們定義了類(lèi) B,然后把它賦值給了 A 的類(lèi)變量 __metaclass__,這就表示創(chuàng)建 A 的過(guò)程,交給了類(lèi) B。
B 在定義時(shí),首先繼承了 type,然后定義了 __new__ 方法,最后調(diào)用 type 方法返回了一個(gè)類(lèi),這樣當(dāng)創(chuàng)建類(lèi) A 時(shí),會(huì)自動(dòng)調(diào)用類(lèi) B 的 __new__ 方法,然后得到一個(gè)類(lèi)實(shí)例。
創(chuàng)建類(lèi)的過(guò)程
好了,上面我們演示了通過(guò)元類(lèi)創(chuàng)建一個(gè)類(lèi)的兩種方式,分別是通過(guò)方法創(chuàng)建和通過(guò)類(lèi)創(chuàng)建。
其實(shí)創(chuàng)建一個(gè)類(lèi)的完整流程如下:
- 檢查類(lèi)中是否有 __metaclass__ 屬性,如果有,則調(diào)用 __metaclass__ 指定的方法或類(lèi)創(chuàng)建
- 如果類(lèi)中沒(méi)有 __metaclass__ 屬性,那么會(huì)繼續(xù)在父類(lèi)中尋找
- 如果任何父類(lèi)中都沒(méi)有,那么就用 type 創(chuàng)建這個(gè)類(lèi)
也就是說(shuō),如果我們沒(méi)有指定 __metaclass__,那么所有的類(lèi)都是默認(rèn)由 type 創(chuàng)建,這種情況是我們大多數(shù)定義類(lèi)時(shí)的流程。
如果類(lèi)中指定了 __metaclass__,那么這個(gè)類(lèi)的創(chuàng)建就會(huì)交給外部來(lái)做,外部可以定義具體的創(chuàng)建邏輯。
哪種創(chuàng)建類(lèi)的方式更好?
雖然有兩種方式可以創(chuàng)建類(lèi),那么哪種方式更好呢?
一般我們建議使用類(lèi)的方式創(chuàng)建,它的優(yōu)點(diǎn)如下:
- 使用類(lèi)更能清楚地表達(dá)意圖
- 使用類(lèi)更加 OOP,因?yàn)轭?lèi)可以繼承其他類(lèi),而且可以更友好地使用面向?qū)ο筇匦?/li>
- 使用類(lèi)可以更好地組織代碼結(jié)構(gòu)
另外,使用類(lèi)創(chuàng)建一個(gè)類(lèi)時(shí),這里有一個(gè)優(yōu)化點(diǎn):在 __new__ 方法中不建議直接調(diào)用 type 方法,而是建議調(diào)用 super 的 __new__ 來(lái)創(chuàng)建類(lèi),執(zhí)行結(jié)果與 type 方法是一樣的:
- class B(type):
- def __new__(cls, name, bases, attr):
- # 使用 super.__new__ 創(chuàng)建類(lèi)
- return super(B, cls).__new__(cls, name, bases, attr)
創(chuàng)建類(lèi)時(shí)自定義行為
前面我們用元類(lèi)創(chuàng)建一個(gè)類(lèi)時(shí),它的功能非常簡(jiǎn)單。現(xiàn)在我們來(lái)看一下,使用元類(lèi)創(chuàng)建類(lèi)時(shí),如何定義一些自己的邏輯,然后改變類(lèi)的屬性或行為。
我們看下面這個(gè)例子:
- # coding: utf8
- class Meta(type):
- def __new__(cls, name, bases, attr):
- # 通過(guò) Meta 創(chuàng)建的類(lèi) 屬性會(huì)都變成大寫(xiě)
- for k, v in attr.items():
- if not k.startswith('__'):
- attr[k] = v.upper()
- else:
- attr[k] = v
- return type(name, bases, attr)
- class A(object):
- # 通過(guò) Meta 創(chuàng)建類(lèi)
- __metaclass__ = Meta
- name = 'zhangsan'
- class B(object):
- # 通過(guò) Meta 創(chuàng)建類(lèi)
- __metaclass__ = Meta
- name = 'lisi'
- # 打印類(lèi)屬性 會(huì)自動(dòng)變成大寫(xiě)
- print A.name # ZHANGSAN
- print B.name # LISI
在這個(gè)例子中,我們定義了一個(gè)元類(lèi) Meta,然后在定義類(lèi) A 和 B 時(shí),把創(chuàng)建類(lèi)的過(guò)程交給了 Meta,在 Meta 類(lèi)中,我們可以拿到 A 和 B 的屬性,然后把它們的屬性都轉(zhuǎn)換成了大寫(xiě)。
所以當(dāng)我們打印 A 和 B 的屬性時(shí),雖然定義的變量是小寫(xiě)的,但輸出結(jié)果都變成了大寫(xiě),這就是元類(lèi)發(fā)揮的作用。
使用場(chǎng)景
了解了元類(lèi)的實(shí)現(xiàn)原理,那么元類(lèi)都會(huì)用在哪些場(chǎng)景呢?
我們?cè)陂_(kāi)發(fā)中其實(shí)用的并不多,元類(lèi)的使用,經(jīng)常會(huì)出現(xiàn)在一些框架中,例如Django ORM、peewee,下面是使用 Django ORM 定義一個(gè)數(shù)據(jù)表映射類(lèi)的代碼:
- class Person(models.Model):
- # 注意: name 和 age 是類(lèi)屬性
- name = models.CharField(max_length=30)
- age = models.IntegerField()
- person = Person(name='zhangsan', age=20)
- print person.name # zhangsan
- print person.age # 20
仔細(xì)看在這段代碼中,我們定義了一個(gè) Person 類(lèi),然后在類(lèi)中定義了類(lèi)屬性 name 和 age,它們的類(lèi)型分別是 CharField 和 IntegerField,之后我們初始化 Person 實(shí)例,然后通過(guò)實(shí)例獲取 name 和 age 屬性,輸出的卻是 str 和 int,而不再是 CharField 和 IntegerField。
能做到這樣的秘密就在于,Person 類(lèi)在創(chuàng)建時(shí),它的邏輯交給了另一個(gè)類(lèi),這個(gè)類(lèi)針對(duì)類(lèi)屬性進(jìn)行了轉(zhuǎn)換,最終變成對(duì)象與數(shù)據(jù)表的映射,通過(guò)轉(zhuǎn)換映射,我們就可以通過(guò)實(shí)例屬性的方式,友好地訪問(wèn)表中對(duì)應(yīng)的字段值了。
總結(jié)
總結(jié)一下,這篇文章我們講了元類(lèi)的實(shí)現(xiàn)原理,了解到元類(lèi)是創(chuàng)建所有類(lèi)的根源,我們可以通過(guò) type 方法,或者在類(lèi)中定義 __metaclass__ 的方式,把創(chuàng)建類(lèi)的過(guò)程交給外部。
當(dāng)使用 __metaclass__ 創(chuàng)建類(lèi)時(shí),它可以是一個(gè)方法,也可以是一個(gè)類(lèi)。我們通常會(huì)使用類(lèi)的方式去實(shí)現(xiàn)一個(gè)元類(lèi),這樣做更方便我們組織代碼,實(shí)現(xiàn)面向?qū)ο蟆?/p>
在使用元類(lèi)創(chuàng)建一個(gè)類(lèi)時(shí),我們可以修改創(chuàng)建類(lèi)的細(xì)節(jié),例如對(duì)屬性做統(tǒng)一的轉(zhuǎn)換,或者增加新的方法等等,這對(duì)于我們開(kāi)發(fā)一個(gè)復(fù)雜功能的類(lèi)很友好,它可以把創(chuàng)建類(lèi)的細(xì)節(jié)屏蔽在元類(lèi)中,所以元類(lèi)常常用在優(yōu)秀的開(kāi)源框架中。、
分享文章:Python進(jìn)階——元類(lèi)是怎么創(chuàng)建一個(gè)類(lèi)的?
當(dāng)前路徑:http://m.5511xx.com/article/coccoco.html


咨詢
建站咨詢
