裝飾器
裝飾器是Python中的一個重要概念,多用於在不修改原函數的基礎上,為函數增加額外的功能。
基礎裝飾器
例如小李給女朋友買了一款iPhone12作為生日禮物,手機原封未拆封。
def gift():
print('iPhone12')
gift() # 運行顯示禮物信息
但還是覺得禮物太單薄,於是又買了一盒德芙巧克力,一支dior的口紅,並找了個精美的禮品盒包裝了一下,盒子里放滿了泡沫球。
def gift():
print('iPhone12')
def box(gift):
print('='*5 + '禮物盒' + '='*5)
print('一盒泡沫球')
print('好多巧克力')
print('一支dior口紅')
return gift
gift = box(gift) # 將禮物包裝后作為禮物
gift() # 顯示禮物信息
運行后顯示如下:
=====禮物盒=====
一盒泡沫球
好多巧克力
一支dior口紅
iPhone12
這個box便是一個裝飾器,它的參數是一個函數對象,同數字、字符串、列表、字典等數據類型一樣,函數和類也可以作為函數的參數使用,畢竟在Python里人人平等,一切皆對象。
box在使用時依然返回了原來的gift,只是在拿到這個gift之前增加了兩個額外的驚喜,然后我們把box作為gift使用即可。
裝飾器本質上就是以函數作為參數,對函數做一些處理,並替換原函數的一種高階函數。
上例中,使用裝飾器表示為如下。
def box(gift): # 以函數為參數的裝飾器
print('='*5 + '禮物盒' + '='*5)
print('一盒泡沫球')
print('好多巧克力')
print('一支dior口紅')
return gift
@box # 掛載裝飾器,會自動替換原函數
def gift():
print('iPhone12')
gift() # 這里面得到的gift實際上是裝飾后的gift即`box(gift)`
運行后顯示和上例相同。
處理函數參數
小李突然想到,買哪個顏色應該征詢下女友的意見,也就是原來的gift應支持一個可供選擇的顏色參數。
def gift(color):
print(f'iPhone12{color}版')
作為一個細心的boyfriend,小李需要根據對應的手機顏色選擇同樣顏色的泡沫球,也就是需要能獲取到,被裝飾的gift函數的參數。
這時候我們需要在盒子內部(box裝飾器),重新准備一個新的禮物,根據顏色參數做不同的處理,然后根據顏色拿到指定的iPhone12禮物。
def box(gift):
print('='*5 + '禮物盒' + '='*5)
def new_gift(color): # 准備一個新的禮物,參數和原gift參數一致
print(f'一盒{color}泡沫球') # 根據顏色准備泡沫球
print('好多巧克力')
print('一支dior口紅')
return gift(color) # 根據顏色拿到指定的iPhone12
return new_gift # 返回新禮物,新禮物調用時,增加一些驚喜,並返回原有禮物gift(color)的結果。
@box
def gift(color):
print(f'iPhone12{color}版')
gift('紅色') # 實際上這里的gift是被box裝飾后狸貓換太子的new_gift函數,而new_gift('紅色'),返回原gift('紅色')的結果。
在box內部為了根據參數做對應處理,我們新建了一個函數,函數內部也可以定義內部函數,內部函數new_gift可以獲取並使用外部box函數的參數,如gift。
為了能獲取到原有函數gift的參數,我們需要建立一個傀儡函數new_gift,這個函數和原函數gift的參數一致、返回結果一致,即new_gift('紅色')返回的就是gift('紅色')。
然后狸貓換太子,不再返回原來的gift函數對象,而是返回替換的new_gift函數對象。
運行后顯示
=====禮物盒=====
一盒紅色泡沫球
好多巧克力
一支dior口紅
iPhone12紅色版
注意:在裝飾器box里,要返回一個函數對象,如上例中的
return gift
或本例中的return new_gift
。而在傀儡函數new_gift中,為了和原函數gift結果一致,要返回原函數的調用結果即gift(color)。
從普遍意義上講,作為商家,為了裝飾器box可以包裝任何形式的禮物,無論禮物有什么參數都可以滿足,這就要求我們的傀儡函數new_gift支持任意類型的參數即def new_gift(*args, **kwargs)
。
然后把無論什么參數*args, **kwargs
交由原函數gift(*args, **kwargs)
處理即可。
修改后,我們便得到一個通用的裝飾器,可以包裝任何禮物。
def box(gift):
print('='*5 + '禮物盒' + '='*5)
def new_gift(*args, **kwargs): # 接受任意數量的參數
if args and len(args) > 0: # 由於參數不確定了,我們假設萬一有參數,第一個參數是color參數
color = args[0]
print(f'一盒{color}泡沫球')
else:
print(f'一盒泡沫球')
print('好多巧克力')
print('一支dior口紅')
result = gift(*args, **kwargs) # 如果我們需要對原函數的結果做出處理,可以先獲取到結果
# print(f'原函數結果{result}') 由於原函數gift沒有return,這是其實是None
return result # 返回原函數結果
return new_gift
@box
def gift(color, pro=False): # 新的禮物函數,兩個參數,默認買12,萬一女友要Pro,也可以
if pro is True:
print(f'iPhone12 Pro{color}版')
else:
print(f'iPhone12{color}版')
gift('海藍色', pro=True)
這樣,無論被裝飾的函數有幾個參數,box裝飾器都可以正常處理。
運行后顯示如下。
=====禮物盒=====
一盒海藍色泡沫球
好多巧克力
一支dior口紅
iPhone12 Pro海藍色版
帶參裝飾器
信心滿滿的小李覺得,在盒子上還可以做些文章,要根據女友的喜好選擇不同形狀的箱子,因此我們需要根據參數來定制我們的裝飾器box,在盒子外面再加一層定制函數。
def custom_box(shape): # 根據參數定制裝飾器
def box(gift): # 裝飾器函數
print('='*5 + f'{shape}禮物盒' + '='*5) # 根據形狀定制
# ...
return box # 返回裝飾器函數
此時我們得到一個可以根據參數進行定制的裝飾器函數custom_box,這個裝飾器接收到參數后,傳遞給真實裝飾器box,並返回定制后box裝飾器函數。
完整代碼如下。
def custom_box(shape): # 根據參數定制裝飾器 =====================
def box(gift): # 實際的裝飾器函數 ---------------------------
print('='*5 + f'{shape}禮物盒' + '='*5)
def new_gift(*args, **kwargs): # 傀儡函數 ..............
if args and len(args) > 0:
color = args[0]
print(f'一盒{color}泡沫球')
else:
print(f'一盒泡沫球')
print('好多巧克力')
print('一支dior口紅')
result = gift(*args, **kwargs)
return result # 返回原函數結果 ......................
return new_gift # 返回傀儡函數 ---------------------------
return box # 返回定制的裝飾器 ===============================
@custom_box('心形') # 使用可定制的裝飾器
def gift(color, pro=False):
if pro is True:
print(f'iPhone12 Pro{color}版')
else:
print(f'iPhone12{color}版')
gift('海藍色', pro=True)
注意:裝飾器在導入模塊時立即計算的,即沒調用
gift('海藍色', pro=True)
之前就已經執行生成定制后的box。
運行后,結果如下。
=====心形禮物盒=====
一盒海藍色泡沫球
好多巧克力
一支dior口紅
iPhone12 Pro海藍色版
生成器和迭代器
可迭代對象
實現了__iter__方法, __iter__方法返回一個迭代器
迭代器
按標准的迭代協議實現__iter__和__next__方法,StopIteration結束
class A:
start = 0
def __iter__(self):
return self
def __next__(self):
if self.start > 10:
raise StopIteration
self.start += 1
return self.start
生成器
內部實現了迭代器的一種函數,通過yield記錄當前位置並返回一次迭代結果
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
列表推導式
推倒式:當我們對一批可迭代的數據(如列表或字典)進行提取或處理,最后要得到一個新的列表或字典時,推導式是一種非常簡潔的表達方式。
比如,有一批數據
data = [
{'name': '張三', 'gender': 'male', 'age': 12},
{'name': '李四', 'gender': 'female', 'age': 10},
{'name': '王五', 'gender': 'male', 'age': 20},
{'name': '趙六', 'gender': 'male', 'age': 11},
{'name': '周七', 'gender': 'female', 'age': 16},
{'name': '孫八', 'gender': 'male', 'age': 13},
]
我們想要把數據中的name都提取出來形成一個新的列表,一般的操作是這樣的。
names = [] # 定義一個空列表
for item in data: # 遍歷數據
name = item['name'] # 提取每行中的name
names.append(name) # 追加到列表中
如果用推導式的話,形式如下。
names = [item['name'] for item in data] # 遍歷data,提取每項中的name生成一個新列表
數據處理
在提取數據時,我們還可以對每一項數據進行,處理,假設我們需要每個名稱前加上'姓名: '這個字符串,可以這樣。
names = ['姓名: '+item['name'] for item in data]
'姓名: '+item['name']
就是每一項的數據
數據篩選
同樣我們還可以對數據進行篩選,比如我們只要年齡大於12歲,后面可以使用if進行過濾
names = [item['name'] for item in data if item['age']>12]
多重循環
推導式還支持多重循環,比如
for x in range(1,5)
if x > 2
for y in range(1,4)
if y < 3
x*y
使用推導式表示如下
[x*y for x in range(1,5) if x > 2 for y in range(1,4) if y < 3]
批量執行操作
由於推導式就是一種循環操作,我們也可以使用推導式來批量執行一些相似操作,比如:
def step1(driver):
print('步驟1)
def step2(driver):
print('步驟2)
def step3(driver):
print('步驟3)
我們可以將函數名放到一個列表里,然后使用推導式循環執行
steps = [step1, step2, step3] # 函數名列表
[step(driver) for step in steps] # 不需要變量接收,我們只需要它循環執行
字典推導式
當我們需要遍歷一批數據最后得到一個字典時,同樣可以使用字典推導式,如:
data = [
{'name': '張三', 'gender': 'male', 'age': 12},
{'name': '李四', 'gender': 'female', 'age': 10},
{'name': '王五', 'gender': 'male', 'age': 20},
{'name': '趙六', 'gender': 'male', 'age': 11},
{'name': '周七', 'gender': 'female', 'age': 16},
{'name': '孫八', 'gender': 'male', 'age': 13},
]
假設我們想得到一個{'張三': 12, '李四': 10, ....}
這樣的一個字典,使用字典推導式方式如下:
persons = {item['name']: item['age'] for item in data}
字典推導式同樣支持if篩選等操作。
生成器
生成器實際上是一種包含初始數據和推導法則的對象,比如我們可以輕松的寫出1w以內所有的奇數,原因是因為我只需要記住從1開始每次加2即可。
生成器便是這樣。對應大量的數據或者CSV/Excel文件中的數據,生成器可以大量的節省內存,比如csv.Reader(f)就是一個生成器,只存了當前位置和讀取下一行數據的方法。
當你需要遍歷時,它再每次給你讀取一行數據給你。
如列表推導式的例子,
data = [
{'name': '張三', 'gender': 'male', 'age': 12},
{'name': '李四', 'gender': 'female', 'age': 10},
{'name': '王五', 'gender': 'male', 'age': 20},
{'name': '趙六', 'gender': 'male', 'age': 11},
{'name': '周七', 'gender': 'female', 'age': 16},
{'name': '孫八', 'gender': 'male', 'age': 13},
]
names = [item['name'] for item in data]
我們把列表的中括號改為小括號就得到一個生成器
names2 = (item['name'] for item in data)
注意:生成器和推導式不同,其中的循環不是立即執行的,只用你遍歷這個生成器時才會執行
for name in names: # 遍歷列表推導式生成的新列表
print(name)
for name in names2: # 遍歷一個生成器
print(name)
兩個打印結果是一樣的,生成器更節省內存,只有遍歷時才運行。
魔術方法
魔術方法 | 描述 |
---|---|
__new__ |
創建類並返回這個類的實例 |
__init__ |
可理解為“構造函數”,在對象初始化的時候調用,使用傳入的參數初始化該實例 |
__del__ |
可理解為“析構函數”,當一個對象進行垃圾回收時調用 |
__metaclass__ |
定義當前類的元類 |
__class__ |
查看對象所屬的類 |
__base__ |
獲取當前類的父類 |
__bases__ |
獲取當前類的所有父類 |
__str__ |
定義當前類的實例的文本顯示內容 |
__getattribute__ |
定義屬性被訪問時的行為 |
__getattr__ |
定義試圖訪問一個不存在的屬性時的行為 |
__setattr__ |
定義對屬性進行賦值和修改操作時的行為 |
__delattr__ |
定義刪除屬性時的行為 |
__copy__ |
定義對類的實例調用 copy.copy() 獲得對象的一個淺拷貝時所產生的行為 |
__deepcopy__ |
定義對類的實例調用 copy.deepcopy() 獲得對象的一個深拷貝時所產生的行為 |
__eq__ |
定義相等符號“==”的行為 |
__ne__ |
定義不等符號“!=”的行為 |
__lt__ |
定義小於符號“<”的行為 |
__gt__ |
定義大於符號“>”的行為 |
__le__ |
定義小於等於符號“<=”的行為 |
__ge__ |
定義大於等於符號“>=”的行為 |
__add__ |
實現操作符“+”表示的加法 |
__sub__ |
實現操作符“-”表示的減法 |
__mul__ |
實現操作符“*”表示的乘法 |
__div__ |
實現操作符“/”表示的除法 |
__mod__ |
實現操作符“%”表示的取模(求余數) |
__pow__ |
實現操作符“**”表示的指數操作 |
__and__ |
實現按位與操作 |
__or__ |
實現按位或操作 |
__xor__ |
實現按位異或操作 |
__len__ |
用於自定義容器類型,表示容器的長度 |
__getitem__ |
用於自定義容器類型,定義當某一項被訪問時,使用 self[key] 所產生的行為 |
__setitem__ |
用於自定義容器類型,定義執行 self[key]=value 時產生的行為 |
__delitem__ |
用於自定義容器類型,定義一個項目被刪除時的行為 |
__iter__ |
用於自定義容器類型,一個容器迭代器 |
__reversed__ |
用於自定義容器類型,定義當 reversed( ) 被調用時的行為 |
__contains__ |
用於自定義容器類型,定義調用 in 和 not in 來測試成員是否存在的時候所產生的行為 |
__missing__ |
用於自定義容器類型,定義在容器中找不到 key 時觸發的行為 |