鍍金池/ 教程/ Python/ 特殊方法 (2)
標(biāo)準(zhǔn)庫(kù) (4)
如何成為 Python 高手
標(biāo)準(zhǔn)庫(kù) (6)
標(biāo)準(zhǔn)庫(kù) (3)
類(2)
Pandas 使用 (2)
xml
用 tornado 做網(wǎng)站 (5)
文件(1)
練習(xí)
列表(3)
從小工到專家
除法
錯(cuò)誤和異常 (2)
函數(shù)(1)
用 tornado 做網(wǎng)站 (7)
為做網(wǎng)站而準(zhǔn)備
函數(shù)練習(xí)
標(biāo)準(zhǔn)庫(kù) (8)
Pandas 使用 (1)
回顧 list 和 str
字典(1)
用 tornado 做網(wǎng)站 (3)
字符串(1)
函數(shù)(2)
寫一個(gè)簡(jiǎn)單的程序
將數(shù)據(jù)存入文件
語句(5)
SQLite 數(shù)據(jù)庫(kù)
集成開發(fā)環(huán)境(IDE)
集合(1)
類(1)
用 tornado 做網(wǎng)站 (6)
用 tornado 做網(wǎng)站 (2)
自省
語句(4)
錯(cuò)誤和異常 (1)
用 tornado 做網(wǎng)站 (4)
集合(2)
列表(1)
標(biāo)準(zhǔn)庫(kù) (1)
生成器
mysql 數(shù)據(jù)庫(kù) (1)
第三方庫(kù)
實(shí)戰(zhàn)
運(yùn)算符
類(3)
字典(2)
語句(1)
數(shù)和四則運(yùn)算
語句(2)
文件(2)
MySQL 數(shù)據(jù)庫(kù) (2)
電子表格
迭代器
mongodb 數(shù)據(jù)庫(kù) (1)
特殊方法 (2)
特殊方法 (1)
字符編碼
編寫模塊
用 tornado 做網(wǎng)站 (1)
標(biāo)準(zhǔn)庫(kù) (5)
函數(shù)(4)
類(5)
字符串(2)
關(guān)于 Python 的故事
函數(shù)(3)
字符串(4)
處理股票數(shù)據(jù)
常用數(shù)學(xué)函數(shù)和運(yùn)算優(yōu)先級(jí)
字符串(3)
為計(jì)算做準(zhǔn)備
多態(tài)和封裝
類(4)
迭代
語句(3)
錯(cuò)誤和異常 (3)
分析 Hello
Python 安裝
標(biāo)準(zhǔn)庫(kù) (2)
列表(2)
元組

特殊方法 (2)

書接上回,不管是實(shí)例還是類,都用__dict__來存儲(chǔ)屬性和方法,可以籠統(tǒng)地把屬性和方法稱為成員或者特性,用一句籠統(tǒng)的話說,就是__dict__存儲(chǔ)對(duì)象成員。但,有時(shí)候訪問的對(duì)象成員沒有存在其中,就是這樣:

>>> class A(object):
...     pass
... 
>>> a = A()
>>> a.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'x'

x 不是實(shí)例的成員,用 a.x 訪問,就出錯(cuò)了,并且錯(cuò)誤提示中報(bào)告了原因:“'A' object has no attribute 'x'”

在很多情況下,這種報(bào)錯(cuò)是足夠的了。但是,在某種我現(xiàn)在還說不出的情況下,你或許不希望這樣報(bào)錯(cuò),或許希望能夠有某種別的提示、操作等。也就是我們更希望能在成員不存在的時(shí)候有所作為,不是等著報(bào)錯(cuò)。

要處理類似的問題,就要用到本節(jié)中的知識(shí)了。

__getattr__、__setattr__和其它類似方法

還是用上面的例子,如果訪問 a.x,它不存在,那么就要轉(zhuǎn)向到某個(gè)操作。我們把這種情況稱之為“攔截”。就好像“尋隱者不遇”,卻被童子“遙指杏花村”,將你“攔截”了。在 Python 中,有一些方法就具有這種“攔截”能力。

  • __setattr__(self,name,value):如果要給 name 賦值,就調(diào)用這個(gè)方法。
  • __getattr__(self,name):如果 name 被訪問,同時(shí)它不存在的時(shí)候,此方法被調(diào)用。
  • __getattribute__(self,name):當(dāng) name 被訪問時(shí)自動(dòng)被調(diào)用(注意:這個(gè)僅能用于新式類),無論 name 是否存在,都要被調(diào)用。
  • __delattr__(self,name):如果要?jiǎng)h除 name,這個(gè)方法就被調(diào)用。

如果一時(shí)沒有理解,不要緊,是正常的。需要用例子說明。

>>> class A(object):
...     def __getattr__(self, name):
...         print "You use getattr"
...     def __setattr__(self, name, value):
...         print "You use setattr"
...         self.__dict__[name] = value
... 

類 A 是新式類,除了兩個(gè)方法,沒有別的屬性。

>>> a = A()
>>> a.x
You use getattr

a.x,按照本節(jié)開頭的例子,是要報(bào)錯(cuò)的。但是,由于在這里使用了__getattr__(self, name) 方法,當(dāng)發(fā)現(xiàn) x 不存在于對(duì)象的__dict__中的時(shí)候,就調(diào)用了__getattr__,即所謂“攔截成員”。

>>> a.x = 7
You use setattr

給對(duì)象的屬性賦值時(shí)候,調(diào)用了__setattr__(self, name, value)方法,這個(gè)方法中有一句 self.__dict__[name] = value,通過這個(gè)語句,就將屬性和數(shù)據(jù)保存到了對(duì)象的__dict__中,如果在調(diào)用這個(gè)屬性:

>>> a.x
7

它已經(jīng)存在于對(duì)象的__dict__之中。

在上面的類中,當(dāng)然可以使用__getattribute__(self, name),因?yàn)樗切率筋悺2⑶?,只要訪問屬性就會(huì)調(diào)用它。例如:

>>> class B(object):
...     def __getattribute__(self, name):
...         print "you are useing getattribute"
...         return object.__getattribute__(self, name)
...

為了與前面的類區(qū)分,新命名一個(gè)類名字。需要提醒注意,在這里返回的內(nèi)容用的是 return object.__getattribute__(self, name),而沒有使用 return self.__dict__[name]像是。因?yàn)槿绻眠@樣的方式,就是訪問 self.__dict__,只要訪問這個(gè)屬性,就要調(diào)用`getattribute``,這樣就導(dǎo)致了無線遞歸下去(死循環(huán))。要避免之。

>>> b = B()
>>> b.y
you are useing getattribute
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __getattribute__
AttributeError: 'B' object has no attribute 'y'
>>> b.two
you are useing getattribute
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __getattribute__
AttributeError: 'B' object has no attribute 'two'

訪問不存在的成員,可以看到,已經(jīng)被__getattribute__攔截了,雖然最后還是要報(bào)錯(cuò)的。

>>> b.y = 8
>>> b.y
you are useing getattribute
8

當(dāng)給其賦值后,意味著已經(jīng)在__dict__里面了,再調(diào)用,依然被攔截,但是由于已經(jīng)在__dict__內(nèi),會(huì)把結(jié)果返回。

當(dāng)你看到這里,是不是覺得上面的方法有點(diǎn)魔力呢?不錯(cuò)。但是,它有什么具體應(yīng)用呢?看下面的例子,能給你帶來啟發(fā)。

#!/usr/bin/env Python
# coding=utf-8

"""
study __getattr__ and __setattr__
"""

class Rectangle(object):
    """
    the width and length of Rectangle
    """
    def __init__(self):
        self.width = 0
        self.length = 0

    def setSize(self, size):
        self.width, self.length = size
    def getSize(self):
        return self.width, self.length

if __name__ == "__main__":
    r = Rectangle()
    r.width = 3
    r.length = 4
    print r.getSize()
    r.setSize( (30, 40) )
    print r.width
    print r.length

上面代碼來自《Beginning Python:From Novice to Professional,Second Edittion》(by Magnus Lie Hetland),根據(jù)本教程的需要,稍作修改。

$ python 21301.py 
(3, 4)
30
40

這段代碼已經(jīng)可以正確運(yùn)行了。但是,作為一個(gè)精益求精的程序員。總覺得那種調(diào)用方式還有可以改進(jìn)的空間。比如,要給長(zhǎng)寬賦值的時(shí)候,必須賦予一個(gè)元組,里面包含長(zhǎng)和寬。這個(gè)能不能改進(jìn)一下呢?

#!/usr/bin/env Python
# coding=utf-8

"""
study __getattr__ and __setattr__
"""

class Rectangle(object):
    """
    the width and length of Rectangle
    """
    def __init__(self):
        self.width = 0
        self.length = 0

    def setSize(self, size):
        self.width, self.length = size
    def getSize(self):
        return self.width, self.length

    size = property(getSize, setSize)

if __name__ == "__main__":
    r = Rectangle()
    r.width = 3
    r.length = 4
    print r.size
    r.size = 30, 40
    print r.width
    print r.length

以上代碼的運(yùn)行結(jié)果同上。但是,因?yàn)榧恿艘痪?size = property(getSize, setSize),使得調(diào)用方法是不是更優(yōu)雅了呢?原來用 r.getSize(),現(xiàn)在使用 r.size,就好像調(diào)用一個(gè)屬性一樣。難道你不覺得眼熟嗎?在《多態(tài)和封裝》中已經(jīng)用到過 property 函數(shù)了,雖然寫法略有差別,但是作用一樣。

本來,這樣就已經(jīng)足夠了。但是,因?yàn)楸竟?jié)中出來了特殊方法,所以,一定要用這些特殊方法從新演繹一下這段程序。雖然重新演繹的不一定比原來的好,主要目的是演示本節(jié)的特殊方法應(yīng)用。

#!/usr/bin/env Python
# coding=utf-8

class NewRectangle(object):
    def __init__(self):
        self.width = 0
        self.length = 0

    def __setattr__(self, name, value):
        if name == "size":
            self.width, self.length = value
        else:
            self.__dict__[name] = value

    def __getattr__(self, name):
        if name == "size":
            return self.width, self.length
        else:
            raise AttributeError

if __name__ == "__main__":
    r = NewRectangle()
    r.width = 3
    r.length = 4
    print r.size
    r.size = 30, 40
    print r.width
    print r.length

除了類的樣式變化之外,調(diào)用樣式?jīng)]有變。結(jié)果是一樣的。

這就算了解了一些這些屬性了吧。但是,有一篇文章是要必須推薦給讀者閱讀的:Python Attributes and Methods,讀了這篇文章,對(duì) Python 的對(duì)象屬性和方法會(huì)有更深入的理解。

獲得屬性順序

通過實(shí)例獲取其屬性(也有說特性的,名詞變化了,但是本質(zhì)都是屬性和方法),如果在__dict__中有相應(yīng)的屬性,就直接返回其結(jié)果;如果沒有,會(huì)到類屬性中找。比如:

#!/usr/bin/env Python
# coding=utf-8

class A(object):
    author = "qiwsir"
    def __getattr__(self, name):
        if name != "author":
            return "from starter to master."

if __name__ == "__main__":
    a = A()
    print a.author
    print a.lang

運(yùn)行程序:

$ python 21302.py 
qiwsir
from starter to master.

當(dāng) a = A() 后,并沒有為實(shí)例建立任何屬性,或者說實(shí)例的__dict__是空的,這在上節(jié)中已經(jīng)探討過了。但是如果要查看 a.author,因?yàn)閷?shí)例的屬性中沒有,所以就去類屬性中找,發(fā)現(xiàn)果然有,于是返回其值 "qiwsir"。但是,在找 a.lang 的時(shí)候,不僅實(shí)例屬性中沒有,類屬性中也沒有,于是就調(diào)用了__getattr__()方法。在上面的類中,有這個(gè)方法,如果沒有__getattr__()方法呢?如果沒有定義這個(gè)方法,就會(huì)引發(fā) AttributeError,這在前面已經(jīng)看到了。

這就是通過實(shí)例查找特性的順序。


總目錄   |   上節(jié):特殊方法(1)   |   下節(jié):迭代器

如果你認(rèn)為有必要打賞我,請(qǐng)通過支付寶:qiwsir@126.com,不勝感激。