摘要:原鏈接中的元類是什么類也是對象在理解元類之前,需要掌握中類概念。事實上,是中用于創建所有類的元類。類本身是元類的對象在中,除了,一切皆對象,一切都是類或者元類的對象。事實上是自己的元類,
學習契機
項目中使用Elasticsearch(ES)存儲海量業務數據,基于ES向外提供的API進一層封裝,按需處理原始數據提供更精確、更多樣化的結果。在研究這一層的代碼時接觸到@six.add_metaclass(abc.ABCMeta),故而學習一下Python的元類。不過,雖然@six.add_metaclass(abc.ABCMeta)實現上與元類有關,但實際應用只需要調用其接口,并不需要接觸后幕后的元類操作。
翻譯這篇答案是為了方便自己記憶理解,其實原文中一些地方我自己不是很明白,所以這個翻譯會根據自己理解的程度持續更新。
stackoverflow-What are metaclasses in Python?
Python中的元類是什么 類也是對象在理解元類之前,需要掌握Python中類概念。Python的類概念稍有奇特,其借鑒于Smalltalk。
在大部分語言中,類用于描述如何生成一個對象,在Python中也是如此:
</>復制代碼
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
但是,在Python中類的意義更多,類同時也是對象。當你使用關鍵字class時,Python解釋器執行代碼時會生成一個對象。以下代碼會在內存中創建一個名為“ObjectCreator”的對象:
</>復制代碼
>>> class ObjectCreator(object):
... pass
...
這個對象(類)自身可以創建對象(實例),這是為什么它是類的原因。
不過它仍然是一個對象,你可以:
可以將它賦值給變量
可以復制
可以添加屬性 TODO 添加屬性只是對象的特性?
可以將其當作函數參數傳遞
舉例:
</>復制代碼
>>> print(ObjectCreator) # you can print a class because it"s an object
>>> 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>
動態創建類
既然類也是對象,那就可像其他對象那樣,動態創建類。
首先,可以使用關鍵字class,在函數中創建類:
</>復制代碼
>>> 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>
但是這還不夠動態,因為還是需要自己編寫整個類的代碼。所以,想一想,這些類既然是對象,就必然是某種東西生成的。當使用class關鍵字時,Python自動創建了類這個對象,但是像Python中大部分事情一樣,Python中也可以手動創建類。
還記type方法嗎?一個可以讓你知道一個對象是什么類型的方法:
</>復制代碼
>>> print(type(1))
>>> print(type("1"))
>>> print(type(ObjectCreator))
>>> print(type(ObjectCreator()))
除此之外,type還有一個完全不同的功能:動態創建類。將類的描述作為參數傳遞給type,會返回一個類。(我知道,同一個函數根據傳參的不同而展示出兩種完全不同的功能很不合理,不過這是為了Python的向后兼容。)
type如何創建類:
</>復制代碼
type(類名,
父類元祖 (可為空),
包含鍵值對屬性的字典)
舉例:
</>復制代碼
>>> class MyShinyClass(object):
... pass
上面這個MyShinyClass類可以用以下方法手動創建:
</>復制代碼
>>> MyShinyClass = type("MyShinyClass", (), {}) # 返回一個類
>>> print(MyShinyClass)
>>> print(MyShinyClass()) # 創建一個類對象
<__main__.MyShinyClass object at 0x8997cec>
應該注意到了,使用"MyShinyClass"作為類名,也將其作為一個變量名,賦值為類的引用。類名和變量名是可以不同的,但是沒必要把事情搞復雜。
type可以接受一個定義類屬性的字典作為參數:
</>復制代碼
>>> class Foo(object):
... bar = True
以上定義等同于:
</>復制代碼
>>> Foo = type("Foo", (), {"bar":True})
使用起來跟一個普通類一樣:
</>復制代碼
>>> print(Foo)
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar) # 利用實例打印類屬性
True
當然,也可以作為基類,給其他類繼承:
</>復制代碼
>>> class FooChild(Foo):
... pass
以上代碼等同于:
</>復制代碼
>>> FooChild = type("FooChild", (Foo,), {})
>>> print(FooChild)
>>> print(FooChild.bar) # bar屬性繼承自類Foo
True
你肯定還想為類添加方法。只需要定義一個名稱合理的函數,并將這個函數名作為屬性傳遞給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
在動態創建一個類之后,為這個類添加更多的方法,就像為一個正常創建的類添加方法一樣:
</>復制代碼
>>> def echo_bar_more(self):
... print("yet another method")
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, "echo_bar_more")
True
現在已經看到:在Python中,類也是對象,可以動態地創建類。當使用關鍵字class時,Python使用元類像這樣創建類的。
什么是元類(終于講到了)元類就是創建類的“東西”。我們定義類是為了創建對象,是吧?但是我們認識到在Python中類也是對象,而元類就是創建類這種對象(類)的,它們是類的類,你可以這樣理解:
</>復制代碼
MyClass = MetaClass()
MyObject = MyClass()
你已經看到type可以讓你做如下操作:
</>復制代碼
MyClass = type("MyClass", (), {})
這是因為type方法實際上是一個元類。事實上,type是Python中用于創建所有類的元類。不過現在你一定很奇怪為什么這個類名首字母是小寫,而不是Type?我猜這是同str保持一致,str是用來創建string對象的類,int是用來創建integer對象的類,type則是創建類對象的類。
在Python中,一切,對就是一切,都是對象。包括整數、字符串、函數和類。所有東西都是對象,它們都是創建自某個類。
通過__class__屬性可以驗證這一點:
</>復制代碼
>>> age = 35
>>> age.__class__
>>> name = "bob"
>>> name.__class__
>>> def foo(): pass
>>> foo.__class__
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
那么,一個__class__的__class__屬性是什么呢?
</>復制代碼
>>> age.__class__.__class__
>>> name.__class__.__class__
>>> foo.__class__.__class__
>>> b.__class__.__class__
由此可見:元類確實就是創建類對象的東西。如果你覺得合適你就可以稱之為“類工廠”。type是Python使用的內置元類,當然,你也可以自己創建元類。
__metaclass__屬性編寫一個類時添加上__metaclass__屬性:
</>復制代碼
class Foo(object):
__metaclass__ = something...
[...]
如果你像上面這樣做,Python就會使用元類創建一個Foo類。
要當心了,這里有些小圈套。你先寫下了“class Foo(object)”,但此時內存中還沒有創建Foo類對象。Python會在類的聲明中尋找屬性__metaclass_,如果找到了就會使用其創建Foo類;如果沒有,會使用type創建這個類。下面這段文字要多讀幾遍。
當你編寫以下代碼時:
</>復制代碼
class Foo(Bar):
pass
Python做了這些事情:
</>復制代碼
在類Foo中有定義__metaclass__屬性嗎?
如果有,則繼續;
如果沒有,Python會在模塊層尋找__metaclass__屬性(這只針對沒有繼承任何其他類的情況);
如果模塊層也沒有,則會在Bar(第一個父類)中尋找(這就有可能是內置的type)。
這樣找到__metaclass__后,使用它在內存中創建名稱為Foo的類對象(這邊跟上,一個類對象)
需要注意的是,__metaclass__屬性不會被繼承,但是父類的元類(Bar.__class__)可以被繼承:如果Bar的__metaclass__屬性定義了使用type()(不是type.__new())創建Bar類,其子類不會繼承這個行為。(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 這邊不太理解
現在有個新問題,你可以賦什么值給__metaclass__?
答案是:可以創建一個類的東西。
那什么可以創建類?type、子類化type或者使用type的東西。
元類的主要目的,是在創建類的時候動態地改變類。通常你會想創建符合當前上下文的類供API使用。舉一個簡單的例子,當你希望一個模塊中所有的類屬性都是小寫時,有幾種方法可以實現,其中有一種方法就是在模塊層設置__metaclass__。使用這種方法,這個模塊中所有的類都將使用此元類創建,我們只需要使元類將所有類屬性置為小寫。
幸運的是,__metaclass__可以被任意調用,不一定非要是一個正式的類(我知道,名稱包含“class”不一定非要是一個類,搞清楚了...這很有用)。
現在先用一個函數舉一個簡單的例子:
</>復制代碼
# 元類會自動獲取通常傳給`type`的參數
def upper_attr(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
# 利用"type"創建類
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # 這會影響此模塊中所有的類
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"
現在,完成同樣的功能,但是為元類定義一個真實的類:
</>復制代碼
# 記住`type`實際上是一個像`str`和`int`的類,可以用于被繼承
class UpperAttrMetaclass(type):
# __new__放在__init__之前調用,此方法創建對象并反回
# 而__init__則是初始化作為參數傳遞給此方法的對象
# 除非你想控制如何創建一個對象,否則很少用到__new__
# 在這里,被創建的對象是類,而我們想自定義這個類,所以重寫了__new__
# 如果需要的話你也可以在__init__中做一些操作
# 一些高級用法會包括重寫__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)
這不是真正的面向對象(OOP),這里直接調用了type,沒有重寫或者調用父類的__new__?,F在像這樣處理:
</>復制代碼
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
# 復用type.__new__方法
# 這是基本的OOP,沒什么深奧的
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)
你大概發現了傳給type的額外的參數upperattr_metaclass。這沒什么奇怪的:__new__的第一個參數總是其定義的類。就像類方法中第一個參數總是self。當然,為了清晰期間,這里我起的名字比較長,但是像self這樣的參數通常有一個傳統的名字。所以真正的產品代碼中,元類是像這樣的:
</>復制代碼
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)
以上,關于元類也沒有更多了。使用元類的代碼比較復雜的原因不在于元類,而在于你通常會依靠自省、操縱繼承、__dict__變量等,使用元類做一些晦澀的事情。(it"s because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.)TODO
元類來用于黑魔法時的確特別有用,因為也會將事情搞得很復雜。但就其本身而言,是簡單的:
攔截一個類的創建
修改類
返回修改的類
為什么會用元類代替函數?既然__metaclass__可以被任意調用,為什么要使用明顯更復雜的類呢?有這樣一些理由:
意圖明顯。當你看到UpperAttrMetaclass(type),你知道接下來會發生什么。
可以使用OOP。元類可以繼承自元類,重寫父類的方法,元類甚至可以使用元類。
如果為一個類指定的元類是類而不是方法,這個類的子類將是元類的一個實例。Children of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function.TODO
你可以將代碼組織得更好。使用元類時肯定不會僅想像上面舉的例子那樣簡單,通常是用于比較復雜的場景。將多個方法組織在一個類中有益于使代碼更容易閱讀。
你可以使用__new__,__init__ 和 __call__,這些方法可以處理不用的事情。即使很多時候你可以在__new__中完成所有工作,當然,一些人會更習慣用__init__。
這些東西叫 “metaclass”,小心了!這一定很難搞!
為什么使用元類好了,現在的問題是:為什么要是用這樣晦澀且容易出錯的特性?其實,通常你不會用:
</>復制代碼
元類是99%的用戶根本不必操心的深度魔法。如果你在考慮是否需要使用,那就不要用(真正需要的用戶很清楚他們的需求,根本不需要解釋為什么要使用元類)
Python領袖 Tim Peters
使用元類的一個主要場景是創建API。Django ORM是一個典型的例子,它允許你像下面這樣定義:
</>復制代碼
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
如果你這樣做:
</>復制代碼
guy = Person(name="bob", age="35")
print(guy.age)
它不會返回一個IntegerField的對象,而是返回一個整數,甚至可以從數據庫中直接獲取數據。TODO
這是因為,在models.Model中定義了__metaclass__,使用了一些魔法將你定義的簡單的Person類轉換為一個復雜的數據庫掛鉤。(turn the Person you just defined with simple statements into a complex hook to a database field.)TODO
Django使用元類對外提供簡單的API,簡化了一些復雜的東西,API中重建的代碼會去完成幕后真正的工作。
結語首先,類是可以創建實例的對象。類本身是元類的對象:
</>復制代碼
>>> class Foo(object): pass
>>> id(Foo)
142630324
在Python中,除了type,一切皆對象,一切都是類或者元類的對象。事實上type是自己的元類,這在純Python中這是無法實現的,這里在實現層上做了一些手段。
其次,元類是復雜的。你可能不希望對非常簡單的類使用元類,那么還有其他兩種手段用來改變類:
monkey patching
類裝飾器
如果你需要改變類,99%的情況下使用這兩種方法。
但其實98%的情況你根本不需要改變類。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/44599.html
摘要:先簡單介紹下中的元類。元類就是創建類的類,對于元類來說,類是它的實例,將返回。中的所有類,都是的實例,換句話說,是元類的基類。 我在看源代碼的時候,經常蹦出這一句:How does it work!竟然有這種操作?本系列文章,試圖剖析代碼中發生的魔法。順便作為自己的閱讀筆記,以作提高。 先簡單介紹下Python中的元類(metaclass)。元類就是創建類的類,對于元類來說,類是它的實...
摘要:什么是元類剛才說了,元類就是創建類的類。類上面的屬性,相信愿意了解元類細節的盆友,都肯定見過這個東西,而且為之好奇。使用了這個魔法方法就意味著就會用指定的元類來創建類了。深刻理解中的元類 (一) python中的類 今天看到一篇好文,然后結合自己的情況總結一波。這里討論的python類,都基于python2.7x以及繼承于object的新式類進行討論。 首先在python中,所有東西都...
摘要:如果還是沒有找到,就會使用父類中的元類來創建類。元類通常用于處理比較復雜的情況。這是因為使用了元類,它會將中定義的字段轉換成數據庫中的字段。中所有數據類型都是對象,它們要么是類的實例要么是元類的實例。 原文地址:what is metaclass in Python?我的簡書地址::nummy 類即對象 在理解元類之前,需要先掌握Python中的類,Python中類的概念與SmallT...
摘要:但一般情況下,我們使用類作為元類。那么,元類到底有什么用呢要你何用元類的主要目的是為了控制類的創建行為。當然,有很多種做法,這里展示用元類的做法。當你創建類時,解釋器會調用元類來生成它,定義一個繼承自的普通類意味著調用來創建它。 元類 Python 中的元類(metaclass)是一個深度魔法,平時我們可能比較少接觸到元類,本文將通過一些簡單的例子來理解這個魔法。 類也是對象 在 Py...
摘要:但是隨后有人提出反對意見并說這個是隨后搜索到這篇文章深刻理解中的元類里面介紹了如何使用函數創建一個類,并解釋了屬性。 有如下代碼 #-*-coding:utf-8-*- class a(): pass a1 = a() print(type(a),type(a1)) 兩個python版本分別為Python2.7.11Python3.5.1 在python2中得到的結果(, )a...
閱讀 1570·2021-11-23 09:51
閱讀 1107·2021-10-12 10:12
閱讀 2826·2021-09-22 16:06
閱讀 3650·2019-08-30 15:56
閱讀 3474·2019-08-30 15:53
閱讀 3120·2019-08-29 16:29
閱讀 2372·2019-08-29 15:27
閱讀 2031·2019-08-26 10:49
极致性价比!云服务器续费无忧!
Tesla A100/A800、Tesla V100S等多种GPU云主机特惠2折起,不限台数,续费同价。
NVIDIA RTX 40系,高性价比推理显卡,满足AI应用场景需要。
乌兰察布+上海青浦,满足东推西训AI场景需要