一爵赵、類也是對象、globals查看所有的全局對象引用
-
1.1泊脐、在大多數(shù)編程語言中空幻,類就是一組用來描述如何生成一個對象的代碼段。在Python中這一點(diǎn)仍然成立:
>>> class ObjectCreator(object): … pass … >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
但是容客,Python中的類還遠(yuǎn)不止如此秕铛。類同樣也是一種對象。是的缩挑,沒錯但两,就是對象。只要你使用關(guān)鍵字class供置,Python解釋器在執(zhí)行的時候就會創(chuàng)建一個對象谨湘。如下面的代碼段:
>>> class ObjectCreator(object): … pass …
-
1.2、將在內(nèi)存中創(chuàng)建一個對象,名字就是ObjectCreator紧阔。這個對象(類對象ObjectCreator)擁有創(chuàng)建對象(實(shí)例對象)的能力坊罢。但是,它的本質(zhì)仍然是一個對象擅耽,故你可以對它做如下的操作:
1活孩、你可以將它賦值給一個變量 2、你可以拷貝它 3乖仇、你可以為它增加屬性 4憾儒、你可以將它作為函數(shù)參數(shù)進(jìn)行傳遞
下面是示例:
>>> print(ObjectCreator) # 你可以打印一個類,因?yàn)樗鋵?shí)也是一個對象 <class '__main__.ObjectCreator'> >>> def echo(o): … print(o) … >>> echo(ObjectCreator) # 你可以將類做為參數(shù)傳給函數(shù) <class '__main__.ObjectCreator'> >>> print(hasattr(ObjectCreator, 'new_attribute')) Fasle >>> ObjectCreator.new_attribute = 'foo' # 你可以為類增加屬性 >>> print(hasattr(ObjectCreator, 'new_attribute')) True >>> print(ObjectCreator.new_attribute) foo >>> ObjectCreatorMirror = ObjectCreator # 你可以將類賦值給一個變量 >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c>
-
1.3乃沙、globals查看所有的全局對象引用
- 在終端控制臺打雍蕉帷:globals()
- 提示:當(dāng)定義一個函數(shù)崔涂、類、全局變量時始衅,其實(shí)就是創(chuàng)建一個“對象”冷蚂,然后再 globals獲取的的這個字典中添加一個名字,讓這個名字指向剛剛創(chuàng)建的對象空間而已
- 在終端控制臺打雍蕉帷:globals()
二汛闸、動態(tài)地創(chuàng)建類(元類創(chuàng)建類蝙茶,類創(chuàng)建實(shí)例對象)
-
2.1、因?yàn)?strong>類也是對象诸老,你可以在運(yùn)行時動態(tài)的創(chuàng)建它們隆夯,就像其他任何對象一樣。首先别伏,你可以在函數(shù)中創(chuàng)建類蹄衷,使用class關(guān)鍵字即可。
>>> def choose_class(name): … if name == 'foo': … class Foo(object): … pass … return Foo # 返回的是類厘肮,不是類的實(shí)例 … else: … class Bar(object): … pass … return Bar … >>> MyClass = choose_class('foo') >>> print(MyClass) # 函數(shù)返回的是類愧口,不是類的實(shí)例 <class '__main__'.Foo> >>> print(MyClass()) # 你可以通過這個類創(chuàng)建類實(shí)例,也就是對象 <__main__.Foo object at 0x89c6d4c>
- 但這還不夠動態(tài)类茂,因?yàn)槟闳匀恍枰约壕帉懻麄€類的代碼崇败。由于類也是對象异袄,所以它們必須是通過什么東西來生成的才對。
- 當(dāng)你使用class關(guān)鍵字時,Python解釋器自動創(chuàng)建這個對象痛黎。但就和Python中的大多數(shù)事情一樣,Python仍然提供給你手動處理的方法诅挑。
-
2.2返帕、還記得內(nèi)建函數(shù)type嗎?這個古老但強(qiáng)大的函數(shù)能夠讓你知道一個對象的類型是什么,就像這樣:
>>> print(type(1)) # 數(shù)值的類型 <type 'int'> >>> print(type("1")) # 字符串的類型 <type 'str'> >>> print(type(ObjectCreator())) # 實(shí)例對象的類型 <class '__main__.ObjectCreator'> >>> print(type(ObjectCreator)) # 類的類型 <type 'type'>
仔細(xì)觀察上面的運(yùn)行結(jié)果提揍,發(fā)現(xiàn)使用type對ObjectCreator查看類型是啤月,答案為type, 是不是有些驚訝劳跃。谎仲。∨俾兀看下面的
三
郑诺。
三、 使用type創(chuàng)建類(type還有一種完全不同的功能杉武,動態(tài)的創(chuàng)建類)
- 3.1辙诞、type可以接受一個類的描述作為參數(shù),然后返回一個類轻抱。(要知道飞涂,根據(jù)傳入?yún)?shù)的不同,同一個函數(shù)擁有兩種完全不同的用法是一件很傻的事情祈搜,但這在Python中是為了保持向后兼容性)
-
type可以如下這樣工作:3個參數(shù)较店,類名、元組容燕、字典
type(類名, 由父類名稱組成的元組(針對繼承的情況梁呈,可以為空),包含屬性的字典(名稱和值))
-
如下面的代碼:
In [2]: class Test: #定義了一個Test類 ...: pass ...: In [3]: Test() # 創(chuàng)建了一個Test類的實(shí)例對象 Out[3]: <__main__.Test at 0x10d3f8438>
-
可以手動像這樣創(chuàng)建:
In [5]: Test2 = type("Test2",(),{}) # 定了一個Test2類 In [6]: Test2() # 創(chuàng)建了一個Test2類的實(shí)例對象 Out[6]: <__main__.Test2 at 0x10c3c5828>
-
提示:我們使用 "Test2" 作為類名蘸秘,并且也可以把它當(dāng)做一個變量來作為類的引用官卡。類和變量是不同的,這里沒有任何理由把事情弄的復(fù)雜醋虏。即type函數(shù)中第1個實(shí)參寻咒,也可以叫做其他的名字,這個名字表示類的名字
In [23]: StudentClass = type('Student', (), {}) In [24]: print(StudentClass) <class '__main__.Student'>
-
四颈嚼、使用type創(chuàng)建帶有屬性的類
-
4.1仔涩、type 接受一個字典來為類定義屬性,故
>>> Foo = type('Foo', (), {'bar': True})
和下面的意思一樣
>>> class Foo(object): … bar = True
并且可以將Foo當(dāng)成一個普通的類一樣使用:
In [10]: Foo = type('Foo',(),{'bar':True}) In [11]: print(Foo) <class '__main__.Foo'> In [12]: print(Foo.bar) True In [13]: f = Foo() In [14]: print(f) <__main__.Foo object at 0x10c4308d0> In [15]: print(f.bar) True
-
4.2粘舟、當(dāng)然熔脂,你可以繼承上面的類,代碼如下:
>>> class FooChild(Foo): … pass
使用type就可以寫成如下:
>>> FooChild = type('FooChild', (Foo,), {}) >>> print(FooChild) <class '__main__.FooChild'> >>> print(FooChild.bar) # bar屬性是由Foo繼承而來 True
注意:
- 第一個參數(shù)是字符串柑肴,類名
- type的第2個參數(shù)霞揉,元組中是父類的名字,而不是字符串
- 添加的屬性是類屬性晰骑,并不是實(shí)例屬性
五适秩、使用type創(chuàng)建帶有方法的類
5.1绊序、為你的類增加方法,只需要定義一個有著恰當(dāng)簽名的函數(shù)并將其作為屬性賦值就可以了秽荞。
-
5.2骤公、添加實(shí)例方法,如下:
In [16]: def echo_bar(self): ...: print(self.bar) ...: In [17]: FooChild = type('FooChild',(Foo,),{'echo_bar':echo_bar}) In [18]: hasattr(Foo,'echo_bar') Out[18]: False In [19]: hasattr(FooChild, 'echo_bar') Out[19]: True In [20]: my_foo = FooChild() In [21]: my_foo.echo_bar() True
-
5.3扬跋、添加靜態(tài)方法阶捆,如下:
In [36]: @staticmethod ...: def test_static(): ...: print("static method ....") ...: In [37]: Foochild = type('Foochild', (Foo,), {"echo_bar": echo_bar, "test_static": test_static}) In [38]: fooclid = Foochild() In [39]: fooclid.test_static Out[39]: <function __main__.test_static> In [40]: fooclid.test_static() static method .... In [41]: fooclid.echo_bar() True
-
5.4、添加類方法
In [42]: @classmethod ...: def test_class(cls): ...: print(cls.bar) ...: In [43]: In [43]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "test_static": test_static, "test_class": test_class}) In [44]: In [44]: fooclid = Foochild() In [45]: fooclid.test_class() True
-
5.5钦听、你可以看到洒试,在Python中,類也是對象朴上,你可以動態(tài)的創(chuàng)建類垒棋。這就是當(dāng)你使用關(guān)鍵字class時Python在幕后做的事情,而這就是通過元類來實(shí)現(xiàn)的痪宰。較為完整的使用type創(chuàng)建類的方式叼架,如下:
class A(object): num = 100 def print_b(self): print(self.num) @staticmethod def print_static(): print("----haha-----") @classmethod def print_class(cls): print(cls.num) B = type("B", (A,), {"print_b": print_b, "print_static": print_static, "print_class": print_class}) b = B() b.print_b() b.print_static() b.print_class() # 結(jié)果 # 100 # ----haha----- # 100
六、 到底什么是元類
-
6.1衣撬、元類就是用來創(chuàng)建類的“東西”乖订。你創(chuàng)建類就是為了創(chuàng)建類的實(shí)例對象,不是嗎淮韭?但是我們已經(jīng)學(xué)習(xí)到了Python中的類也是對象。
元類就是用來創(chuàng)建這些類(對象)的贴届,元類就是類的類靠粪,你可以這樣理解為:MyClass = MetaClass() # 使用元類創(chuàng)建出一個對象,這個對象稱為“類” my_object = MyClass() # 使用“類”來創(chuàng)建出實(shí)例對象
前面你已經(jīng)看到了type可以讓你像這樣做:
MyClass = type('MyClass', (), {})
這是因?yàn)楹瘮?shù)type實(shí)際上是一個元類毫蚓。type就是Python在背后用來創(chuàng)建所有類的元類占键。現(xiàn)在你想知道那為什么type會全部采用小寫形式而不是Type呢?好吧元潘,我猜這是為了和str保持一致性畔乙,str是用來創(chuàng)建字符串對象的類,而int是用來創(chuàng)建整數(shù)對象的類翩概。type就是創(chuàng)建類對象的類牲距。你可以通過檢查class屬性來看到這一點(diǎn)。Python中所有的東西钥庇,注意牍鞠,我是指所有的東西——都是對象。這包括整數(shù)评姨、字符串难述、函數(shù)以及類。它們?nèi)慷际菍ο螅宜鼈兌际菑囊粋€類創(chuàng)建而來胁后,這個類就是type店读。
>>> age = 35 >>> age.__class__ <type 'int'> >>> >>> name = 'bob' >>> name.__class__ <type 'str'> >>> >>> def foo(): pass >>>foo.__class__ <type 'function'> >>> >>> class Bar(object): pass >>> b = Bar() >>> b.__class__ <class '__main__.Bar'> >>>
-
6.2、現(xiàn)在攀芯,對于任何一個
__class_
_的__class__
屬性又是什么呢屯断?>>> a.__class__.__class__ <type 'type'> >>> age.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
因此,元類就是創(chuàng)建類這種對象的東西敲才。type就是Python的內(nèi)建元類裹纳,當(dāng)然了,你也可以創(chuàng)建自己的元類紧武。
七剃氧、__metaclass__
屬性
-
7.1、你可以在定義一個類的時候?yàn)槠涮砑?code>__metaclass__屬性阻星。
class Foo(object): __metaclass__ = something… ...省略...
如果你這么做了朋鞍,Python就會用元類來創(chuàng)建類
Foo
。小心點(diǎn)妥箕,這里面有些技巧滥酥。你首先寫下class Foo(object)
,但是類Foo
還沒有在內(nèi)存中創(chuàng)建畦幢。Python會在類的定義中尋找__metaclass__
屬性坎吻,如果找到了,Python就會用它來創(chuàng)建類Foo
宇葱,如果沒有找到瘦真,就會用內(nèi)建的type來創(chuàng)建這個類。把下面這段話反復(fù)讀幾次黍瞧。當(dāng)你寫如下代碼時 :class Foo(Bar): pass
Python做了如下的操作:
- (1)诸尽、Foo中有metaclass這個屬性嗎?如果是印颤,Python會通過
__metaclass__
創(chuàng)建一個名字為Foo的類(對象) - (2)您机、如果Python沒有找到
__metaclass__
,它會繼續(xù)在Bar(父類)中尋找__metaclass__
屬性年局,并嘗試做和前面同樣的操作际看。 - (3)、如果Python在任何父類中都找不到
__metaclass__
矢否,它就會在模塊層次中去尋找__metaclass__
仿村,并嘗試做同樣的操作。 - (4)兴喂、如果還是找不到
__metaclass__
,Python就會用內(nèi)置的type來創(chuàng)建這個類對象蔼囊。 - 現(xiàn)在的問題就是焚志,你可以在metaclass中放置些什么代碼呢?答案就是:可以創(chuàng)建一個類的東西畏鼓。那么什么可以用來創(chuàng)建一個類呢酱酬?type,或者任何使用到type或者子類化type的東東都可以云矫。
- (1)诸尽、Foo中有metaclass這個屬性嗎?如果是印颤,Python會通過
八膳沽、元類應(yīng)用的補(bǔ)充 (元類的主要目的就是為了當(dāng)創(chuàng)建類時能夠自動地改變類。)
8.1让禀、假想一個很傻的例子挑社,你決定在你的模塊里所有的類的屬性都應(yīng)該是大寫形式。有好幾種方法可以辦到巡揍,但其中一種就是通過在模塊級別設(shè)定
__metaclass__
痛阻。采用這種方法,這個模塊中的所有類都會通過這個元類來創(chuàng)建腮敌,我們只需要告訴元類把所有的屬性都改成大寫形式就萬事大吉了阱当。-
8.2、幸運(yùn)的是糜工,
__metaclass__
實(shí)際上可以被任意調(diào)用弊添,它并不需要是一個正式的類。所以捌木,我們這里就先以一個簡單的函數(shù)作為例子開始油坝。-
python2中
#-*- coding:utf-8 -*- def upper_attr(class_name, class_parents, class_attr): # class_name 會保存類的名字 Foo # class_parents 會保存類的父類 object # class_attr 會以字典的方式保存所有的類屬性 # 遍歷屬性字典,把不是__開頭的屬性名字變?yōu)榇髮? new_attr = {} for name, value in class_attr.items(): if not name.startswith("__"): new_attr[name.upper()] = value # 調(diào)用type來創(chuàng)建一個類 return type(class_name, class_parents, new_attr) class Foo(object): __metaclass__ = upper_attr # 設(shè)置Foo類的元類為upper_attr bar = 'bip' print(hasattr(Foo, 'bar')) print(hasattr(Foo, 'BAR')) f = Foo() print(f.BAR)
-
python3中
#-*- coding:utf-8 -*- def upper_attr(class_name, class_parents, class_attr): #遍歷屬性字典刨裆,把不是__開頭的屬性名字變?yōu)榇髮? new_attr = {} for name,value in class_attr.items(): if not name.startswith("__"): new_attr[name.upper()] = value #調(diào)用type來創(chuàng)建一個類 return type(class_name, class_parents, new_attr) class Foo(object, metaclass=upper_attr): bar = 'bip' print(hasattr(Foo, 'bar')) print(hasattr(Foo, 'BAR')) f = Foo() print(f.BAR)
對比:python2與python3的主要區(qū)別是:
metaclass
放的位置不一樣
-
-
8.3澈圈、用
class
來當(dāng)做元類,如下:#coding=utf-8 class UpperAttrMetaClass(type): # __new__ 是在__init__之前被調(diào)用的特殊方法 # __new__是用來創(chuàng)建對象并返回之的方法 # 而__init__只是用來將傳入的參數(shù)初始化給對象 # 你很少用到__new__崔拥,除非你希望能夠控制對象的創(chuàng)建 # 這里极舔,創(chuàng)建的對象是類凤覆,我們希望能夠自定義它链瓦,所以我們這里改寫__new__ # 如果你希望的話,你也可以在__init__中做些事情 # 還有一些高級的用法會涉及到改寫__call__特殊方法盯桦,但是我們這里不用 def __new__(cls, class_name, class_parents, class_attr): # 遍歷屬性字典慈俯,把不是__開頭的屬性名字變?yōu)榇髮? new_attr = {} for name, value in class_attr.items(): if not name.startswith("__"): new_attr[name.upper()] = value # 方法1:通過'type'來做類對象的創(chuàng)建 return type(class_name, class_parents, new_attr) # 方法2:復(fù)用type.__new__方法 # 這就是基本的OOP編程,沒什么魔法 # return type.__new__(cls, class_name, class_parents, new_attr) # python3的用法 class Foo(object, metaclass=UpperAttrMetaClass): bar = 'bip' # python2的用法 # class Foo(object): # __metaclass__ = UpperAttrMetaClass # bar = 'bip' print(hasattr(Foo, 'bar')) # 輸出: False print(hasattr(Foo, 'BAR')) # 輸出:True f = Foo() print(f.BAR) # 輸出:'bip'
-
8.4拥峦、元類本身而言贴膘,它們其實(shí)是很簡單的:
- 攔截類的創(chuàng)建
- 修改類
- 返回修改之后的類
最后:究竟為什么要使用元類?
- 現(xiàn)在回到我們的大主題上來略号,究竟是為什么你會去使用這樣一種容易出錯且晦澀的特性刑峡?好吧洋闽,一般來說,你根本就用不上它:
- “元類就是深度的魔法突梦,99%的用戶應(yīng)該根本不必為此操心诫舅。如果你想搞清楚究竟是否需要用到元類,那么你就不需要它宫患。那些實(shí)際用到元類的人都非常清楚地知道他們需要做什么刊懈,而且根本不需要解釋為什么要用元類⊥尴校” —— Python界的領(lǐng)袖 Tim Peters