摘要:原鏈接中的元類是什么類也是對(duì)象在理解元類之前,需要掌握中類概念。事實(shí)上,是中用于創(chuàng)建所有類的元類。類本身是元類的對(duì)象在中,除了,一切皆對(duì)象,一切都是類或者元類的對(duì)象。事實(shí)上是自己的元類,
學(xué)習(xí)契機(jī)
項(xiàng)目中使用Elasticsearch(ES)存儲(chǔ)海量業(yè)務(wù)數(shù)據(jù),基于ES向外提供的API進(jìn)一層封裝,按需處理原始數(shù)據(jù)提供更精確、更多樣化的結(jié)果。在研究這一層的代碼時(shí)接觸到@six.add_metaclass(abc.ABCMeta),故而學(xué)習(xí)一下Python的元類。不過,雖然@six.add_metaclass(abc.ABCMeta)實(shí)現(xiàn)上與元類有關(guān),但實(shí)際應(yīng)用只需要調(diào)用其接口,并不需要接觸后幕后的元類操作。
翻譯這篇答案是為了方便自己記憶理解,其實(shí)原文中一些地方我自己不是很明白,所以這個(gè)翻譯會(huì)根據(jù)自己理解的程度持續(xù)更新。
stackoverflow-What are metaclasses in Python?
Python中的元類是什么 類也是對(duì)象在理解元類之前,需要掌握Python中類概念。Python的類概念稍有奇特,其借鑒于Smalltalk。
在大部分語(yǔ)言中,類用于描述如何生成一個(gè)對(duì)象,在Python中也是如此:
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
但是,在Python中類的意義更多,類同時(shí)也是對(duì)象。當(dāng)你使用關(guān)鍵字class時(shí),Python解釋器執(zhí)行代碼時(shí)會(huì)生成一個(gè)對(duì)象。以下代碼會(huì)在內(nèi)存中創(chuàng)建一個(gè)名為“ObjectCreator”的對(duì)象:
>>> class ObjectCreator(object): ... pass ...
這個(gè)對(duì)象(類)自身可以創(chuàng)建對(duì)象(實(shí)例),這是為什么它是類的原因。
不過它仍然是一個(gè)對(duì)象,你可以:
可以將它賦值給變量
可以復(fù)制
可以添加屬性 TODO 添加屬性只是對(duì)象的特性?
可以將其當(dāng)作函數(shù)參數(shù)傳遞
舉例:
>>> print(ObjectCreator) # you can print a class because it"s an object動(dòng)態(tài)創(chuàng)建類>>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # you can pass a class as a parameter >>> print(hasattr(ObjectCreator, "new_attribute")) False >>> ObjectCreator.new_attribute = "foo" # you can add attributes to a class >>> print(hasattr(ObjectCreator, "new_attribute")) True >>> print(ObjectCreator.new_attribute) foo >>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable >>> print(ObjectCreatorMirror.new_attribute) foo >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c>
既然類也是對(duì)象,那就可像其他對(duì)象那樣,動(dòng)態(tài)創(chuàng)建類。
首先,可以使用關(guān)鍵字class,在函數(shù)中創(chuàng)建類:
>>> def choose_class(name): ... if name == "foo": ... class Foo(object): ... pass ... return Foo # return the class, not an instance ... else: ... class Bar(object): ... pass ... return Bar ... >>> MyClass = choose_class("foo") >>> print(MyClass) # the function returns a class, not an instance>>> print(MyClass()) # you can create an object from this class <__main__.Foo object at 0x89c6d4c>
但是這還不夠動(dòng)態(tài),因?yàn)檫€是需要自己編寫整個(gè)類的代碼。所以,想一想,這些類既然是對(duì)象,就必然是某種東西生成的。當(dāng)使用class關(guān)鍵字時(shí),Python自動(dòng)創(chuàng)建了類這個(gè)對(duì)象,但是像Python中大部分事情一樣,Python中也可以手動(dòng)創(chuàng)建類。
還記type方法嗎?一個(gè)可以讓你知道一個(gè)對(duì)象是什么類型的方法:
>>> print(type(1))>>> print(type("1")) >>> print(type(ObjectCreator)) >>> print(type(ObjectCreator()))
除此之外,type還有一個(gè)完全不同的功能:動(dòng)態(tài)創(chuàng)建類。將類的描述作為參數(shù)傳遞給type,會(huì)返回一個(gè)類。(我知道,同一個(gè)函數(shù)根據(jù)傳參的不同而展示出兩種完全不同的功能很不合理,不過這是為了Python的向后兼容。)
type如何創(chuàng)建類:
type(類名, 父類元祖 (可為空), 包含鍵值對(duì)屬性的字典)
舉例:
>>> class MyShinyClass(object): ... pass
上面這個(gè)MyShinyClass類可以用以下方法手動(dòng)創(chuàng)建:
>>> MyShinyClass = type("MyShinyClass", (), {}) # 返回一個(gè)類 >>> print(MyShinyClass)>>> print(MyShinyClass()) # 創(chuàng)建一個(gè)類對(duì)象 <__main__.MyShinyClass object at 0x8997cec>
應(yīng)該注意到了,使用"MyShinyClass"作為類名,也將其作為一個(gè)變量名,賦值為類的引用。類名和變量名是可以不同的,但是沒必要把事情搞復(fù)雜。
type可以接受一個(gè)定義類屬性的字典作為參數(shù):
>>> class Foo(object): ... bar = True
以上定義等同于:
>>> Foo = type("Foo", (), {"bar":True})
使用起來(lái)跟一個(gè)普通類一樣:
>>> print(Foo)>>> print(Foo.bar) True >>> f = Foo() >>> print(f) <__main__.Foo object at 0x8a9b84c> >>> print(f.bar) # 利用實(shí)例打印類屬性 True
當(dāng)然,也可以作為基類,給其他類繼承:
>>> class FooChild(Foo): ... pass
以上代碼等同于:
>>> FooChild = type("FooChild", (Foo,), {}) >>> print(FooChild)>>> print(FooChild.bar) # bar屬性繼承自類Foo True
你肯定還想為類添加方法。只需要定義一個(gè)名稱合理的函數(shù),并將這個(gè)函數(shù)名作為屬性傳遞給type就行:
>>> def echo_bar(self): ... print(self.bar) ... >>> FooChild = type("FooChild", (Foo,), {"echo_bar": echo_bar}) >>> hasattr(Foo, "echo_bar") False >>> hasattr(FooChild, "echo_bar") True >>> my_foo = FooChild() >>> my_foo.echo_bar() True
在動(dòng)態(tài)創(chuàng)建一個(gè)類之后,為這個(gè)類添加更多的方法,就像為一個(gè)正常創(chuàng)建的類添加方法一樣:
>>> def echo_bar_more(self): ... print("yet another method") ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, "echo_bar_more") True
現(xiàn)在已經(jīng)看到:在Python中,類也是對(duì)象,可以動(dòng)態(tài)地創(chuàng)建類。當(dāng)使用關(guān)鍵字class時(shí),Python使用元類像這樣創(chuàng)建類的。
什么是元類(終于講到了)元類就是創(chuàng)建類的“東西”。我們定義類是為了創(chuàng)建對(duì)象,是吧?但是我們認(rèn)識(shí)到在Python中類也是對(duì)象,而元類就是創(chuàng)建類這種對(duì)象(類)的,它們是類的類,你可以這樣理解:
MyClass = MetaClass() MyObject = MyClass()
你已經(jīng)看到type可以讓你做如下操作:
MyClass = type("MyClass", (), {})
這是因?yàn)閠ype方法實(shí)際上是一個(gè)元類。事實(shí)上,type是Python中用于創(chuàng)建所有類的元類。不過現(xiàn)在你一定很奇怪為什么這個(gè)類名首字母是小寫,而不是Type?我猜這是同str保持一致,str是用來(lái)創(chuàng)建string對(duì)象的類,int是用來(lái)創(chuàng)建integer對(duì)象的類,type則是創(chuàng)建類對(duì)象的類。
在Python中,一切,對(duì)就是一切,都是對(duì)象。包括整數(shù)、字符串、函數(shù)和類。所有東西都是對(duì)象,它們都是創(chuàng)建自某個(gè)類。
通過__class__屬性可以驗(yàn)證這一點(diǎn):
>>> age = 35 >>> age.__class__>>> name = "bob" >>> name.__class__ >>> def foo(): pass >>> foo.__class__ >>> class Bar(object): pass >>> b = Bar() >>> b.__class__
那么,一個(gè)__class__的__class__屬性是什么呢?
>>> age.__class__.__class__>>> name.__class__.__class__ >>> foo.__class__.__class__ >>> b.__class__.__class__
由此可見:元類確實(shí)就是創(chuàng)建類對(duì)象的東西。如果你覺得合適你就可以稱之為“類工廠”。type是Python使用的內(nèi)置元類,當(dāng)然,你也可以自己創(chuàng)建元類。
__metaclass__屬性編寫一個(gè)類時(shí)添加上__metaclass__屬性:
class Foo(object): __metaclass__ = something... [...]
如果你像上面這樣做,Python就會(huì)使用元類創(chuàng)建一個(gè)Foo類。
要當(dāng)心了,這里有些小圈套。你先寫下了“class Foo(object)”,但此時(shí)內(nèi)存中還沒有創(chuàng)建Foo類對(duì)象。Python會(huì)在類的聲明中尋找屬性__metaclass_,如果找到了就會(huì)使用其創(chuàng)建Foo類;如果沒有,會(huì)使用type創(chuàng)建這個(gè)類。下面這段文字要多讀幾遍。
當(dāng)你編寫以下代碼時(shí):
class Foo(Bar): pass
Python做了這些事情:
在類Foo中有定義__metaclass__屬性嗎? 如果有,則繼續(xù); 如果沒有,Python會(huì)在模塊層尋找__metaclass__屬性(這只針對(duì)沒有繼承任何其他類的情況); 如果模塊層也沒有,則會(huì)在Bar(第一個(gè)父類)中尋找(這就有可能是內(nèi)置的type)。 這樣找到__metaclass__后,使用它在內(nèi)存中創(chuàng)建名稱為Foo的類對(duì)象(這邊跟上,一個(gè)類對(duì)象)
需要注意的是,__metaclass__屬性不會(huì)被繼承,但是父類的元類(Bar.__class__)可以被繼承:如果Bar的__metaclass__屬性定義了使用type()(不是type.__new())創(chuàng)建Bar類,其子類不會(huì)繼承這個(gè)行為。(Be careful here that the metaclass attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a metaclass attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior.)TODO 這邊不太理解
現(xiàn)在有個(gè)新問題,你可以賦什么值給__metaclass__?
答案是:可以創(chuàng)建一個(gè)類的東西。
那什么可以創(chuàng)建類?type、子類化type或者使用type的東西。
元類的主要目的,是在創(chuàng)建類的時(shí)候動(dòng)態(tài)地改變類。通常你會(huì)想創(chuàng)建符合當(dāng)前上下文的類供API使用。舉一個(gè)簡(jiǎn)單的例子,當(dāng)你希望一個(gè)模塊中所有的類屬性都是小寫時(shí),有幾種方法可以實(shí)現(xiàn),其中有一種方法就是在模塊層設(shè)置__metaclass__。使用這種方法,這個(gè)模塊中所有的類都將使用此元類創(chuàng)建,我們只需要使元類將所有類屬性置為小寫。
幸運(yùn)的是,__metaclass__可以被任意調(diào)用,不一定非要是一個(gè)正式的類(我知道,名稱包含“class”不一定非要是一個(gè)類,搞清楚了...這很有用)。
現(xiàn)在先用一個(gè)函數(shù)舉一個(gè)簡(jiǎn)單的例子:
# 元類會(huì)自動(dòng)獲取通常傳給`type`的參數(shù) def upper_attr(future_class_name, future_class_parents, future_class_attr): """ 返回一個(gè)類對(duì)象,將其屬性置為大寫 """ # 過濾出所有開頭不為"__"的屬性,置為大寫 uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith("__"): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # 利用"type"創(chuàng)建類 return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr # 這會(huì)影響此模塊中所有的類 class Foo(): # global __metaclass__ won"t work with "object" though == 沒看懂 # but we can define __metaclass__ here instead to affect only this class # and this will work with "object" children bar = "bip" print(hasattr(Foo, "bar")) # Out: False print(hasattr(Foo, "BAR")) # Out: True f = Foo() print(f.BAR) # Out: "bip"
現(xiàn)在,完成同樣的功能,但是為元類定義一個(gè)真實(shí)的類:
# 記住`type`實(shí)際上是一個(gè)像`str`和`int`的類,可以用于被繼承 class UpperAttrMetaclass(type): # __new__放在__init__之前調(diào)用,此方法創(chuàng)建對(duì)象并反回 # 而__init__則是初始化作為參數(shù)傳遞給此方法的對(duì)象 # 除非你想控制如何創(chuàng)建一個(gè)對(duì)象,否則很少用到__new__ # 在這里,被創(chuàng)建的對(duì)象是類,而我們想自定義這個(gè)類,所以重寫了__new__ # 如果需要的話你也可以在__init__中做一些操作 # 一些高級(jí)用法會(huì)包括重寫__call__,不過這里還不需要 def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith("__"): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type(future_class_name, future_class_parents, uppercase_attr)
這不是真正的面向?qū)ο螅∣OP),這里直接調(diào)用了type,沒有重寫或者調(diào)用父類的__new__。現(xiàn)在像這樣處理:
class UpperAttrMetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith("__"): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # 復(fù)用type.__new__方法 # 這是基本的OOP,沒什么深?yuàn)W的 return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
你大概發(fā)現(xiàn)了傳給type的額外的參數(shù)upperattr_metaclass。這沒什么奇怪的:__new__的第一個(gè)參數(shù)總是其定義的類。就像類方法中第一個(gè)參數(shù)總是self。當(dāng)然,為了清晰期間,這里我起的名字比較長(zhǎng),但是像self這樣的參數(shù)通常有一個(gè)傳統(tǒng)的名字。所以真正的產(chǎn)品代碼中,元類是像這樣的:
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, dct): uppercase_attr = {} for name, val in dct.items(): if not name.startswith("__"): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type.__new__(cls, clsname, bases, uppercase_attr)
使用super可以更清晰,which will ease inheritance (because yes, you can have metaclasses, inheriting from metaclasses, inheriting from type)TODO:
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, dct): uppercase_attr = {} for name, val in dct.items(): if not name.startswith("__"): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
以上,關(guān)于元類也沒有更多了。使用元類的代碼比較復(fù)雜的原因不在于元類,而在于你通常會(huì)依靠自省、操縱繼承、__dict__變量等,使用元類做一些晦澀的事情。(it"s because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.)TODO
元類來(lái)用于黑魔法時(shí)的確特別有用,因?yàn)橐矔?huì)將事情搞得很復(fù)雜。但就其本身而言,是簡(jiǎn)單的:
攔截一個(gè)類的創(chuàng)建
修改類
返回修改的類
為什么會(huì)用元類代替函數(shù)?既然__metaclass__可以被任意調(diào)用,為什么要使用明顯更復(fù)雜的類呢?有這樣一些理由:
意圖明顯。當(dāng)你看到UpperAttrMetaclass(type),你知道接下來(lái)會(huì)發(fā)生什么。
可以使用OOP。元類可以繼承自元類,重寫父類的方法,元類甚至可以使用元類。
如果為一個(gè)類指定的元類是類而不是方法,這個(gè)類的子類將是元類的一個(gè)實(shí)例。Children of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function.TODO
你可以將代碼組織得更好。使用元類時(shí)肯定不會(huì)僅想像上面舉的例子那樣簡(jiǎn)單,通常是用于比較復(fù)雜的場(chǎng)景。將多個(gè)方法組織在一個(gè)類中有益于使代碼更容易閱讀。
你可以使用__new__,__init__ 和 __call__,這些方法可以處理不用的事情。即使很多時(shí)候你可以在__new__中完成所有工作,當(dāng)然,一些人會(huì)更習(xí)慣用__init__。
這些東西叫 “metaclass”,小心了!這一定很難搞!
為什么使用元類好了,現(xiàn)在的問題是:為什么要是用這樣晦澀且容易出錯(cuò)的特性?其實(shí),通常你不會(huì)用:
元類是99%的用戶根本不必操心的深度魔法。如果你在考慮是否需要使用,那就不要用(真正需要的用戶很清楚他們的需求,根本不需要解釋為什么要使用元類)
Python領(lǐng)袖 Tim Peters
使用元類的一個(gè)主要場(chǎng)景是創(chuàng)建API。Django ORM是一個(gè)典型的例子,它允許你像下面這樣定義:
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
如果你這樣做:
guy = Person(name="bob", age="35") print(guy.age)
它不會(huì)返回一個(gè)IntegerField的對(duì)象,而是返回一個(gè)整數(shù),甚至可以從數(shù)據(jù)庫(kù)中直接獲取數(shù)據(jù)。TODO
這是因?yàn)椋趍odels.Model中定義了__metaclass__,使用了一些魔法將你定義的簡(jiǎn)單的Person類轉(zhuǎn)換為一個(gè)復(fù)雜的數(shù)據(jù)庫(kù)掛鉤。(turn the Person you just defined with simple statements into a complex hook to a database field.)TODO
Django使用元類對(duì)外提供簡(jiǎn)單的API,簡(jiǎn)化了一些復(fù)雜的東西,API中重建的代碼會(huì)去完成幕后真正的工作。
結(jié)語(yǔ)首先,類是可以創(chuàng)建實(shí)例的對(duì)象。類本身是元類的對(duì)象:
>>> class Foo(object): pass >>> id(Foo) 142630324
在Python中,除了type,一切皆對(duì)象,一切都是類或者元類的對(duì)象。事實(shí)上type是自己的元類,這在純Python中這是無(wú)法實(shí)現(xiàn)的,這里在實(shí)現(xiàn)層上做了一些手段。
其次,元類是復(fù)雜的。你可能不希望對(duì)非常簡(jiǎn)單的類使用元類,那么還有其他兩種手段用來(lái)改變類:
monkey patching
類裝飾器
如果你需要改變類,99%的情況下使用這兩種方法。
但其實(shí)98%的情況你根本不需要改變類。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/44599.html
摘要:先簡(jiǎn)單介紹下中的元類。元類就是創(chuàng)建類的類,對(duì)于元類來(lái)說,類是它的實(shí)例,將返回。中的所有類,都是的實(shí)例,換句話說,是元類的基類。 我在看源代碼的時(shí)候,經(jīng)常蹦出這一句:How does it work!竟然有這種操作?本系列文章,試圖剖析代碼中發(fā)生的魔法。順便作為自己的閱讀筆記,以作提高。 先簡(jiǎn)單介紹下Python中的元類(metaclass)。元類就是創(chuàng)建類的類,對(duì)于元類來(lái)說,類是它的實(shí)...
摘要:什么是元類剛才說了,元類就是創(chuàng)建類的類。類上面的屬性,相信愿意了解元類細(xì)節(jié)的盆友,都肯定見過這個(gè)東西,而且為之好奇。使用了這個(gè)魔法方法就意味著就會(huì)用指定的元類來(lái)創(chuàng)建類了。深刻理解中的元類 (一) python中的類 今天看到一篇好文,然后結(jié)合自己的情況總結(jié)一波。這里討論的python類,都基于python2.7x以及繼承于object的新式類進(jìn)行討論。 首先在python中,所有東西都...
摘要:如果還是沒有找到,就會(huì)使用父類中的元類來(lái)創(chuàng)建類。元類通常用于處理比較復(fù)雜的情況。這是因?yàn)槭褂昧嗽悾鼤?huì)將中定義的字段轉(zhuǎn)換成數(shù)據(jù)庫(kù)中的字段。中所有數(shù)據(jù)類型都是對(duì)象,它們要么是類的實(shí)例要么是元類的實(shí)例。 原文地址:what is metaclass in Python?我的簡(jiǎn)書地址::nummy 類即對(duì)象 在理解元類之前,需要先掌握Python中的類,Python中類的概念與SmallT...
摘要:但一般情況下,我們使用類作為元類。那么,元類到底有什么用呢要你何用元類的主要目的是為了控制類的創(chuàng)建行為。當(dāng)然,有很多種做法,這里展示用元類的做法。當(dāng)你創(chuàng)建類時(shí),解釋器會(huì)調(diào)用元類來(lái)生成它,定義一個(gè)繼承自的普通類意味著調(diào)用來(lái)創(chuàng)建它。 元類 Python 中的元類(metaclass)是一個(gè)深度魔法,平時(shí)我們可能比較少接觸到元類,本文將通過一些簡(jiǎn)單的例子來(lái)理解這個(gè)魔法。 類也是對(duì)象 在 Py...
摘要:但是隨后有人提出反對(duì)意見并說這個(gè)是隨后搜索到這篇文章深刻理解中的元類里面介紹了如何使用函數(shù)創(chuàng)建一個(gè)類,并解釋了屬性。 有如下代碼 #-*-coding:utf-8-*- class a(): pass a1 = a() print(type(a),type(a1)) 兩個(gè)python版本分別為Python2.7.11Python3.5.1 在python2中得到的結(jié)果(, )a...
閱讀 1558·2021-11-23 09:51
閱讀 1092·2021-10-12 10:12
閱讀 2811·2021-09-22 16:06
閱讀 3636·2019-08-30 15:56
閱讀 3458·2019-08-30 15:53
閱讀 3110·2019-08-29 16:29
閱讀 2361·2019-08-29 15:27
閱讀 2017·2019-08-26 10:49