1. 程式人生 > >python的metaclass(元類)

python的metaclass(元類)

類也是物件

在理解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) # 你可以列印一個類,因為它同時也是物件
<class '__main__.ObjectCreator'>

>>> def echo(o):
...     print(o)
...
>>> echo(ObjectCreator) # 作為引數傳值給函式
<class '__main__.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
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

這很容易理解吧。但是,這並不那麼動態啊。我們還是需要自己來寫這個類的程式碼。

既然類也是物件,那就應該有用來產生它的東西。這東西就是type。

先來說說你所認識的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居然會有兩個完全不同的作用,這很愚蠢。不過python這樣做是為了保持向後相容性。)

下面舉例type建立類的用法。首先,對於類一般是這麼定義的:

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

在下面,MyShinyClass也可以這樣子被創建出來,並且跟上面的建立方法有一樣的表現:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.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__
<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__屬性.

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 去做一些晦澀的事情,比如, 依賴於自省,控制繼承等等。

相關推薦

【原創】Python 對象創建過程中, __new__, __call__, __init__ 的處理

diff regular luci 自定義 weight ica 一般來說 att ray 原始type: type是最原始的元類,其__call__方法是在你使用" t_class = type(classname_string, base_classes_tuple,

5.1 編程語言的基

屬性 long 測試 byte 發生 代碼 arr dsi 算數運算 編譯器直接支持的數據類型成為基元類型(primitive type)。基元類型直接映射到 Framework類庫(FCL)中存在的類型。 int a = 0; // Most convenient

C++之友函數和友

res con 形參 display tle private 一點 second main 通過friend關鍵字,我們可以將不屬於當前類的一個函數在當前類中加以聲明,該函數便可以成為當前類的友元函數。#include<iostream>using namesp

函數友.

log mod http eache src con 類對象 之間 封裝 友元能夠理解為是類的“朋友”。它能夠訪問類的保護和私有成員。友元的作用在於提高程序的執行效率,可是,它破壞了類的封裝性和隱藏性。友元能夠是一個函數,該函數被稱為友元函數;友元也能夠是一個類

關於的一些使用心得

col 作業 數據 如果 屬性信息 一個 mod 魔法 分析 作廖老師的Python實戰作業時,對元類這個魔法方法有些小心得。 元類這個黑魔法和linux中root都是bug般的存在,不想糾纏概念性東西,試著從Python解釋器的角度分析下元類的執行過程。 其實,元類的主要

python 的簡單解釋

.html www ren 有用 copy tle 例子 sky -i 本文轉自博客:http://www.cnblogs.com/piperck/p/5840443.html 作者:piperck python 類和元類(metaclass)的理解和簡單運用 (一) p

廖雪峰Python學習筆記——使用

ram form 創建對象 字典 comm params int name 學習筆記 元類(MetaClasses) 元類提供了一個改變Python類行為的有效方式。 元類的定義是“一個類的類”。任何實例是它自己的類都是元類。 class demo(object):

python中的(metaclass)

優先 裝飾器 target {} pass get tac 搜索 items 認識python中元類的準備工作。 1,首先需要明白一個概念就是python中一切皆為對象。 input: class Trick(object): pass print type(‘1234

C# 基

多人 sig float 說話 cal img tac com 編程 C#編程中,初始化一個整數有兩種方式: (1)、較繁瑣的方法,代碼如下: Int32 a = new Int32(); (2)、極簡的方法,代碼如下: int a=0; 對比兩種方法,分析如下:

Django-models class Meta:

設置 div rmi 應用 默認值 htm 字段排序 als 簡單 Django模型之Meta選項詳解 Model 元數據就是 "不是一個字段的任何數據" -- 比如排序選項, admin 選項等等. Django模型類的Meta是一個內部類,它用於定義一些Djan

python面向對象( item系列,__enter__ 和__exit__,__call__方法,

屬性 eba callable 好處 繼承方式 類的創建 完成 __weak 依次 python面向對象進階(下) item系列 __slots__方法 __next__ 和 __iter__實現叠代器 析構函數 上下文管理協議 元類一

【Python】【編程】【三】【

無法使用 import iter 時也 food ini lin abc __init__ ‘‘‘# str、 type 和 LineItem 是object 的子類 str、 object 和 LineItem 是 type 的實例,因為它們都是類object 類和

【練習】友和友函數

turn main spa std return col print pri circle #include <iostream> using namespace std; class Circle; class Point { private: fl

Python

tac sorted this result init one ati handler ... http://ningning.today/2017/01/25/python/simple-python-metaclass/ https://stackoverflow.co

Python之Metaclass詳解,Python之

turned 除了 方法 寫法 找到 類對象 global 所在 code 本人Java程序員一枚,這幾天閑來無事就自學了下Python,學到Metaclass感覺有點迷惑,就在網上查相關資料,在棧溢出(stackoverflow)網站上看到一個關於metaclass的回答

相關(type & metaclass)

type 不同 ans -s family 1.2 的人 elf 創建 """metaclass作用: 1) 攔截類的創建 2) 修改類 3) 返回修改之後的類 """ """為什麽要用metaclass類而不

metaclass

add 手動 isp aps pos sel list 方式 base 閱讀目錄 一 知識儲備 二 引子(類也是對象) 三 什麽是元類? 四 創建類的兩種方式 五 自定義元類控制類的行為 六 練習題 一 知識儲備 exec:三個參數 參數一:字符串形式的

C++中的友函數和友

pan string 拷貝構造函數 student 私有 oid 一個 each cor 友元函數可以修改類的私有屬性,寫在類的public/private/protected底下都可以。友元函數的函數體寫在類的外面時,寫法和普通函數一樣,不需要加friend關鍵字,但函數

python全棧開發基礎【補充】metaclass(

認識 全棧 rgs bubuko class a alt 創建 繼承 圖片 一、創建類的執行流程 二、元類的認識 什麽是元類呢?在Python3中繼承type的就是元類 二、元類的示例 # 方式一 class MyType(type): ‘‘‘繼承type

type

body 運行 賦值 ini 展示 rgs tac bject meta 自定義元類來創建類 python中的類也是對象,是type類的對象 在編譯器運行到class Foo的時候自動運行Foo = type(‘Foo‘,{object,},{})來創建類對象並放入內存中