1. 程式人生 > >python_魔法方法(四):屬性訪問

python_魔法方法(四):屬性訪問

通常可以通過點(.)操作符的形式去訪問物件的屬性,也可以通過BIF適當地去訪問屬性,看個例子吧

>>> class A():
    def __init__(self):
        self.x = 'XYZ-xyz'
        
>>> a = A()
>>> a.x
'XYZ-xyz'
>>> getattr(a,'x','沒有這個屬性')
'XYZ-xyz'
>>> getattr(a,'z','沒有這個屬性')
'沒有這個屬性'
>>> setattr(a,'
z','red') >>> getattr(a,'z','red') 'red' >>> delattr(a,'x') >>> a.x Traceback (most recent call last): File "<pyshell#12>", line 1, in <module> a.x AttributeError: 'A' 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#30>", line 1, in <module> c.size AttributeError: 'C' object has no attribute 'size'

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

來介紹下屬性相關的魔法方法:

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

好了,我們先來做個測試:

>>> class A():
    def __getattribute__(self,name):
        print('getattribute')
        #使用super()呼叫object基類__getattribut__()方法
        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')
    
>>> a = A()
>>> a.x
getattribute
getattr
>>> a.x = 1
setattr
>>> a.x
getattribute
1
>>> del a.x
delattr
>>> setattr(a,'y','yellow')
setattr

這幾個魔法方法在使用的時候要注意,防止進入死迴圈陷阱,下面通過例項來說明,寫一個矩形類(Rectangle),預設有寬(width)和高(height)兩個屬性;如果為一個叫square的屬性賦值,那麼說明這是一個正方形,值就是正方形的邊長,此時高寬都應該等於邊長

>>> class Rectangle():
    def __init__(self,width = 0,height = 0):
        self.width = width
        self.height = height
    def __setattr__(self,key,value):
        if key == 'square':
            self.width = value
            self.height = value
        else:
            self.key = value
    def getArea(self):
        return self.width *self.height
    
>>> r1 = Rectangle(4,5)
Traceback (most recent call last):
  File "<pyshell#65>", line 1, in <module> r1 = Rectangle(4,5)
  File "<pyshell#64>", line 3, in __init__ self.width = width
  File "<pyshell#64>", line 10, in __setattr__ self.key = value
  File "<pyshell#64>", line 10, in __setattr__  self.key = value
  File "<pyshell#64>", line 10, in __setattr__  self.key = value
  [Previous line repeated 325 more times]
  File "<pyshell#64>", line 6, in __setattr__  if key == 'square':
RecursionError: maximum recursion depth exceeded in comparison

出現這個迴圈的原因在於呼叫了__init__()方法,在這裡給self.width和self.height分別初始化賦值。一旦發生賦值操作就會自動觸發__setattr__()魔法方法,width和height兩個屬性被賦值,於是執行了else的下邊的語句,就又變成了self.width=value,那麼久相當於又觸發了__setattr__()魔法方法,造成了死迴圈。

解決方法一,就是和剛才的一樣,使用super()來呼叫基類的__setattr__(),那麼這樣就依賴基類的方法來實現賦值:

>>> class Rectangle():
    def __init__(self,width = 0,height = 0):
        self.width = width
        self.height = height
    def __setattr__(self,key,value):
        if key == 'square':
            self.width = value
            self.height = value
        else:
            super().__setattr__(key,value)
    def getArea(self):
        return self.width *self.height
    
>>> r1 = Rectangle(4,5)
>>> r1.getArea()
20
>>> r1.square = 10
>>> r1.getArea()
100

解決方法二:給特殊屬性__dict__賦值。物件有一個特殊的屬性,__dict__,它的作用是以字典的形式顯示出當前物件的所有屬性以及相對於的值。

呼叫死迴圈的類

>>> r1.__dict__
{'width': 10, 'height': 10}
>>> class Rectangle():
    def __init__(self,width = 0,height = 0):
        self.width = width
        self.height = height
    def __setattr__(self,key,value):
        if key == 'square':
            self.width = value
            self.height = value
        
>>> r1 = Rectangle(4,5)