在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 陌生的 metaclass
基礎(chǔ)
itertools
HTTP 服務(wù)
hashlib
閉包
文件和目錄
單元測試
使用 @property
標(biāo)準(zhǔn)模塊
陌生的 metaclass
Base64
進(jìn)程、線程和協(xié)程
讀寫二進(jìn)制文件
匿名函數(shù)
輸入和輸出
Click
元組
字符編碼
partial 函數(shù)
參考資料
collections
協(xié)程
類和實(shí)例
Python 之旅
定制類和魔法方法
常用數(shù)據(jù)類型
繼承和多態(tài)
ThreadLocal
HTTP 協(xié)議簡介
Requests 庫的使用
讀寫文本文件
列表
os 模塊
迭代器 (Iterator)
正則表達(dá)式
集合
上下文管理器
異常處理
你不知道的 super
定義函數(shù)
datetime
資源推薦
字典
slots 魔法
hmac
第三方模塊
進(jìn)程
類方法和靜態(tài)方法
函數(shù)參數(shù)
高階函數(shù)
函數(shù)
re 模塊
高級(jí)特性
線程
argparse
生成器
結(jié)束語
字符串
map/reduce/filter
函數(shù)式編程
Celery
裝飾器

陌生的 metaclass

Python 中的元類(metaclass)是一個(gè)深度魔法,平時(shí)我們可能比較少接觸到元類,本文將通過一些簡單的例子來理解這個(gè)魔法。

類也是對象

在 Python 中,一切皆對象。字符串,列表,字典,函數(shù)是對象,類也是一個(gè)對象,因此你可以:

  • 把類賦值給一個(gè)變量
  • 把類作為函數(shù)參數(shù)進(jìn)行傳遞
  • 把類作為函數(shù)的返回值
  • 在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建類

看一個(gè)簡單的例子:

class Foo(object):
    foo = True

class Bar(object):
    bar = True

def echo(cls):
    print cls

def select(name):
    if name == 'foo':
        return Foo        # 返回值是一個(gè)類
    if name == 'bar':
        return Bar

>>> echo(Foo)             # 把類作為參數(shù)傳遞給函數(shù) echo
<class '__main__.Foo'>
>>> cls = select('foo')   # 函數(shù) select 的返回值是一個(gè)類,把它賦給變量 cls
>>> cls
__main__.Foo

熟悉又陌生的 type

在日常使用中,我們經(jīng)常使用 object 來派生一個(gè)類,事實(shí)上,在這種情況下,Python 解釋器會(huì)調(diào)用 type 來創(chuàng)建類。

這里,出現(xiàn)了 type,沒錯(cuò),是你知道的 type,我們經(jīng)常使用它來判斷一個(gè)對象的類型,比如:

class Foo(object):
    Foo = True

>>> type(10)
<type 'int'>
>>> type('hello')
<type 'str'>
>>> type(Foo())
<class '__main__.Foo'>
>>> type(Foo)
<type 'type'>

事實(shí)上,type 除了可以返回對象的類型,它還可以被用來動(dòng)態(tài)地創(chuàng)建類(對象)。下面,我們看幾個(gè)例子,來消化一下這句話。

使用 type 來創(chuàng)建類(對象)的方式如下:

type(類名, 父類的元組(針對繼承的情況,可以為空),包含屬性和方法的字典(名稱和值))

最簡單的情況

假設(shè)有下面的類:

class Foo(object):
    pass

現(xiàn)在,我們不使用 class 關(guān)鍵字來定義,而使用 type,如下:

Foo = type('Foo', (object, ), {})    # 使用 type 創(chuàng)建了一個(gè)類對象

上面兩種方式是等價(jià)的。我們看到,type 接收三個(gè)參數(shù):

  • 第 1 個(gè)參數(shù)是字符串 'Foo',表示類名
  • 第 2 個(gè)參數(shù)是元組 (object, ),表示所有的父類
  • 第 3 個(gè)參數(shù)是字典,這里是一個(gè)空字典,表示沒有定義屬性和方法

在上面,我們使用 type() 創(chuàng)建了一個(gè)名為 Foo 的類,然后把它賦給了變量 Foo,我們當(dāng)然可以把它賦給其他變量,但是,此刻沒必要給自己找麻煩。

接著,我們看看使用:

>>> print Foo
<class '__main__.Foo'>
>>> print Foo()
<__main__.Foo object at 0x10c34f250>

有屬性和方法的情況

假設(shè)有下面的類:

class Foo(object):
    foo = True
    def greet(self):
        print 'hello world'
        print self.foo

type 來創(chuàng)建這個(gè)類,如下:

def greet(self):
    print 'hello world'
    print self.foo

Foo = type('Foo', (object, ), {'foo': True, 'greet': greet})

上面兩種方式的效果是一樣的,看下使用:

>>> f = Foo()
>>> f.foo
True
>>> f.greet
<bound method Foo.greet of <__main__.Foo object at 0x10c34f890>>
>>> f.greet()
hello world
True

繼承的情況

再來看看繼承的情況,假設(shè)有如下的父類:

class Base(object):
    pass

我們用 Base 派生一個(gè) Foo 類,如下:

class Foo(Base):
   foo = True

改用 type 來創(chuàng)建,如下:

Foo = type('Foo', (Base, ), {'foo': True})

什么是元類(metaclass)

元類(metaclass)是用來創(chuàng)建類(對象)的可調(diào)用對象。這里的可調(diào)用對象可以是函數(shù)或者類等。但一般情況下,我們使用類作為元類。對于實(shí)例對象、類和元類,我們可以用下面的圖來描述:

類是實(shí)例對象的模板,元類是類的模板

+----------+             +----------+             +----------+
|          |             |          |             |          |
|          | instance of |          | instance of |          |
| instance +------------>+  class   +------------>+ metaclass|
|          |             |          |             |          |
|          |             |          |             |          |
+----------+             +----------+             +----------+

我們在前面使用了 type 來創(chuàng)建類(對象),事實(shí)上,type 就是一個(gè)元類。

那么,元類到底有什么用呢?要你何用...

元類的主要目的是為了控制類的創(chuàng)建行為。我們還是先來看看一些例子,以消化這句話。

元類的使用

先從一個(gè)簡單的例子開始,假設(shè)有下面的類:

class Foo(object):
    name = 'foo'
    def bar(self):
        print 'bar'

現(xiàn)在我們想給這個(gè)類的方法和屬性名稱前面加上 my_ 前綴,即 name 變成 my_name,bar 變成 my_bar,另外,我們還想加一個(gè) echo 方法。當(dāng)然,有很多種做法,這里展示用元類的做法。

1.首先,定義一個(gè)元類,按照默認(rèn)習(xí)慣,類名以 Metaclass 結(jié)尾,代碼如下:

class PrefixMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # 給所有屬性和方法前面加上前綴 my_
        _attrs = (('my_' + name, value) for name, value in attrs.items())  

        _attrs = dict((name, value) for name, value in _attrs)  # 轉(zhuǎn)化為字典
        _attrs['echo'] = lambda self, phrase: phrase  # 增加了一個(gè) echo 方法

        return type.__new__(cls, name, bases, _attrs)  # 返回創(chuàng)建后的類

上面的代碼有幾個(gè)需要注意的點(diǎn):

  • PrefixMetaClass 從 type 繼承,這是因?yàn)?PrefixMetaclass 是用來創(chuàng)建類的
  • __new__ 是在 __init__ 之前被調(diào)用的特殊方法,它用來創(chuàng)建對象并返回創(chuàng)建后的對象,對它的參數(shù)解釋如下:
    • cls:當(dāng)前準(zhǔn)備創(chuàng)建的類
    • name:類的名字
    • bases:類的父類集合
    • attrs:類的屬性和方法,是一個(gè)字典

2.接著,我們需要指示 Foo 使用 PrefixMetaclass 來定制類。

在 Python2 中,我們只需在 Foo 中加一個(gè) __metaclass__ 的屬性,如下:

class Foo(object):
    __metaclass__ = PrefixMetaclass
    name = 'foo'
    def bar(self):
        print 'bar'

在 Python3 中,這樣做:

class Foo(metaclass=PrefixMetaclass):
    name = 'foo'
    def bar(self):
        print 'bar'

現(xiàn)在,讓我們看看使用:

>>> f = Foo()
>>> f.name    # name 屬性已經(jīng)被改變
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-774-4511c8475833> in <module>()
----> 1 f.name

AttributeError: 'Foo' object has no attribute 'name'
>>>
>>> f.my_name
'foo'
>>> f.my_bar()
bar
>>> f.echo('hello')
'hello'

可以看到,F(xiàn)oo 原來的屬性 name 已經(jīng)變成了 my_name,而方法 bar 也變成了 my_bar,這就是元類的魔法。

再來看一個(gè)繼承的例子,下面是完整的代碼:

class PrefixMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # 給所有屬性和方法前面加上前綴 my_
        _attrs = (('my_' + name, value) for name, value in attrs.items())  

        _attrs = dict((name, value) for name, value in _attrs)  # 轉(zhuǎn)化為字典
        _attrs['echo'] = lambda self, phrase: phrase  # 增加了一個(gè) echo 方法

        return type.__new__(cls, name, bases, _attrs)

class Foo(object):
    __metaclass__ = PrefixMetaclass   # 注意跟 Python3 的寫法有所區(qū)別
    name = 'foo'
    def bar(self):
        print 'bar'

class Bar(Foo):
    prop = 'bar'

其中,PrefixMetaclass 和 Foo 跟前面的定義是一樣的,只是新增了 Bar,它繼承自 Foo。先讓我們看看使用:

>>> b = Bar()
>>> b.prop     # 發(fā)現(xiàn)沒這個(gè)屬性
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-778-825e0b6563ea> in <module>()
----> 1 b.prop

AttributeError: 'Bar' object has no attribute 'prop'
>>> b.my_prop
'bar'
>>> b.my_name
'foo'
>>> b.my_bar()
bar
>>> b.echo('hello')
'hello'

我們發(fā)現(xiàn),Bar 沒有 prop 這個(gè)屬性,但是有 my_prop 這個(gè)屬性,這是為什么呢?

原來,當(dāng)我們定義 class Bar(Foo) 時(shí),Python 會(huì)首先在當(dāng)前類,即 Bar 中尋找 __metaclass__,如果沒有找到,就會(huì)在父類 Foo 中尋找 __metaclass__,如果找不到,就繼續(xù)在 Foo 的父類尋找,如此繼續(xù)下去,如果在任何父類都找不到 __metaclass__,就會(huì)到模塊層次中尋找,如果還是找不到,就會(huì)用 type 來創(chuàng)建這個(gè)類。

這里,我們在 Foo 找到了 __metaclass__,Python 會(huì)使用 PrefixMetaclass 來創(chuàng)建 Bar,也就是說,元類會(huì)隱式地繼承到子類,雖然沒有顯示地在子類使用 __metaclass__,這也解釋了為什么 Bar 的 prop 屬性被動(dòng)態(tài)修改成了 my_prop。

寫到這里,不知道你理解元類了沒?希望理解了,如果沒理解,就多看幾遍吧~

小結(jié)

  • 在 Python 中,類也是一個(gè)對象。
  • 類創(chuàng)建實(shí)例,元類創(chuàng)建類。
  • 元類主要做了三件事:
    • 攔截類的創(chuàng)建
    • 修改類的定義
    • 返回修改后的類
  • 當(dāng)你創(chuàng)建類時(shí),解釋器會(huì)調(diào)用元類來生成它,定義一個(gè)繼承自 object 的普通類意味著調(diào)用 type 來創(chuàng)建它。

參考資料

上一篇:Python 之旅下一篇:迭代器 (Iterator)