摘要:如果還是沒有找到,就會使用父類中的元類來創(chuàng)建類。元類通常用于處理比較復(fù)雜的情況。這是因為使用了元類,它會將中定義的字段轉(zhuǎn)換成數(shù)據(jù)庫中的字段。中所有數(shù)據(jù)類型都是對象,它們要么是類的實例要么是元類的實例。
類即對象原文地址:what is metaclass in Python?
我的簡書地址::nummy
在理解元類之前,需要先掌握Python中的類,Python中類的概念與SmallTalk中類的概念相似。
在大多數(shù)語言中,類是用來描述如何創(chuàng)建對象的代碼段,這在Python中也是成立的:
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
Python中,類其實也是對象。當我們使用關(guān)鍵字class的時候,Python會執(zhí)行這段代碼,然后生成一個對象。下面的代碼在內(nèi)存中創(chuàng)建一個對象ObjectCreator:
>>> class ObjectCreator(object): ... pass ...
當一個對象具有創(chuàng)建對象的能力時,就稱該對象為類。
所以類本質(zhì)上還是一個對象,因此它具有以下屬性:
可以將它賦值給其它變量
可以對它進行復(fù)制
可以給它添加屬性
可以將它傳遞給函數(shù)作為參數(shù)
例如:
>>> print(ObjectCreator) # you can print a class because it"s an object動態(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>
既然類就是對象,那我們就可以像創(chuàng)建其他對象一樣動態(tài)創(chuàng)建類。
首先,在函數(shù)中使用class創(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>
但是上面的例子也稱不上是完全動態(tài)的創(chuàng)建類,因為我們還需要在其中編寫整個類的代碼。
既然類就是對象,那么它們肯定是通過某個東西來創(chuàng)建的。當使用class關(guān)鍵字的時候,Python會自動創(chuàng)建類,Python也提供了方法讓我們手動來創(chuàng)建類。
還記得type()函數(shù)嗎?這個函數(shù)可以獲取對象的類型。
>>> print(type(1))>>> print(type("1")) >>> print(type(ObjectCreator)) >>> print(type(ObjectCreator()))
type還有另外一個功能,那就是創(chuàng)建類。type使用類的相關(guān)描述作為參數(shù),然后返回一個類。
type創(chuàng)建類的語法如下:
type(類名,基類元組(可以為空,用于繼承), 包含屬性或函數(shù)的字典)
例如:
>>> class MyShinyClass(object): ... pass
上面的類可以使用下面的方法手動創(chuàng)建:
>>> MyShinyClass = type("MyShinyClass", (), {}) # returns a class object >>> print(MyShinyClass)>>> print(MyShinyClass()) # create an instance with the class <__main__.MyShinyClass object at 0x8997cec>
type也接收一個字典參數(shù)來定義類中的屬性:
>>> class Foo(object): ... bar = True
等價于
>>> Foo = type("Foo", (), {"bar":True})
通過type創(chuàng)建的類使用方式跟普通類一樣:
>>> 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 is inherited from Foo True
最后,我們可能還想給類添加方法,可以先定義一個函數(shù),然后將它以屬性的方式賦予給類。
>>> 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
而且,我們還可以在動態(tài)創(chuàng)建類之后,給類添加更多的方法和屬性:
>>> def echo_bar_more(self): ... print("yet another method") ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, "echo_bar_more") True什么是元類?
通常,我們定義類來創(chuàng)建對象,但是現(xiàn)在我們知道類也是對象。那么是通過什么來創(chuàng)建類呢?答案就是元類。你可以想象關(guān)系如下:
MyClass = MetaClass() MyObject = MyClass()
你已經(jīng)知道使用type可以創(chuàng)建類:
MyClass = type("MyClass", (), {})
那是因為type函數(shù)實際上就是一個元類,Python使用type作為元類來創(chuàng)建所有的類。
通過檢查class屬性,我們可以知道,其實Python中任何數(shù)據(jù)類型都是對象,包括整型、字符串、函數(shù)以及類,它們都是對象。它們都是從類中創(chuàng)建的。
>>> 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__
所以類其實就是通過元類來創(chuàng)建的,你可以將元類稱之為類工廠。
type是內(nèi)置的元類,Python默認使用它來創(chuàng)建類。當然,我們也可以定義屬于我們自己的元類。
當我們創(chuàng)建類的時候,可以給它添加metaclass屬性:
class Foo(object): __metaclass__ = something... [...]
如果我們定義了metaclass屬性,Python就會使用這個元類來創(chuàng)建類Foo。
注意,編譯器首先讀取class Foo(object),這時并不會在內(nèi)存中創(chuàng)建Foo類。Python會繼續(xù)查找類定義中的__meatclass__,如果找到了,就使用它來創(chuàng)建類Foo,如果沒有找到,就使用type來創(chuàng)建類。
所以對于以下代碼:
class Foo(Bar): pass
Python工作流程如下:
首先檢查Foo中是否具有屬性__metaclass__?
如果找到,就使用__metaclass__定義的元類在內(nèi)存中創(chuàng)建一個類對象。
如果在類定義中沒有找到這個屬性,就在模塊級別中進行查找。
如果還是沒有找到,就會使用父類Bar中的元類來創(chuàng)建類。
注意:類中的__metaclass__屬性不會被子類繼承,但是父類中的__class__會被繼承。
自定義元類元類的主要作用是在創(chuàng)建類的時候自動改變類。
例如,想要實現(xiàn)模塊中所有的類屬性都是大寫格式。可以定義模塊級別的__metaclass__來實現(xiàn)。
這樣模塊中所有的類都是通過這個元類來創(chuàng)建的。
def upper_attr(future_class_name, future_class_parents, future_class_attr): """ 返回一個類,該類的所有屬性名的都為大寫 """ # 將不是__開頭的屬性名轉(zhuǎn)為大寫字母 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 # 定義模塊級別的元類,這樣模塊中所有類都會使用該元類創(chuàng)建 class Foo(): # 注意,新式類不支持模塊級別的元類,但是可以在類中定義__metaclass__ bar = "bip" print(hasattr(Foo, "bar")) # 輸出: False print(hasattr(Foo, "BAR")) # 輸出: True f = Foo() print(f.BAR) # Out: "bip"
也可以將metaclass定義為一個真正的類:
# 記住type還是一個類,所以可以繼承它 class UpperAttrMetaclass(type): # __new__ 會在__init__之前調(diào)用,它會創(chuàng)建并返回一個實例 # 而__init__僅用于初始化,進行一些參數(shù)的配置 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的思想,因為它直接調(diào)用了type方法,實際上可以調(diào)用type的__new__方法。
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 # 調(diào)用type.__new__方法 return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
你可能注意到參數(shù)upperattr_metaclass, 它代表要實例化的類。當然,我這里取這么個復(fù)雜的名字主要是為了明確它的含義。但是,就像self參數(shù)一樣,所有參數(shù)都有其習慣性命名。所以生產(chǎn)環(huán)境下的metaclass定義如下:
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方法,以便減輕這種繼承關(guā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 super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
元類實際上做了以下三方面的工作:
干涉創(chuàng)建類的過程
修改類
返回修改之后的類
為什么使用類而不是函數(shù)來定義元類?理由如下:
目的更明確,當你閱讀UpperAttrMetaclass(type)的時候,你知道它用來做什么。
可以使用面向?qū)ο缶幊?,元類可以繼承自其它元類,還可以覆蓋父類方法。
可以更好的組織代碼結(jié)構(gòu)。元類通常用于處理比較復(fù)雜的情況。
可以為__new__、__init__和__call__編寫鉤子,為后續(xù)開發(fā)者提供便利。
為什么使用元類?現(xiàn)在,終極問題來了,為什么要使用元類這種模糊且容易出錯的功能?
一般情況下,我們并不會使用元類,99%的開發(fā)者并不會用到元類,所以一般不用考慮這個問題。
元類主用用于創(chuàng)建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)
返回的結(jié)果是int類型而不是IntegerField對象。這是因為models.Model使用了元類,它會將Python中定義的字段轉(zhuǎn)換成數(shù)據(jù)庫中的字段。
通過使用元類,Django將復(fù)雜的接口轉(zhuǎn)換成簡單的接口。
首先,我們知道了類其實就是可以創(chuàng)建實例的對象。而類又是通過元類來創(chuàng)建的。
>>> class Foo(object): pass >>> id(Foo) 142630324
Python中所有數(shù)據(jù)類型都是對象,它們要么是類的實例要么是元類的實例。
除了type,它實際上是自身的元類。這一點沒法在Python中重現(xiàn),因為它是在編譯階段實現(xiàn)的。
其次, 元類都是復(fù)雜的,對于一般的類是用不著的??梢允褂靡韵聝煞N技巧修改類:
monkey patch
類修飾器
當你需要修改類的時候,99%的情況下可以使用元類。但是99%的情況下,你根本不需要修改一個類。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/38050.html
摘要:同時,在元類中,我們還需要加上一個判斷,只有在這個類創(chuàng)建時才需要控制其類的生成,其他的就不需要了。完整代碼后臺回復(fù)元類獲取原創(chuàng)不易,如果文章對你有用的話,點贊留言轉(zhuǎn)發(fā)是對我的最大支持日常學代碼不止,還有美和樂趣 我之前在深入理解python中的類和對象中說過,python中的類也是一個對象,可以說是類對象,可以由type來創(chuàng)建類對象的。有了這個知識我們先看看下面這個函數(shù): showIm...
摘要:什么是元類剛才說了,元類就是創(chuàng)建類的類。類上面的屬性,相信愿意了解元類細節(jié)的盆友,都肯定見過這個東西,而且為之好奇。使用了這個魔法方法就意味著就會用指定的元類來創(chuàng)建類了。深刻理解中的元類 (一) python中的類 今天看到一篇好文,然后結(jié)合自己的情況總結(jié)一波。這里討論的python類,都基于python2.7x以及繼承于object的新式類進行討論。 首先在python中,所有東西都...
摘要:原鏈接中的元類是什么類也是對象在理解元類之前,需要掌握中類概念。事實上,是中用于創(chuàng)建所有類的元類。類本身是元類的對象在中,除了,一切皆對象,一切都是類或者元類的對象。事實上是自己的元類, 學習契機 項目中使用Elasticsearch(ES)存儲海量業(yè)務(wù)數(shù)據(jù),基于ES向外提供的API進一層封裝,按需處理原始數(shù)據(jù)提供更精確、更多樣化的結(jié)果。在研究這一層的代碼時接觸到@six.add_me...
摘要:但一般情況下,我們使用類作為元類。那么,元類到底有什么用呢要你何用元類的主要目的是為了控制類的創(chuàng)建行為。當然,有很多種做法,這里展示用元類的做法。當你創(chuàng)建類時,解釋器會調(diào)用元類來生成它,定義一個繼承自的普通類意味著調(diào)用來創(chuàng)建它。 元類 Python 中的元類(metaclass)是一個深度魔法,平時我們可能比較少接觸到元類,本文將通過一些簡單的例子來理解這個魔法。 類也是對象 在 Py...
摘要:好吧,事實上,類本身也是實例,當然,它們是元類的實例。中的一切都是對象,它們要么是類的實例,要么是元類的實例,除了。 寫在最前面 一些很重要的知識,我的寫得有點亂,也可以去看這些文章 Python 面向?qū)ο螅ǔ跫壠?Python 面向?qū)ο螅ㄟM階篇) 深刻理解Python中的元類(metaclass) 首先來看一個例子,正常情況下我們定義并且實例一個類如下 class Foo(ob...
閱讀 3826·2021-11-25 09:43
閱讀 2170·2021-11-23 10:11
閱讀 1397·2021-09-29 09:35
閱讀 1310·2021-09-24 10:31
閱讀 2035·2019-08-30 15:48
閱讀 2353·2019-08-29 15:28
閱讀 425·2019-08-29 12:36
閱讀 3490·2019-08-28 18:12