成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Python -- 元類metaclass詳解

tracy / 2070人閱讀

摘要:原鏈接中的元類是什么類也是對象在理解元類之前,需要掌握中類概念。事實(shí)上,是中用于創(chuàng)建所有類的元類。類本身是元類的對象在中,除了,一切皆對象,一切都是類或者元類的對象。事實(shí)上是自己的元類,

學(xué)習(xí)契機(jī)

項(xiàng)目中使用Elasticsearch(ES)存儲海量業(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í)原文中一些地方我自己不是很明白,所以這個翻譯會根據(jù)自己理解的程度持續(xù)更新。

原鏈接

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中類的意義更多,類同時(shí)也是對象。當(dāng)你使用關(guān)鍵字class時(shí),Python解釋器執(zhí)行代碼時(shí)會生成一個對象。以下代碼會在內(nèi)存中創(chuàng)建一個名為“ObjectCreator”的對象:

>>> class ObjectCreator(object):
...       pass
...

這個對象(類)自身可以創(chuàng)建對象(實(shí)例),這是為什么它是類的原因。
不過它仍然是一個對象,你可以:

可以將它賦值給變量

可以復(fù)制

可以添加屬性 TODO 添加屬性只是對象的特性?

可以將其當(dāng)作函數(shù)參數(shù)傳遞

舉例:

>>> 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>
動態(tài)創(chuà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>

但是這還不夠動態(tài),因?yàn)檫€是需要自己編寫整個類的代碼。所以,想一想,這些類既然是對象,就必然是某種東西生成的。當(dāng)使用class關(guān)鍵字時(shí),Python自動創(chuàng)建了類這個對象,但是像Python中大部分事情一樣,Python中也可以手動創(chuàng)建類。
還記type方法嗎?一個可以讓你知道一個對象是什么類型的方法:

>>> print(type(1))

>>> print(type("1"))

>>> print(type(ObjectCreator))

>>> print(type(ObjectCreator()))

除此之外,type還有一個完全不同的功能:動態(tài)創(chuàng)建類。將類的描述作為參數(shù)傳遞給type,會返回一個類。(我知道,同一個函數(shù)根據(jù)傳參的不同而展示出兩種完全不同的功能很不合理,不過這是為了Python的向后兼容。)
type如何創(chuàng)建類:

type(類名,
     父類元祖 (可為空),
     包含鍵值對屬性的字典)

舉例:

>>> class MyShinyClass(object):
...       pass

上面這個MyShinyClass類可以用以下方法手動創(chuàng)建:

>>> MyShinyClass = type("MyShinyClass", (), {}) # 返回一個類
>>> print(MyShinyClass)

>>> print(MyShinyClass()) # 創(chuàng)建一個類對象
<__main__.MyShinyClass object at 0x8997cec>

應(yīng)該注意到了,使用"MyShinyClass"作為類名,也將其作為一個變量名,賦值為類的引用。類名和變量名是可以不同的,但是沒必要把事情搞復(fù)雜。
type可以接受一個定義類屬性的字典作為參數(shù):

>>> 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)  # 利用實(shí)例打印類屬性
True

當(dāng)然,也可以作為基類,給其他類繼承:

>>>   class FooChild(Foo):
...         pass

以上代碼等同于:

>>> FooChild = type("FooChild", (Foo,), {})
>>> print(FooChild)

>>> print(FooChild.bar) # bar屬性繼承自類Foo
True

你肯定還想為類添加方法。只需要定義一個名稱合理的函數(shù),并將這個函數(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

在動態(tài)創(chuàng)建一個類之后,為這個類添加更多的方法,就像為一個正常創(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中,類也是對象,可以動態(tài)地創(chuàng)建類。當(dāng)使用關(guān)鍵字class時(shí),Python使用元類像這樣創(chuàng)建類的。

什么是元類(終于講到了)

元類就是創(chuàng)建類的“東西”。我們定義類是為了創(chuàng)建對象,是吧?但是我們認(rèn)識到在Python中類也是對象,而元類就是創(chuàng)建類這種對象(類)的,它們是類的類,你可以這樣理解:

MyClass = MetaClass()
MyObject = MyClass()

你已經(jīng)看到type可以讓你做如下操作:

MyClass = type("MyClass", (), {})

這是因?yàn)閠ype方法實(shí)際上是一個元類。事實(shí)上,type是Python中用于創(chuàng)建所有類的元類。不過現(xiàn)在你一定很奇怪為什么這個類名首字母是小寫,而不是Type?我猜這是同str保持一致,str是用來創(chuàng)建string對象的類,int是用來創(chuàng)建integer對象的類,type則是創(chuàng)建類對象的類。
在Python中,一切,對就是一切,都是對象。包括整數(shù)、字符串、函數(shù)和類。所有東西都是對象,它們都是創(chuàng)建自某個類。
通過__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__

那么,一個__class__的__class__屬性是什么呢?

>>> age.__class__.__class__

>>> name.__class__.__class__

>>> foo.__class__.__class__

>>> b.__class__.__class__

由此可見:元類確實(shí)就是創(chuàng)建類對象的東西。如果你覺得合適你就可以稱之為“類工廠”。type是Python使用的內(nèi)置元類,當(dāng)然,你也可以自己創(chuàng)建元類。

__metaclass__屬性

編寫一個類時(shí)添加上__metaclass__屬性:

class Foo(object):
    __metaclass__ = something...
    [...]

如果你像上面這樣做,Python就會使用元類創(chuàng)建一個Foo類。
要當(dāng)心了,這里有些小圈套。你先寫下了“class Foo(object)”,但此時(shí)內(nèi)存中還沒有創(chuàng)建Foo類對象。Python會在類的聲明中尋找屬性__metaclass_,如果找到了就會使用其創(chuàng)建Foo類;如果沒有,會使用type創(chuàng)建這個類。下面這段文字要多讀幾遍。
當(dāng)你編寫以下代碼時(shí):

class Foo(Bar):
    pass

Python做了這些事情:

在類Foo中有定義__metaclass__屬性嗎?
如果有,則繼續(xù);
如果沒有,Python會在模塊層尋找__metaclass__屬性(這只針對沒有繼承任何其他類的情況);
如果模塊層也沒有,則會在Bar(第一個父類)中尋找(這就有可能是內(nèi)置的type)。
這樣找到__metaclass__后,使用它在內(nèi)存中創(chuàng)建名稱為Foo的類對象(這邊跟上,一個類對象)

需要注意的是,__metaclass__屬性不會被繼承,但是父類的元類(Bar.__class__)可以被繼承:如果Bar的__metaclass__屬性定義了使用type()(不是type.__new())創(chuàng)建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 這邊不太理解
現(xiàn)在有個新問題,你可以賦什么值給__metaclass__?
答案是:可以創(chuàng)建一個類的東西。
那什么可以創(chuàng)建類?type、子類化type或者使用type的東西。

自定義元類

元類的主要目的,是在創(chuàng)建類的時(shí)候動態(tài)地改變類。通常你會想創(chuàng)建符合當(dāng)前上下文的類供API使用。舉一個簡單的例子,當(dāng)你希望一個模塊中所有的類屬性都是小寫時(shí),有幾種方法可以實(shí)現(xiàn),其中有一種方法就是在模塊層設(shè)置__metaclass__。使用這種方法,這個模塊中所有的類都將使用此元類創(chuàng)建,我們只需要使元類將所有類屬性置為小寫。
幸運(yùn)的是,__metaclass__可以被任意調(diào)用,不一定非要是一個正式的類(我知道,名稱包含“class”不一定非要是一個類,搞清楚了...這很有用)。
現(xiàn)在先用一個函數(shù)舉一個簡單的例子:

# 元類會自動獲取通常傳給`type`的參數(shù)
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"創(chuàng)建類
    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"

現(xiàn)在,完成同樣的功能,但是為元類定義一個真實(shí)的類:

# 記住`type`實(shí)際上是一個像`str`和`int`的類,可以用于被繼承
class UpperAttrMetaclass(type):
    # __new__放在__init__之前調(diào)用,此方法創(chuàng)建對象并反回
    # 而__init__則是初始化作為參數(shù)傳遞給此方法的對象
    # 除非你想控制如何創(chuàng)建一個對象,否則很少用到__new__
    # 在這里,被創(chuàng)建的對象是類,而我們想自定義這個類,所以重寫了__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)

這不是真正的面向?qū)ο螅∣OP),這里直接調(diào)用了type,沒有重寫或者調(diào)用父類的__new__?,F(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,沒什么深奧的
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

你大概發(fā)現(xiàn)了傳給type的額外的參數(shù)upperattr_metaclass。這沒什么奇怪的:__new__的第一個參數(shù)總是其定義的類。就像類方法中第一個參數(shù)總是self。當(dāng)然,為了清晰期間,這里我起的名字比較長,但是像self這樣的參數(shù)通常有一個傳統(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ù)雜的原因不在于元類,而在于你通常會依靠自省、操縱繼承、__dict__變量等,使用元類做一些晦澀的事情。(it"s because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.)TODO
元類來用于黑魔法時(shí)的確特別有用,因?yàn)橐矔⑹虑楦愕煤軓?fù)雜。但就其本身而言,是簡單的:

攔截一個類的創(chuàng)建

修改類

返回修改的類

為什么會用元類代替函數(shù)?

既然__metaclass__可以被任意調(diào)用,為什么要使用明顯更復(fù)雜的類呢?有這樣一些理由:

意圖明顯。當(dāng)你看到UpperAttrMetaclass(type),你知道接下來會發(fā)生什么。

可以使用OOP。元類可以繼承自元類,重寫父類的方法,元類甚至可以使用元類。

如果為一個類指定的元類是類而不是方法,這個類的子類將是元類的一個實(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í)肯定不會僅想像上面舉的例子那樣簡單,通常是用于比較復(fù)雜的場景。將多個方法組織在一個類中有益于使代碼更容易閱讀。

你可以使用__new__,__init__ 和 __call__,這些方法可以處理不用的事情。即使很多時(shí)候你可以在__new__中完成所有工作,當(dāng)然,一些人會更習(xí)慣用__init__。

這些東西叫 “metaclass”,小心了!這一定很難搞!

為什么使用元類

好了,現(xiàn)在的問題是:為什么要是用這樣晦澀且容易出錯的特性?其實(shí),通常你不會用:

元類是99%的用戶根本不必操心的深度魔法。如果你在考慮是否需要使用,那就不要用(真正需要的用戶很清楚他們的需求,根本不需要解釋為什么要使用元類) 
Python領(lǐng)袖 Tim Peters

使用元類的一個主要場景是創(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)

它不會返回一個IntegerField的對象,而是返回一個整數(shù),甚至可以從數(shù)據(jù)庫中直接獲取數(shù)據(jù)。TODO
這是因?yàn)?,在models.Model中定義了__metaclass__,使用了一些魔法將你定義的簡單的Person類轉(zhuǎn)換為一個復(fù)雜的數(shù)據(jù)庫掛鉤。(turn the Person you just defined with simple statements into a complex hook to a database field.)TODO

Django使用元類對外提供簡單的API,簡化了一些復(fù)雜的東西,API中重建的代碼會去完成幕后真正的工作。

結(jié)語

首先,類是可以創(chuàng)建實(shí)例的對象。類本身是元類的對象:

>>> class Foo(object): pass
>>> id(Foo)
142630324

在Python中,除了type,一切皆對象,一切都是類或者元類的對象。事實(shí)上type是自己的元類,這在純Python中這是無法實(shí)現(xiàn)的,這里在實(shí)現(xiàn)層上做了一些手段。
其次,元類是復(fù)雜的。你可能不希望對非常簡單的類使用元類,那么還有其他兩種手段用來改變類:

monkey patching

類裝飾器

如果你需要改變類,99%的情況下使用這兩種方法。
但其實(shí)98%的情況你根本不需要改變類。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/44599.html

相關(guān)文章

  • How does it work - with_metaclass

    摘要:先簡單介紹下中的元類。元類就是創(chuàng)建類的類,對于元類來說,類是它的實(shí)例,將返回。中的所有類,都是的實(shí)例,換句話說,是元類的基類。 我在看源代碼的時(shí)候,經(jīng)常蹦出這一句:How does it work!竟然有這種操作?本系列文章,試圖剖析代碼中發(fā)生的魔法。順便作為自己的閱讀筆記,以作提高。 先簡單介紹下Python中的元類(metaclass)。元類就是創(chuàng)建類的類,對于元類來說,類是它的實(shí)...

    testbird 評論0 收藏0
  • python 類和元類(metaclass)的理解和簡單運(yùn)用

    摘要:什么是元類剛才說了,元類就是創(chuàng)建類的類。類上面的屬性,相信愿意了解元類細(xì)節(jié)的盆友,都肯定見過這個東西,而且為之好奇。使用了這個魔法方法就意味著就會用指定的元類來創(chuàng)建類了。深刻理解中的元類 (一) python中的類 今天看到一篇好文,然后結(jié)合自己的情況總結(jié)一波。這里討論的python類,都基于python2.7x以及繼承于object的新式類進(jìn)行討論。 首先在python中,所有東西都...

    zhangqh 評論0 收藏0
  • [譯]什么是元類metaclass?

    摘要:如果還是沒有找到,就會使用父類中的元類來創(chuàng)建類。元類通常用于處理比較復(fù)雜的情況。這是因?yàn)槭褂昧嗽?,它會將中定義的字段轉(zhuǎn)換成數(shù)據(jù)庫中的字段。中所有數(shù)據(jù)類型都是對象,它們要么是類的實(shí)例要么是元類的實(shí)例。 原文地址:what is metaclass in Python?我的簡書地址::nummy 類即對象 在理解元類之前,需要先掌握Python中的類,Python中類的概念與SmallT...

    zsirfs 評論0 收藏0
  • Python: 陌生的 metaclass

    摘要:但一般情況下,我們使用類作為元類。那么,元類到底有什么用呢要你何用元類的主要目的是為了控制類的創(chuàng)建行為。當(dāng)然,有很多種做法,這里展示用元類的做法。當(dāng)你創(chuàng)建類時(shí),解釋器會調(diào)用元類來生成它,定義一個繼承自的普通類意味著調(diào)用來創(chuàng)建它。 元類 Python 中的元類(metaclass)是一個深度魔法,平時(shí)我們可能比較少接觸到元類,本文將通過一些簡單的例子來理解這個魔法。 類也是對象 在 Py...

    miya 評論0 收藏0
  • 由type()函數(shù)對類和實(shí)例使用結(jié)果差異而引出的一個問題

    摘要:但是隨后有人提出反對意見并說這個是隨后搜索到這篇文章深刻理解中的元類里面介紹了如何使用函數(shù)創(chuàng)建一個類,并解釋了屬性。 有如下代碼 #-*-coding:utf-8-*- class a(): pass a1 = a() print(type(a),type(a1)) 兩個python版本分別為Python2.7.11Python3.5.1 在python2中得到的結(jié)果(, )a...

    zhangwang 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<