課時45:魔法方法:屬性訪問

那是個好男孩發表於2018-08-25

目錄:

  一、屬性訪問

  二、課時45課後習題及答案

 

****************

一、屬性訪問

****************

 通常可以通過(.)操作符的形式去訪問物件的屬性,在類與物件這一章的最後一節也有談到如何通過幾個BIF適當地去訪問屬性:

>>> class C:
    def __init__(self):
        self.x = `X-man`

        
>>> c = C()
>>> c.x
`X-man`
>>> getattr(c,`x`,`木有這個屬性`)
`X-man`
>>> getattr(c,`y`,`木有這個屬性`)
`木有這個屬性`
>>> setattr(c,`y`,`Yellow`)
>>> getattr(c,`y`,`木有這個屬性`)
`Yellow`
>>> delattr(c,`x`)
>>> c.x
Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    c.x
AttributeError: `C` object has no attribute `x`

然後還介紹了一個叫做property()函式的用法,這個property()使得我們可以用屬性去訪問屬性:

>>> class C:
    def __init__(self,size = 10):
        self.size = size
    def getSize(self):
        return self.size
    def setSize(self,value):
        self.size = value
    def delSize(self):
        del self.size
    x = property(getSize,setSize,delSize)

    
>>> c = C()
>>> c.x
10
>>> c.x = 12
>>> c.x
12
>>> c.size
12
>>> del c.x
>>> c.size
Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    c.size
AttributeError: `C` object has no attribute `size`

那麼關於屬性訪問,肯定也有相應得魔法方法來管理。通過對這些魔法方法的重寫,可以隨心所欲的控制物件的屬性訪問。

下表列舉了屬性相關的魔法方法。

__getattr__(self, name)             定義當使用者試圖獲取一個不存在的屬性時的行為
__getattribute__(self, name)       定義當該類的屬性被訪問時的行為
__setattr__(self, name, value)     定義當一個屬性被設定時的行為
__delattr__(self, name)             定義當一個屬性被刪除時的行為

做個小測試:

class C:
    def __getattribute__(self, name):
        print(`getattribute`)
        # 使用 super() 呼叫 object 基類的 __getattribute__ 方法
        return super().__getattribute__(name)

    def __setattr__(self, name, value):
        print(`setattr`)
        super().__setattr__(name, value)

    def __delattr__(self, name):
        print(`delattr`)
        super().__delattr__(name)

    def __getattr__(self, name):
        print(`getattr`)
>>> c = C()
>>> c.x
getattribute
getattr
>>> c.x = 1
setattr
>>> c.x
getattribute
1
>>> del c.x
delattr
>>> setattr(c,`y`,`Yellow`)
setattr

這幾個魔法方法在使用上需要注意的是,有一個死迴圈的陷阱,初學者很容易中招,通過一個例項來講解。

寫一個矩形類,預設有寬和高兩個屬性;
如果為一個叫square的屬性賦值,那麼說明這是一個正方形,值就是正方形的邊長,此時寬和高都應該等於邊長。

class Rectangle:
    def __init__(self, width=0, height=0):
        self.width = width
        self.height = height

    def __setattr__(self, name, value):
        if name == `square`:
            self.width = value
            self.height = value
        else:
            self.name = value

    def getArea(self):
        return self.width * self.height
>>> r1 = Rectangle(4,5)
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    r1 = Rectangle(4,5)
  File "C:Users14158Desktoplalallalalal.py", line 3, in __init__
    self.width = width
  File "C:Users14158Desktoplalallalalal.py", line 11, in __setattr__
    self.name = value
  File "C:Users14158Desktoplalallalalal.py", line 11, in __setattr__
    self.name = value
  File "C:Users14158Desktoplalallalalal.py", line 11, in __setattr__
    self.name = value
  [Previous line repeated 987 more times]
  File "C:Users14158Desktoplalallalalal.py", line 7, in __setattr__
    if name == `square`:
RecursionError: maximum recursion depth exceeded in comparison

這是為什麼呢?

分析一下:例項化物件,呼叫__init__()方法,在這裡self.width和self.heigth分別初始化賦值。一發生賦值操作,就會自動觸發__setattr__()魔法方法,width和height兩個屬性被賦值,於是執行else的下邊的語句,就變成了self.width = value,那麼就相當於又觸發了__setattr__(),那麼這樣就依賴基類的方法來實現賦值:

        else:
            super().__setattr__(name,value)
>>> r1 = Rectangle(4,5)
>>> r1.getArea()
20
>>> r1.square = 10
>>> r1.getArea()
100

另一種方法就是給特殊屬性__dict__賦值。物件有一個特殊屬性,叫做__dict__,它的作用是以字典的形式顯示出當前物件的所有屬性以及相對應的值:

        else:
            self.__dict__[name] = value

執行結果一樣。

 

*******************************

二、課時45課後習題及答案

*******************************

 

 

相關文章