摘要:但我并不是一個翻譯者并不會嚴格遵守每行每句的翻譯有時候我會將表述換個順序省略一些我認為無關緊要的話,以便讀者更好理解。類也是對象在理解之前,我們先要掌握中的類是什么。這個對象類自身擁有產生對象實例的能力。不過這樣做是為了保持向后兼容性。
前言
這篇博客是我在stackoverflow上看了一個提問回復后寫的,例子基本用的都是e-satis本人的例子,語言組織也基本按照翻譯來。
但我并不是一個翻譯者,并不會嚴格遵守每行每句的翻譯;
有時候我會將表述換個順序,省略一些我認為無關緊要的話,以便讀者更好理解。
所以,如果你不喜歡我的語言表述,或者想要看英文原文,可以去查看原回復。
類也是對象在理解metaclass之前,我們先要掌握python中的類class是什么。
python中類的概念,是借鑒自smalltalk語言。
在大部分語言中,類指的是"描述如何產生一個對象(object)"的一段代碼,這對于python也是如此。
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
但是,在python中,類遠不止如此,類同時也是對象。
當你遇到關鍵詞class的時候,python就會自動執行產生一個對象。下面的代碼段中:
>>> class ObjectCreator(object): ... pass ...
python在內存中產生了一個名叫做"ObjectCreator"的對象。這個對象(類)自身擁有產生對象(實例instance)的能力。 這就是為什么稱呼這東西(后面遇到容易混淆的地方,我們稱之為:類對象)也是類的原因。同時,它也是一個對象,因此你可以對它做如下操作:
賦值給變量
復制它
為它增加屬性(attribute)
作為參數傳值給函數
舉例:
>>> print(ObjectCreator) # 你可以打印一個類,因為它同時也是對象動態創建類>>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # 作為參數傳值給函數 >>> 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 # 將類賦值給變量 >>> 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>
這很容易理解吧。但是,這并不那么動態啊。我們還是需要自己來寫這個類的代碼。
既然類也是對象,那就應該有用來產生它的東西。這東西就是type。
先來說說你所認識的type。這個古老而好用的函數,可以讓我們知道一個對象的類型是什么。
>>> print(type(1))>>> print(type("1")) >>> print(type(ObjectCreator)) >>> print(type(ObjectCreator()))
實際上,type還有一個完全不同的功能,它可以在運行時產生類。type可以傳入一些參數,然后返回一個類。(好吧,必須承認,根據不同的傳入參數,一個相同的函數type居然會有兩個完全不同的作用,這很愚蠢。不過python這樣做是為了保持向后兼容性。)
下面舉例type創建類的用法。首先,對于類一般是這么定義的:
>>> class MyShinyClass(object): ... pass 在下面,MyShinyClass也可以這樣子被創建出來,并且跟上面的創建方法有一樣的表現: >>> MyShinyClass = type("MyShinyClass", (), {}) # returns a class object >>> print(MyShinyClass)>>> print(MyShinyClass()) # create an instance with the class <__main__.MyShinyClass object at 0x8997cec>
type創建類需要傳入三個參數,分別為:
類的名字
一組"類的父類"的元組(tuple) (這個會實現繼承,也可以為空)
字典 (類的屬性名與值,key-value的形式,不傳相當于為空,如一般寫法中的pass).
下面來點復雜的,來更好的理解type傳入的三個參數:
class Foo(object): bar = True def echo_bar(self): print(self.bar)
等價于:
def echo_bar(self): print(self.bar) Foo = type("Foo", (), {"bar":True, "echo_bar": echo_bar})
想要看點有繼承關系的類的實現,來:
class FooChild(Foo): pass
等價于:
FooChild = type("FooChild", (Foo, ), {})
回顧一下我們學到哪了: 在python中,類就是對象,并且你可以在運行的時候動態創建類.
那到底什么是metaclass(元類)metaclass 就是創建類的那家伙。(事實上,type就是一個metaclass)
我們知道,我們定義了class就是為了能夠創建object的,沒錯吧?
我們也學習了,python中類也是對象。
那么,metaclass就是用來創造“類對象”的類.它是“類對象”的“類”。
可以這樣子來理解:
MyClass = MetaClass() MyObject = MyClass()
也可以用我們上面學到的type來表示:
MyClass = type("MyClass", (), {})
說白了,函數type就是一個特殊的metaclass.
python在背后使用type創造了所有的類。type是所有類的metaclass.
我們可以使用__class__屬性來驗證這個說法.
在python中,一切皆為對象:整數、字符串、函數、類.所有這些對象,都是通過類來創造的.
>>> 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__
metaclass就是創造類對象的工具.如果你喜歡,你也可以稱之為"類的工廠".
type是python內置的metaclass。不過,你也可以編寫自己的metaclass.
__metaclass__ 屬性我們可以在一個類中加入 __metaclass__ 屬性.
class Foo(object): __metaclass__ = something... ...... # 省略
當你這么做了,python就會使用metaclass來創造類:Foo。
注意啦,這里有些技巧的。
當你寫下class Foo(object)的時候,類對象Foo還沒有在內存中生成。
python會在類定義中尋找__metaclass__ 。如果找到了,python就會使用這個__metaclass__ 來創造類對象: Foo。如果沒找到,python就使用type來創造Foo。
請把下面的幾段話重復幾遍:
當你寫如下代碼的時候:
class Foo(Bar): pass
python做了以下事情:
Foo中有__metaclass__這個屬性嗎?
如果有,python會在內存中通過__metaclass__創建一個名字為Foo的類對象。
如果python沒有在Foo中找到__metaclass__,它會繼續在Bar(父類)中尋找__metaclass__,并嘗試做和前面同樣的操作。
如果python由下往上遍歷父類也都沒有找不到__metaclass__,它就會在模塊(module)中去尋找__metaclass__,并嘗試做同樣的操作。
如果還是沒有找不到__metaclass__, python才會用內置的type(這也是一個metaclass)來創建這個類對象。
現在問題來了,我們要怎么用代碼來實現__metaclass__呢? 寫一些可以用來產生類(class)的東西就行。
那什么可以產生類?無疑就是type,或者type的任何子類,或者任何使用到type的東西都行.
自定義metaclass使用metaclass的主要目的,是為了能夠在創建類的時候,自動地修改類。
一個很傻的需求,我們決定要將該模塊中的所有類的屬性,改為大寫。
有幾種方法可以做到,這里使用__metaclass__來實現.
在模塊的層次定義metaclass,模塊中的所有類都會使用它來創造類。我們只需要告訴metaclass,將所有的屬性轉化為大寫。
# type也是一個類,我們可以繼承它. class UpperAttrMetaclass(type): # __new__ 是在__init__之前被調用的特殊方法 # __new__是用來創建對象并返回這個對象 # 而__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,而不是改寫父類的__type__方法.
所以我們也可以這樣子處理:
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 return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
這樣子看,我們只是復用了 type.__new__方法,這就是我們熟悉的基本的OOP編程,沒什么魔法可言.
你可能注意到,__new__方法相比于
type(future_class_name, future_class_parents, future_class_attr)
多了一個參數: upperattr_metaclass, 請別在意,這沒什么特別的: __new__總是將"它要定義的類"作為第一個參數。
這就好比是 self 在類的一般方法(method)中一樣,也是被作為第一個參數傳入。
當然啦,這里的名字的確是我起的太長了。就像self一樣,所有的參數都有它們傳統的名稱。
因此,在實際的代碼中,一個metaclass應該是寫成下面樣子的:
(我們同時使用常見的super來讓代碼更清晰)
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attr = {} for name, val in attrs.items(): if not name.startswith("__"): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, attrs)
使用了 metaclass 的代碼是比較復雜,但我們使用它的原因并不是為了復雜, 而是因為我們通常會使用 metaclass 去做一些晦澀的事情,比如, 依賴于自省,控制繼承等等。
確實,用 metaclass 來搞些“黑魔法”是特別有用的,因而會復雜化代碼。
但就metaclass本身而言,它們其實是很簡單的:中斷類的默認創建、修改類、最后返回修改后的類.
到底為什么要使用metaclass現在我們面臨一個問題: 為什么要使用metaclass? 它容易出錯且晦澀難懂.
好吧,一般來說,我們根本就用不上它, 99%的用戶應該根本不必為此操心。
實際用到metaclass的人,很清楚他們到底需要做什么,根本不用解釋為什么要用.
metaclass 的一個主要用途就是構建API。Django(一個python寫的web框架)的ORM 就是一個例子。
用Django先定義了以下Model:
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
然后執行下面代碼:
guy = Person.objects.get(name="bob") print guy.age # result is 35
這里打印的輸出并不是IntegerField,而是一個int,int是從數據庫中獲取的.
這是因為 models.Model 使用 __metaclass__來實現了復雜的數據庫查詢。但對于你看來,這就是簡單的API而已,不用關心背后的復雜工作。
結語復習一下,我們知道了,類是能夠創造對象實例的對象,同時也是metaclass的對象實例(因為metaclass創造了它們).
在python中,一切皆為對象。它們要么是類的實例,要么是metaclass的實例, 除了type。
type是它自身的metaclass。至于是怎么實現的,總之純python語言是不可能實現的,這需要在實現層面上耍一些小手段才能做到的。
metaclass用起來比較復雜, 如果需要對非常簡單的類進行修改, 你可能不會使用它。有以下兩個技術可以供你選擇:
猴子修補 Monkey patch
類修飾器
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44215.html
摘要:如果還是沒有找到,就會使用父類中的元類來創建類。元類通常用于處理比較復雜的情況。這是因為使用了元類,它會將中定義的字段轉換成數據庫中的字段。中所有數據類型都是對象,它們要么是類的實例要么是元類的實例。 原文地址:what is metaclass in Python?我的簡書地址::nummy 類即對象 在理解元類之前,需要先掌握Python中的類,Python中類的概念與SmallT...
摘要:什么是元類剛才說了,元類就是創建類的類。類上面的屬性,相信愿意了解元類細節的盆友,都肯定見過這個東西,而且為之好奇。使用了這個魔法方法就意味著就會用指定的元類來創建類了。深刻理解中的元類 (一) python中的類 今天看到一篇好文,然后結合自己的情況總結一波。這里討論的python類,都基于python2.7x以及繼承于object的新式類進行討論。 首先在python中,所有東西都...
摘要:但一般情況下,我們使用類作為元類。那么,元類到底有什么用呢要你何用元類的主要目的是為了控制類的創建行為。當然,有很多種做法,這里展示用元類的做法。當你創建類時,解釋器會調用元類來生成它,定義一個繼承自的普通類意味著調用來創建它。 元類 Python 中的元類(metaclass)是一個深度魔法,平時我們可能比較少接觸到元類,本文將通過一些簡單的例子來理解這個魔法。 類也是對象 在 Py...
摘要:最前面那個,解釋器實際的流程是解析這段代碼,得知它需要創建一個類對象,這個類的名字叫做它的父類列表用表示是,它的屬性用一個來表示就是。解決方法很簡單關鍵就是前面被特別標記了的應當返回這個的父類的方法返回的對象。 (原發于我的blog:Python: metaclass小記 ) 友情提示:本文不一定適合閱讀,如果執意要讀,請備好暈車藥。 題記 Metaclasses are deepe...
摘要:原鏈接中的元類是什么類也是對象在理解元類之前,需要掌握中類概念。事實上,是中用于創建所有類的元類。類本身是元類的對象在中,除了,一切皆對象,一切都是類或者元類的對象。事實上是自己的元類, 學習契機 項目中使用Elasticsearch(ES)存儲海量業務數據,基于ES向外提供的API進一層封裝,按需處理原始數據提供更精確、更多樣化的結果。在研究這一層的代碼時接觸到@six.add_me...
閱讀 1619·2021-11-11 10:59
閱讀 2624·2021-09-04 16:40
閱讀 3650·2021-09-04 16:40
閱讀 2979·2021-07-30 15:30
閱讀 1615·2021-07-26 22:03
閱讀 3164·2019-08-30 13:20
閱讀 2225·2019-08-29 18:31
閱讀 439·2019-08-29 12:21