__setattr__,__delattr__,__getattr__ 到底幹了什麼?

公眾號:pythonislover

Python大數據與SQL優化筆記

__setattr__,__delattr__,__getattr__是python中自帶的三個函數,可能有人對這幾個函數還不太熟悉,今天我們說說他們是幹什麼用的。

我們先看一段代碼

class Cat:
class_level = 貴族
def __init__(self,name,type,speed,age):
self.name = name
self.type= type
self.speed = speed
self.age = age

def run(self):
print(%s歲的%s%s正在以%s的速度奔跑 % (self.age,self.type,self.name,self.speed))

def __getattr__(self, item):
print(你找的屬性不存在)

def __setattr__(self, key, value):
print(你在設置屬性)

def __delattr__(self, item):
print(你在刪除屬性)

xiaohua = Cat(小花,波斯貓,10m/s,10)
xiaohua.run() #10歲的波斯貓小花正在以10m/s的速度奔跑

結果:

你在設置屬性

你在設置屬性

你在設置屬性

你在設置屬性

你找的屬性不存在

你找的屬性不存在

你找的屬性不存在

你找的屬性不存在

None歲的NoneNone正在以None的速度奔跑

可以看到__setattr__和__getattr__ 被觸發了,為什麼呢? 我們上面的代碼只是做了一個實例化,還記得實例化的時候要做什麼嗎,就是找到__init__函數設置實例的數據屬性,所以這裡__setattr__被觸發了,但是__getattr__ 為什麼被觸發了呢? 因為我們的__setattr__函數下面只是列印了一行,並沒有任何的其他賦值操作,所以__init__裡面的數據屬性沒有被賦值,在對象調用run方法的時候就找不到self.age,self.type,self.name,self.speed這些屬性,所以就會觸發_getattr__方法。

下面總結下這3個方法的使用場景:

#__setattr__添加/修改屬性會觸發它的執行
#__getattr__只有在使用點調用屬性且屬性不存在的時候才會觸發

#__delattr__刪除屬性的時候會觸發

如果我們在Class沒有定義這3個方法,那麼系統會用Python自帶的內部函數,如果我們在類裡面自定義了這3個函數,那麼python會先調用我們自己定義的這3個函數

上面的__getattr__之所以被觸發就是因為__setattr__函數下面只是列印了一行,並沒有任何的其他賦值操作。

下面我看再看看, 我們怎麼完善上面那部分代碼,讓__setattr__成功賦值,讓__getattr__不被觸發,還是__delattr__怎麼觸發。

class Cat:
class_level = 貴族
def __init__(self,name,type,speed,age):
self.name = name
self.type= type
self.speed = speed
self.age = age

def run(self):
print(%s歲的%s%s正在以%s的速度奔跑 % (self.age,self.type,self.name,self.speed))

def __getattr__(self, item):
print(你找的屬性不存在)

def __setattr__(self, key, value):
print(你在設置屬性)
# self.key=value #這種方法不行,會產生無限遞歸了,因為他本身self.key=value也會觸發__setattr__
self.__dict__[key] = value #我們在給對象屬性賦值的時候,內部原理就是操作對象的__dict__字典,所以我們可以直接操作對象的字典實現屬性賦值
#刪除屬性的時候會觸發
def __delattr__(self, item):
print(你在刪除屬性)
# del self.item #無限遞歸了,和上面的__setattr__原理一樣
self.__dict__.pop(item)

#實例化,會到__init__函數里去給實例數據屬性賦值
xiaohua = Cat(小花,波斯貓,10m/s,10)

#結果:
# 你在設置屬性
# 你在設置屬性
# 你在設置屬性
# 你在設置屬性
xiaohua.run() #10歲的波斯貓小花正在以10m/s的速度奔跑
# 結果:
# 10歲的波斯貓小花正在以10m/s的速度奔跑
#我們看到__setattr__正常賦值了
#實例數據屬性賦值操作,觸發__setattr__方法
xiaohua.weight = 5kg
print(xiaohua.__dict__)

# 結果:
# 你在設置屬性
# {name: 小花, type: 波斯貓, speed: 10m/s, age: 10, weight: 5kg}
#刪除對象的數據屬性操作,觸發__delattr__方法
del xiaohua.weight
print(xiaohua.__dict__)

# 結果:
# 你在刪除屬性
# {name: 小花, type: 波斯貓, speed: 10m/s, age: 10}
#調用對象的不存在的數據屬性,會觸發__getattr__
xiaohua.size

# 結果:
# 你找的屬性不存在

上面我們就理清楚了這三個內置函數的作用和什麼時候會被調用。

下面我們說一個__getattr__實現的一個授權案例:

授權:授權是包裝的一個特性, 包裝一個類型通常是對已存在的類型的一些定製,這種做法可以新建,修改或刪除原有產品的功能。其它的則保持原樣。授權的過程,即是所有更新的功能都是由新類的某部分來處理,但已存在的功能就授權給對象的默認屬性。(解釋來自於網路)

實現授權的關鍵點就是覆蓋__getattr__方法

授權的大概作用就是對一些系統級的方式,實現自定義的一些改變,比如下面的這個案例

下面的案例實現的功能:

  1. 在原生List類的基礎下,實現append方法只能插入int類型數據

  2. 執行新功能獲取列表的中間index值

class List:
def __init__(self,onelist):
self.onelist=onelist #
#基於原生的List類,修改原生的append方法,只能插入int類型的數據
def append(self, p_object):
if not isinstance(p_object,int):
raise TypeError(must be int)
self.onelist.append(p_object)

@property
def getmid(self):
新增自己的方法
index = len(self.onelist) // 2
return self.onelist[index]

def __getattr__(self, item):
return getattr(self.onelist,item) #這是關鍵點,如果item在本類中不存在,會去原生的List類裡面取尋找item方法
l=List([1,2,3]) #等價於l=[1,2,3]
l.append(4)
print(l)
#l.append(abc) #報錯,必須為int類型
#新增自己的方法,獲取列表中間的index
print(l.getmid)

#基於授權,獲得insert方法,insert方法在本類中沒有實現,所以回去原生的List中尋找insert方法
#list.insert(index, obj)
l.insert(0,9)
print(l)

推薦閱讀:

TAG:Python | Python入門 | Python開發 |