1. 程式人生 > >Python之Metaclass詳解,Python之元類

Python之Metaclass詳解,Python之元類

turned 除了 方法 寫法 找到 類對象 global 所在 code

本人Java程序員一枚,這幾天閑來無事就自學了下Python,學到Metaclass感覺有點迷惑,就在網上查相關資料,在棧溢出(stackoverflow)網站上看到一個關於metaclass的回答,感覺回答很不錯,解決的自己的疑惑,閑的蛋疼就翻譯了一下。

原貼寫的很好,尤其看得時候感覺循序漸進,由淺入深,很適合我這種小白用戶。但翻的時候才感覺,自己真是閑的蛋疼,回答簡直太長了,無奈都翻了這麽多,不能半途而非,咬牙翻完,希望能幫到其他道友。

貼上原文地址:https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python 裏邊點贊最高的回答即是。

第一次翻譯技術貼,水平一般,大家多指正。

關於Metaclass

類就是對象

1、借鑒於Smalltalk語言。borrowed from the Smalltalk language.
2、大多數語言中類是用來定義如何創建對象的代碼段, Python中類也有同樣作用,但不止於此:類也是對象    

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

在python中只要用了class關鍵字,python就執行它(class)在內存中創建對象,以上代碼結束後,python在內存中創建了一個名為ObjectCreator的對象
這個對象和普通對象有所不同:這個對象本身能創建其他對象(實例),所以這個對象也是一個類
但這個對象依然是對象,因此你能:
1)將他賦值給一個變量
2)拷貝他
3)給他設置屬性
4)將他當做方法的參數傳遞    

>>> print(ObjectCreator) # 打印此對象
<class __main__.ObjectCreator>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # 將此對象當做方法的參數
<class __main__.ObjectCreator>
>>> print(hasattr(ObjectCreator, new_attribute))
False
>>> ObjectCreator.new_attribute = 
foo # 給此對象添加屬性 >>> 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關鍵字創建一個class

>>> def choose_class(name):
...     if name == foo:
...         class Foo(object):
...             pass
...         return Foo # 返回Foo類,而不是對象
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class(foo)
>>> print(MyClass) # 方法返回類,不返回對象
<class __main__.Foo>
>>> print(MyClass()) # 可以通過方法返回的類創建對象
<__main__.Foo object at 0x89c6d4c>

但這並不是動態創建的,因為你仍然需要自己寫出整個class
既然類是對象,那麽類對象必須是由其他什麽東西生成。當你用class關鍵字時,Python自動為你創建類對象,但就像Python中其他大部分things一樣,Python提供了手動創建類對象的方式。
那就是type,它能告訴你一個對象的類型,如下代碼

>>> print(type(1))
<type int>
>>> print(type("1"))
<type str>
>>> print(type(ObjectCreator))
<type type>
>>> print(type(ObjectCreator()))
<class __main__.ObjectCreator>


type還有一個完全不同的功能,它還能運行中創建類對象,type方法能將"類的描述信息"做為參數,並返回類對象(我知道同一個方法針對不同的參數提供不同功能這種方式很笨)
type這樣使用

type(類對象的名稱, 元組形式的所有父類對象 (繼承用,可以為空),包含屬性名和屬性值的字典)


例如

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


可以如下描述

>>> MyShinyClass = type(MyShinyClass, (), {}) # 返回一個類對象
>>> print(MyShinyClass)
<class __main__.MyShinyClass>
>>> print(MyShinyClass()) # 通過返回的類對象創建一個對象
<__main__.MyShinyClass object at 0x8997cec>


你應該註意到,我用“MyShinyClass”做接收類對象的變量名和類對象的名稱,這兩者是可以不同的。
type接收一個字典來定義類的屬性,如下

>>> class Foo(object):
...       bar = True


可以這樣描述

>>> Foo = type(Foo, (), {bar:True})


它能像正常類一樣使用

>>> print(Foo)
<class __main__.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)
<class __main__.FooChild>
>>> print(FooChild.bar) # bar is inherited from Foo
True


如果你想要給類添加方法,只要定義一個合適的方法並將其指定給類對象的一個屬性即可,如下

>>> 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


你甚至可以在類對象被動態創建後各類添加方法,就像給其他普通(通過class關鍵字創建的)類對象添加方法一樣:

>>> 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做的事情,metaclass也是這樣

那到底什麽是metaclass呢(終於!!!)

Metaclasses是創建class的"東西"
你定義類是為了創建對象對吧? 但是我們已經知道,Python裏類就是對象。Metaclass呢,就是創建這種對象(類)的東西.他們是描述類的類,你可以將Metaclass想象成以下形式:

MyClass = MetaClass()
MyObject = MyClass()


你已經知道,type能讓你做如下事情:

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


這是因為方法type實際上是一個Metaclass,type是Python用來創建其他類的Metaclass。
你可以通過__class__屬性來驗證這一點
也就是說type就是用來創建其他class的class
在Python中一切皆對象,一切皆對象,包括int str function(方法) class(類),所有一切都是對象,而且他們都是有某一個類創建

>>> age = 35
>>> age.__class__
<type int>
>>> name = bob
>>> name.__class__
<type str>
>>> def foo(): pass
>>> foo.__class__
<type function>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class __main__.Bar>


那麽所有__class__的__class__是什麽呢?

>>> age.__class__.__class__
<type type>
>>> name.__class__.__class__
<type type>
>>> foo.__class__.__class__
<type type>
>>> b.__class__.__class__
<type type>


所以一個metaclass就是用來創建類對象的東西,你可以稱之為“類工廠”。而type就是Python內建的metaclass(類工廠),當然你能定義你自己的metaclass

__metaclass__屬性

當你創建類時,可以給其添加__metaclass__屬性
python2.x的寫法

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


python3.x的寫法

class Foo(object, metaclass=something...):
    [...]


這樣做Python會通過你指定的metaclass創建Foo類
下邊這點稍難理解,請註意
在class Foo(object),這時內存中還沒有創建Foo類對象。Python會先在類定義代碼中找__metaclass__屬性,找到的話就用你指定的metaclass創建Foo類對象,找不到就通過type創建Foo類對象。
當執行下邊代碼時:

class Foo(Bar):
    pass


Python會:
1、在Foo中有沒有指定__metaclass__屬性
2、如果找到,就會通過__metaclass__指定的東西創建Foo類對象(註意此處是類對象)
3、如果找不到,就會去MODULE(模塊)級別去找,並創建Foo類對象(但只適用於沒有繼承任何父類的類,基本上就是老式類)
4、如果都找不到,就會通過Bar(第一個父類)的metaclass(很有可能是type)來創建Foo類對象,
5、這裏要註意__metaclass__不會被繼承,而父類的__class__(Bar.__class__)會被子類繼承.即:如果Bar用了__metaclass__屬性通過type()而不是type.__new__()創建Bar類對象,那麽子類不會繼承這種行為。
現在問題是:可以將什麽指定給__metaclass__。
答案是:能創建類的東西。
什麽能創建類呢?type type的子類 和所有使用type的

自定義metaclass

metaclass的主要作用就是創建類時使類發生變化
通常做API時才會用到,就是創建和上下文相吻合的類(這句翻的太爛)。
舉個例子:你決定所有模塊下的類的屬性都要大寫,有很多種方法實現此需求,其中一種就是在module級別設置__metaclass__屬性。
這樣一來,此模塊下所有類都通過指定的metaclass創建,你只需要告訴metaclass將所有屬性大寫即可。
幸運的是__metaclass__只要可被調用即可,它不必是一個類。
所以我們先舉個例子,用一個方法做metaclass

# the metaclass will automatically get passed the same argument that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
        Return a class object, with the list of its attribute turned
        into uppercase.
    """

    # pick up any attribute that doesn‘t start with ‘__‘ and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
            if not name.startswith(__):
                    uppercase_attr[name.upper()] = val
            else:
                    uppercase_attr[name] = val

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

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‘


現在,我們用一個類做metaclass來實現

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it‘s the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won‘t
    # see this
    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)
    


上面的方式不是很"面向對象",因為我們直接調用了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

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)


你可能已經註意到了參數"upperattr_metaclass",這個參數並沒有什麽特殊的,__new__方法總是以所在類中的類對象作為第一個參數,就像普通方法中的self參數一樣,將實例作為第一個參數。
當然我用的名字都見名知意,但就像self,所有的參數也有簡短的名字,所以一個真正的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關鍵字使類更清晰:

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)


就這樣。除此之外真的沒有其他關於metaclass的信息了。
The reason behind the complexity of the code using metaclasses is not because of metaclasses, it‘s because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.(這句不太理解,先把原文放上)
實際上,metaclass在做黑魔法時尤其遊泳,也就是做復雜的事情。至於metaclass本身,還是很簡單的:
1、攔截類的生成
2、修改類
3、返回修改後的類

為什麽用metaclass而不用方法呢?

既然__metaclass__能接收一切可被調用的東西,為什麽要用class呢,class可是比方法復雜的多啊!!!
主要是以下幾個原因:
1、目的更明確。你看到UpperAttrMetaclass(type),就知道是什麽意思
2、能用"面向對象編程",metaclass可以繼承metaclass可以重寫父類方法,甚至可以使用其他metaclass。
3、某個類如果你指定了metaclass,則它的子類是這個metaclass的實例,但如果指定了metaclass為function則不是(一個類不能是某個方法的實例)。
4、代碼結構會更好。上面使用metaclass的例子已經非常簡單了,通過使用metaclass的情況都是比較復雜的。將所有方法組織在一個class中,能使你的代碼更易讀。
5、可以在__new__,__init__和__call__中做你想做的事,雖然你能都在__new__寫,但有些人還是習慣在__init_中實現。
6、叫metaclass,該死的,這名字還是有些意義的。

什麽情況下需要用到metaclass

現在的主要問題是,為什麽要用這麽晦澀難懂又容易出錯的metaclass呢
Well,通常你是不需要用的:
Metaclass是深層魔法(deeper magic),99%的用戶都不需要關心它。如果你好奇什麽時候會用到它:你不需要用它。(真正確定自己需要用到metaclass的人,他們不需要知道用它的原因)
Python Guru Tim Peters
metaclass的主要用於創建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對象,而返回int,甚至是直接在數據庫中取出的數據。
這可能是因為,models.Model定義了__metaclass__,而指向的metaclass將你定義的簡單的Person類變為數據庫中的一個字段。
利用metaclass,Django暴露一套API使得復雜的事情看起來很簡單,而在幕後做真正的工作。

最後

首先,類就是能創建實例的對象。實際上,類是metaclass的實例

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


在Python中一切都是對象(實例),他們不是類的實例就是metaclass的實例。
除了type。type的metaclass是自己,實際上在純Python中是不能創建type的,而是通過在Python的實現層作弊實現的。
其次,metaclass是很復雜的。如果你只要對類做一點點調整,就不要使用metaclass,你可以用另外兩種技術來實現:
猴子補丁(monkey patching)
裝飾器
當你需要對類做調整,99%的情況下你可以通過以上兩種方式實現。
但是98%的情況是:你完全不需要對類做調整


Python之Metaclass詳解,Python之元類