偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

理解python的metaclass(元類)

開發(fā) 后端
這篇博客是我在stack overflow上看了一個(gè)提問(wèn)回復(fù)后寫的,例子基本用的都是e-satis本人的例子,語(yǔ)言組織也基本按照翻譯來(lái)。但我并不是一個(gè)翻譯者,并不會(huì)嚴(yán)格遵守每行每句的翻譯;有時(shí)候我會(huì)將表述換個(gè)順序,省略一些我認(rèn)為無(wú)關(guān)緊要的話,以便讀者更好理解。

前言

這篇博客是我在stack overflow上看了一個(gè)提問(wèn)回復(fù)后寫的,例子基本用的都是e-satis本人的例子,語(yǔ)言組織也基本按照翻譯來(lái)。

但我并不是一個(gè)翻譯者,并不會(huì)嚴(yán)格遵守每行每句的翻譯;有時(shí)候我會(huì)將表述換個(gè)順序,省略一些我認(rèn)為無(wú)關(guān)緊要的話,以便讀者更好理解。

所以,如果你不喜歡我的語(yǔ)言表述,或者想要看英文原文,可以點(diǎn)擊此鏈接去查看原回復(fù)。

類也是對(duì)象

在理解metaclass之前,我們先要掌握python中的類(class)是什么。

python中類的概念,是借鑒自smalltalk語(yǔ)言。

在大部分語(yǔ)言中,類指的是"描述如何產(chǎn)生一個(gè)對(duì)象(object)"的一段代碼,這對(duì)于python也是如此。

  1. >>> class ObjectCreator(object): 
  2. ...       pass 
  3. ... 
  4. >>> my_object = ObjectCreator() 
  5. >>> print(my_object) 
  6. <__main__.ObjectCreator object at 0x8974f2c>  

但是,在python中,類遠(yuǎn)不止如此,類同時(shí)也是對(duì)象。當(dāng)你遇到關(guān)鍵詞class的時(shí)候,python就會(huì)自動(dòng)執(zhí)行產(chǎn)生一個(gè)對(duì)象。下面的代碼段中: 

  1. >>> class ObjectCreator(object): 
  2. ...       pass 
  3. ...  

python在內(nèi)存中產(chǎn)生了一個(gè)名叫做"ObjectCreator"的對(duì)象。這個(gè)對(duì)象(類)自身?yè)碛挟a(chǎn)生對(duì)象(實(shí)例instance)的能力。 這就是為什么稱呼這東西(后面遇到容易混淆的地方,我們稱之為:類對(duì)象)也是類的原因。同時(shí),它也是一個(gè)對(duì)象,因此你可以對(duì)它做如下操作:

  • 賦值給變量
  • 復(fù)制它
  • 為它增加屬性(attribute)
  • 作為參數(shù)傳值給函數(shù)

舉例:

  1. >>> print(ObjectCreator) # 你可以打印一個(gè)類,因?yàn)樗瑫r(shí)也是對(duì)象 
  2. <class '__main__.ObjectCreator'
  3.  
  4. >>> def echo(o): 
  5. ...     print(o) 
  6. ... 
  7. >>> echo(ObjectCreator) # 作為參數(shù)傳值給函數(shù) 
  8. <class '__main__.ObjectCreator'
  9.  
  10. >>> print(hasattr(ObjectCreator, 'new_attribute')) 
  11. False 
  12. >>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class 
  13. >>> print(hasattr(ObjectCreator, 'new_attribute')) 
  14. True 
  15. >>> print(ObjectCreator.new_attribute) 
  16. foo 
  17.  
  18. >>> ObjectCreatorMirror = ObjectCreator # 將類賦值給變量 
  19. >>> print(ObjectCreatorMirror.new_attribute) 
  20. foo 
  21. >>> print(ObjectCreatorMirror()) 
  22. <__main__.ObjectCreator object at 0x8997b4c>  

動(dòng)態(tài)創(chuàng)建類

既然類也是對(duì)象,那么我們就可以在運(yùn)行的時(shí)候創(chuàng)建它,跟創(chuàng)建對(duì)象一樣自然。

首先,我們使用class關(guān)鍵字定義一個(gè)產(chǎn)生類的函數(shù): 

  1. >>> def choose_class(name): 
  2. ...     if name == 'foo'
  3. ...         class Foo(object): 
  4. ...             pass 
  5. ...         return Foo # return the class, not an instance 
  6. ...     else
  7. ...         class Bar(object): 
  8. ...             pass 
  9. ...         return Bar 
  10. ... 
  11. >>> MyClass = choose_class('foo'
  12. >>> print(MyClass) # the function returns a class, not an instance 
  13. <class '__main__.Foo'
  14. >>> print(MyClass()) # you can create an object from this class 
  15. <__main__.Foo object at 0x89c6d4c>  

這很容易理解吧。但是,這并不那么動(dòng)態(tài)啊。我們還是需要自己來(lái)寫這個(gè)類的代碼。

既然類也是對(duì)象,那就應(yīng)該有用來(lái)產(chǎn)生它的東西。這東西就是type。

先來(lái)說(shuō)說(shuō)你所認(rèn)識(shí)的type。這個(gè)古老而好用的函數(shù),可以讓我們知道一個(gè)對(duì)象的類型是什么。 

  1. >>> print(type(1)) 
  2. <type 'int'
  3. >>> print(type("1")) 
  4. <type 'str'
  5. >>> print(type(ObjectCreator)) 
  6. <type 'type'
  7. >>> print(type(ObjectCreator())) 
  8. <class '__main__.ObjectCreator' 

實(shí)際上,type還有一個(gè)完全不同的功能,它可以在運(yùn)行時(shí)產(chǎn)生類。type可以傳入一些參數(shù),然后返回一個(gè)類。(好吧,必須承認(rèn),根據(jù)不同的傳入?yún)?shù),一個(gè)相同的函數(shù)type居然會(huì)有兩個(gè)完全不同的作用,這很愚蠢。不過(guò)python這樣做是為了保持向后兼容性。)

下面舉例type創(chuàng)建類的用法。首先,對(duì)于類一般是這么定義的:

  1. >>> class MyShinyClass(object): 
  2. ...       pass  

在下面,MyShinyClass也可以這樣子被創(chuàng)建出來(lái),并且跟上面的創(chuàng)建方法有一樣的表現(xiàn):

  1. >>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object 
  2. >>> print(MyShinyClass) 
  3. <class '__main__.MyShinyClass'
  4. >>> print(MyShinyClass()) # create an instance with the class 
  5. <__main__.MyShinyClass object at 0x8997cec>  

type創(chuàng)建類需要傳入三個(gè)參數(shù),分別為:

  • 類的名字
  • 一組"類的父類"的元組(tuple) (這個(gè)會(huì)實(shí)現(xiàn)繼承,也可以為空)
  • 字典 (類的屬性名與值,key-value的形式,不傳相當(dāng)于為空,如一般寫法中的pass).

下面來(lái)點(diǎn)復(fù)雜的,來(lái)更好的理解type傳入的三個(gè)參數(shù):

  1. class Foo(object): 
  2.     bar = True 
  3.  
  4.     def echo_bar(self): 
  5.         print(self.bar)  

等價(jià)于:

  1. def echo_bar(self): 
  2.     print(self.bar) 
  3.  
  4. Foo = type('Foo', (), {'bar':True'echo_bar': echo_bar})  

想要看點(diǎn)有繼承關(guān)系的類的實(shí)現(xiàn),來(lái):

  1. class FooChild(Foo): 
  2.     pass 

 等價(jià)于:

  1. FooChild = type('FooChild', (Foo, ), {}) 

回顧一下我們學(xué)到哪了: 在python中,類就是對(duì)象,并且你可以在運(yùn)行的時(shí)候動(dòng)態(tài)創(chuàng)建類.

那到底什么是metaclass(元類)

metaclass 就是創(chuàng)建類的那家伙。(事實(shí)上,type就是一個(gè)metaclass)

我們知道,我們定義了class就是為了能夠創(chuàng)建object的,沒(méi)錯(cuò)吧?

我們也學(xué)習(xí)了,python中類也是對(duì)象。

那么,metaclass就是用來(lái)創(chuàng)造“類對(duì)象”的類.它是“類對(duì)象”的“類”。

可以這樣子來(lái)理解:

  

  1. MyClass = MetaClass() 
  2. MyObject = MyClass()  

也可以用我們上面學(xué)到的type來(lái)表示:

  1. MyClass = type('MyClass', (), {}) 

說(shuō)白了,函數(shù)type就是一個(gè)特殊的metaclass.

python在背后使用type創(chuàng)造了所有的類。type是所有類的metaclass.

我們可以使用__class__屬性來(lái)驗(yàn)證這個(gè)說(shuō)法.

在python中,一切皆為對(duì)象:整數(shù)、字符串、函數(shù)、類.所有這些對(duì)象,都是通過(guò)類來(lái)創(chuàng)造的.

  1. >>> age = 35 
  2. >>> age.__class__ 
  3. <type 'int'
  4.  
  5. >>> name = 'bob' 
  6. >>> name.__class__ 
  7. <type 'str'
  8.  
  9. >>> def foo(): pass 
  10. >>> foo.__class__ 
  11. <type 'function'
  12.  
  13. >>> class Bar(object): pass 
  14. >>> b = Bar() 
  15. >>> b.__class__ 
  16. <class '__main__.Bar' 

那么,__class__的__class__又是什么呢? 

  1. >>> age.__class__.__class__ 
  2. <type 'type'
  3. >>> name.__class__.__class__ 
  4. <type 'type'
  5. >>> foo.__class__.__class__ 
  6. <type 'type'
  7. >>> b.__class__.__class__ 
  8. <type 'type' 

metaclass就是創(chuàng)造類對(duì)象的工具.如果你喜歡,你也可以稱之為"類的工廠".

type是python內(nèi)置的metaclass。不過(guò),你也可以編寫自己的metaclass.

__metaclass__ 屬性

我們可以在一個(gè)類中加入 __metaclass__ 屬性.

  1. class Foo(object): 
  2.     __metaclass__ = something... 
  3.     [...]  

當(dāng)你這么做了,python就會(huì)使用metaclass來(lái)創(chuàng)造類:Foo。

注意啦,這里有些技巧的。

當(dāng)你寫下class Foo(object)的時(shí)候,類對(duì)象Foo還沒(méi)有在內(nèi)存中生成。

python會(huì)在類定義中尋找__metaclass__ 。如果找到了,python就會(huì)使用這個(gè)__metaclass__ 來(lái)創(chuàng)造類對(duì)象: Foo。如果沒(méi)找到,python就使用type來(lái)創(chuàng)造Foo。

請(qǐng)把下面的幾段話重復(fù)幾遍:

當(dāng)你寫如下代碼的時(shí)候:

  1. class Foo(Bar): 
  2.  
  3. pass  

python做了以下事情:

Foo中有__metaclass__這個(gè)屬性嗎?

如果有,python會(huì)在內(nèi)存中通過(guò)__metaclass__創(chuàng)建一個(gè)名字為Foo的類對(duì)象。

如果python沒(méi)有在Foo中找到__metaclass__,它會(huì)繼續(xù)在Bar(父類)中尋找__metaclass__,并嘗試做和前面同樣的操作。

如果python由下往上遍歷父類也都沒(méi)有找不到__metaclass__,它就會(huì)在模塊(module)中去尋找__metaclass__,并嘗試做同樣的操作。

如果還是沒(méi)有找不到__metaclass__, python才會(huì)用內(nèi)置的type(這也是一個(gè)metaclass)來(lái)創(chuàng)建這個(gè)類對(duì)象。

現(xiàn)在問(wèn)題來(lái)了,我們要怎么用代碼來(lái)實(shí)現(xiàn)__metaclass__呢? 寫一些可以用來(lái)產(chǎn)生類(class)的東西就行。

那什么可以產(chǎn)生類?無(wú)疑就是type,或者type的任何子類,或者任何使用到type的東西都行.

自定義metaclass

使用metaclass的主要目的,是為了能夠在創(chuàng)建類的時(shí)候,自動(dòng)地修改類。

一個(gè)很傻的需求,我們決定要將該模塊中的所有類的屬性,改為大寫。

有幾種方法可以做到,這里使用__metaclass__來(lái)實(shí)現(xiàn).

在模塊的層次定義metaclass,模塊中的所有類都會(huì)使用它來(lái)創(chuàng)造類。我們只需要告訴metaclass,將所有的屬性轉(zhuǎn)化為大寫。

  1. # type也是一個(gè)類,我們可以繼承它. 
  2. class UpperAttrMetaclass(type): 
  3.     # __new__ 是在__init__之前被調(diào)用的特殊方法 
  4.     # __new__是用來(lái)創(chuàng)建對(duì)象并返回這個(gè)對(duì)象 
  5.     # 而__init__只是將傳入的參數(shù)初始化給對(duì)象 
  6.     # 實(shí)際中,你很少會(huì)用到__new__,除非你希望能夠控制對(duì)象的創(chuàng)建 
  7.     # 在這里,類是我們要?jiǎng)?chuàng)建的對(duì)象,我們希望能夠自定義它,所以我們改寫了__new__ 
  8.     # 如果你希望的話,你也可以在__init__中做些事情 
  9.     # 還有一些高級(jí)的用法會(huì)涉及到改寫__call__,但這里我們就先不這樣. 
  10.  
  11.     def __new__(upperattr_metaclass, future_class_name, 
  12.                 future_class_parents, future_class_attr): 
  13.  
  14.         uppercase_attr = {} 
  15.         for name, val in future_class_attr.items(): 
  16.             if not name.startswith('__'): 
  17.                 uppercase_attr[name.upper()] = val 
  18.             else
  19.                 uppercase_attr[name] = val 
  20.         return type(future_class_name, future_class_parents, uppercase_attr)  

這里的方式其實(shí)不是OOP(面向?qū)ο缶幊?.因?yàn)槲覀冎苯诱{(diào)用了type,而不是改寫父類的__type__方法.

所以我們也可以這樣子處理:

  1. class UpperAttrMetaclass(type): 
  2.  
  3.     def __new__(upperattr_metaclass, future_class_name, 
  4.                 future_class_parents, future_class_attr): 
  5.  
  6.         uppercase_attr = {} 
  7.         for name, val in future_class_attr.items(): 
  8.             if not name.startswith('__'): 
  9.                 uppercase_attr[name.upper()] = val 
  10.             else
  11.                 uppercase_attr[name] = val 
  12.         return type.__new__(upperattr_metaclass, future_class_name, 
  13.                             future_class_parents, uppercase_attr)  

這樣子看,我們只是復(fù)用了 type.__new__方法,這就是我們熟悉的基本的OOP編程,沒(méi)什么魔法可言.

你可能注意到,__new__方法相比于

  1. type(future_class_name, future_class_parents, future_class_attr) 

多了一個(gè)參數(shù): upperattr_metaclass, 請(qǐng)別在意,這沒(méi)什么特別的: __new__總是將"它要定義的類"作為***個(gè)參數(shù)。

這就好比是 self 在類的一般方法(method)中一樣,也是被作為***個(gè)參數(shù)傳入。

當(dāng)然啦,這里的名字的確是我起的太長(zhǎng)了。就像self一樣,所有的參數(shù)都有它們傳統(tǒng)的名稱。因此,在實(shí)際的代碼中,一個(gè)metaclass應(yīng)該是寫成下面樣子的:

(我們同時(shí)使用常見的super來(lái)讓代碼更清晰)

  1. class UpperAttrMetaclass(type): 
  2.  
  3.     def __new__(cls, clsname, bases, attrs): 
  4.         uppercase_attr = {} 
  5.         for name, val in attrs.items(): 
  6.             if not name.startswith('__'): 
  7.                 uppercase_attr[name.upper()] = val 
  8.             else
  9.                 uppercase_attr[name] = val 
  10.         return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, attrs)  

使用了 metaclass 的代碼是比較復(fù)雜,但我們使用它的原因并不是為了復(fù)雜, 而是因?yàn)槲覀兺ǔ?huì)使用 metaclass 去做一些晦澀的事情,比如, 依賴于自省,控制繼承等等。

確實(shí),用 metaclass 來(lái)搞些“黑魔法”是特別有用的,因而會(huì)復(fù)雜化代碼。

但就metaclass本身而言,它們其實(shí)是很簡(jiǎn)單的:中斷類的默認(rèn)創(chuàng)建、修改類、***返回修改后的類.

到底為什么要使用metaclass

現(xiàn)在我們面臨一個(gè)問(wèn)題: 為什么要使用metaclass? 它容易出錯(cuò)且晦澀難懂.

好吧,一般來(lái)說(shuō),我們根本就用不上它, 99%的用戶應(yīng)該根本不必為此操心。

實(shí)際用到metaclass的人,很清楚他們到底需要做什么,根本不用解釋為什么要用.

metaclass 的一個(gè)主要用途就是構(gòu)建API。Django(一個(gè)python實(shí)現(xiàn)的web框架)的ORM 就是一個(gè)例子。

用Django先定義了以下Model:

  1. class Person(models.Model): 
  2.     name = models.CharField(max_length=30) 
  3.     age = models.IntegerField()  

然后執(zhí)行下面代碼:

  1. guy = Person.objects.get(name='bob'
  2.  
  3. print guy.age # result is 35  

這里打印的輸出并不是IntegerField,而是一個(gè)int,int是從數(shù)據(jù)庫(kù)中獲取的.

這是因?yàn)?models.Model 使用 __metaclass__來(lái)實(shí)現(xiàn)了復(fù)雜的數(shù)據(jù)庫(kù)查詢。但對(duì)于你看來(lái),這就是簡(jiǎn)單的API而已,不用關(guān)心背后的復(fù)雜工作。

結(jié)語(yǔ)

復(fù)習(xí)一下,我們知道了,類是能夠創(chuàng)造對(duì)象實(shí)例的對(duì)象,同時(shí)也是metaclass的對(duì)象實(shí)例(因?yàn)閙etaclass創(chuàng)造了它們).

在python中,一切皆為對(duì)象。它們要么是類的實(shí)例,要么是metaclass的實(shí)例, 除了type。

type是它自身的metaclass。至于是怎么實(shí)現(xiàn)的,總之純python語(yǔ)言是不可能實(shí)現(xiàn)的,這需要在實(shí)現(xiàn)層面上耍一些小手段才能做到的。

metaclass用起來(lái)比較復(fù)雜, 如果需要對(duì)非常簡(jiǎn)單的類進(jìn)行修改, 你可能不會(huì)使用它。有以下兩個(gè)技術(shù)可以供你選擇:

責(zé)任編輯:龐桂玉 來(lái)源: segmentfault
相關(guān)推薦

2016-09-06 19:32:11

PythonWeb

2011-08-08 09:22:10

Python

2009-12-18 13:34:09

Ruby metacl

2022-05-20 12:40:23

PythonMetaclass

2024-07-30 11:29:09

2023-12-07 09:07:58

2021-03-22 10:20:04

Python元類代碼

2021-11-29 05:53:54

元宇宙虛擬VR

2015-08-06 15:13:49

runtimeIOS開發(fā)

2024-10-21 08:08:56

2024-05-20 09:26:42

Python裝飾器函數(shù)

2014-03-12 10:19:54

iOS對(duì)象

2024-12-12 08:05:14

元類Python控制類

2024-01-29 16:47:44

函數(shù)封裝開發(fā)

2011-07-15 13:49:30

C++友元函數(shù)友元類

2021-09-15 09:12:56

Python元編程元數(shù)據(jù)

2023-12-16 13:21:00

Python元類ORM

2025-06-05 05:51:33

2009-04-27 09:41:01

C#WPFTemplate

2018-09-03 10:35:15

編程語(yǔ)言PythonDataclasses
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)