python 裝飾器是在面試過程高頻被問到的問題,裝飾器也是乙個非常好用的特性,
熟練掌握裝飾器會讓你的程式設計思路更加寬廣,程式也更加 pythonic。
今天就結合最近的世界盃帶大家理解下裝飾器。
6 月 17 日德國戰墨西哥,小痴雖然是乙個偽球迷,但每年的世界盃還是會了解下。而德國是上屆的冠軍,又是這屆奪冠熱門。德意志戰車在 32 年間小組賽就沒有輸過!臥槽!雖然小痴很少賭球,但這次德國如此強大,肯定會贏吧。搏一搏單車變摩托!隨後小痴買了德國隊贏。心裡想著這次肯定穩了!贏了會所嫩模!小痴連比賽都不看,美滋滋的敲著**。
然後比賽結果卻是德國爆冷 0:1 輸給墨西哥隊,德國隊輸了比賽,小痴也下海幹活。只是此時的天台有點擠,風還有大。
小痴含淚的寫下了下面的**:
def guess_win(func):
def rooftop_status():
result = func()
print('天台已滿,請排隊!')
return result
return rooftop_status
@guess_win
def german_team():
print('德國必勝!')
複製**
輸出結果:
德國必勝!
天台已滿,請排隊!
複製**
首先我們先來了解下什麼是裝飾器,嚴格來說,裝飾器只是語法糖,裝飾器是可呼叫的物件,可以像常規的可呼叫物件那樣呼叫,特殊的地方是裝飾器的引數是乙個函式。
裝飾器的存在是為了適用兩個場景,乙個是增強被裝飾函式的行為,另乙個是**重用。
比如在上面的例子中我們在壓德國隊贏的時候,原本的 german_team() 函式只是輸出德國必勝,但在使用裝飾器(guess_win)後,它的功能多了一項:輸出「天台已滿,請排隊!」。這就是乙個簡單的裝飾器,實現了「增強被裝飾函式的行為」。
乙個良好的裝飾器必須要遵守兩個原則:
這裡並不難以理解,在現在的生產環境中,很多**是不能輕易的改寫,因為這樣有可能傳送意想不到的影響。還有一點就是我們在看大神的**,我們根本不懂如何改寫。同時你也不能修改呼叫方式,因為你並不知道有在乙個專案中,有多少處應用了此函式。
如果你想要很好的理解裝飾器,那下面的兩個內容需要你先有所認知。
我們來看下這個例子:
def func(name):
print('我是{}!慌的一逼!'.format(name))
func('梅西')
y = func
y('勒夫')
複製**
輸出結果:
我是梅西!慌的一逼!
我是勒夫!慌的一逼!
複製**
在**中我們首先定義了函式 func,並呼叫了 func 函式,並且把 func 賦值給 y。y = func 表明了:函式名可以賦值給變數,並且不影響呼叫。
這樣講,可能還有些人不太明白。我們在來對比下我們常用的操作。這其實和整數、數字是一樣的,下面的**你肯定熟悉:
a = 1
b = a
print(a, b)
複製**
2 高階函式
高階函式滿足如下的兩個條件中的任意乙個:a.可以接收函式名作為實參;b.返回值中可以包含函式名。
在 python 標準庫中的 map 和 filter 等函式就是高階函式。
l = [1, 2, 4]
r = map(lambda x: x*3, l)
for i in r:
print('當前天台人數:', i)
複製**
輸出結果:
當前天台人數: 3
當前天台人數: 6
當前天台人數: 12
複製**
自定義乙個能返回函式的函式,也是高階函式:
def f(l):
return map(lambda x: x *5, l)
a = f(l)
for i in a:
print('當前天台人數:', i)
複製**
輸出結果:
當前天台人數: 5
當前天台人數: 10
當前天台人數: 20
複製**
現在你已經知道了「函式名賦值」和「高階函式」,有了這兩個基礎,我們就可以嘗試實現乙個類似的裝飾器。
def status(func):
print('慌的一逼!')
return func
def name():
print('我是梅西!')
temp = status(name)
temp()
複製**
輸出結果:
慌的一逼!
我是梅西!
複製**
在這個例子中我們定義了乙個 status 函式,status 接收乙個函式名然後直接返回該函式名。這樣我們實現了不修改原函式 name,並且新增了乙個新功能的需求。但是這裡有個缺陷就是函式的呼叫方式改變了。即不是原本的 name,而是 temp。
要解決這個問題很簡單,相信 a = a*3 這樣的表示式大家都見過,那麼上述**中的 temp = status(name) 同樣可以修改為 name = status(name),這樣我們就完美的解決了問題:既新增新功能又沒有修改原函式和其呼叫方式。修改後的**如下:
def status(func):
print('慌的一逼!')
return func
def name():
print('我是梅西!')
name = status(name)
name()
複製**
但這樣的**卻有個不便之處,即每次使用這樣的裝飾器,我們都要寫類似 name = status(name) 的**。程式設計師都是懶的,所以才有那麼多高階的語法。在 python 中為了簡化這種情況,提供了乙個語法糖 @,在每個被裝飾的函式上方使用這個語法糖就可以省掉這一句** name = status(name),最後的**如下:
def status(func):
print('慌的一逼!')
return func
@status
def name():
print('我是梅西!')
name()
複製**
這樣我們就弄清楚了裝飾器的工作原理:
但是對比開頭的例子,還是有些不一樣。在開始的例子中,我們還實現了乙個 rooftop_status 函式,來判斷下當前的天台狀是否人滿。但是我們現在是直接返回了函式名,這樣函式呼叫後我們就沒辦法做任何事情。梅西和德國慌了,我們也慌了,各個都要天台見,但在這之前我們也要考慮下天台的情況。
為了能判斷天台的情況,所以此時我們需要在巢狀一層函式,將實現額外功能的部分寫在內層函式中,然後將這個內層函式返回即可。這也是為什麼裝飾器都是巢狀函式的原因。
另外,開篇的例子並沒有返回值,也沒有引數,要對既有引數又有返回值的函式進行裝飾的話,還需要進一步完善。 能夠處理返回值的裝飾器:
def guess_win(func):
def rooftop_status():
result = func()
print('天台已滿,請排隊!')
return result
return rooftop_status
@guess_win
def german_team():
print('德國必勝!')
return
'贏了會所嫩模!輸了下海幹活!'
x = german_team()
print(x)
複製**
輸出結果:
德國必勝!
天台已滿,請排隊!
贏了會所嫩模!輸了下海幹活!
複製**
能夠處理引數的裝飾器:
def guess_win(func):
def rooftop_status(*args, **kwargs):
result = func(*args, **kwargs)
print('天台已滿,請排隊!')
return result
return rooftop_status
@guess_win
def german_team(arg):
print('{}必勝!'.format(arg))
return
'贏了會所嫩模!輸了下海幹活!'
x = german_team('德國')
y = german_team('西班牙')
print(x)
複製**
輸出結果:
德國必勝!
天台已滿,請排隊!
西班牙必勝!
天台已滿,請排隊!
贏了會所嫩模!輸了下海幹活!
複製**
裝飾器的本質是函式,其引數是另乙個函式(被裝飾的函式)。裝飾器通常會額外處理被裝飾的函式,然後把它返回,或者將其替換成另乙個函式或可呼叫物件。行為良好的裝飾器可以重用,以減少**量。
對於這屆的世界盃,我總結了下。
搞笑世界盃
有n張a類票 n張b類票,在兩張票都有的情況下,工作人員會以1 2的概率發票,否則就直接發僅存的那類票。現在請問最後兩張發出去的票是相同種類的概率是多少?n 2000 概率型dp 定義 dp i j 為還剩i張a類票,j張b類票,最後兩張相同的概率。易得出dp i 0 dp 0 i 1.0 i 2 ...
重溫世界盃
description 世界盃結束了,義大利人連本帶利的收回了法國人6年前欠他們的債,捧起了大力神盃,成就了4星義大利.世界盃雖然結束了,但是這界世界盃給我們還是留下許多值得回憶的東西.比如我們聽到了黃名嘴的3分鐘激情解說,我們懂得了原來可以向同乙個人出示3張黃牌,我們還看到了齊達內的頭不僅能頂球還...
世界盃紀錄之最
世界盃決賽階段進球最多的比賽 第5屆世界盃奧地利隊7 5擊敗瑞士隊,雙方共打進12個球。世界盃預賽階段乙個隊一場比賽進球最多的紀錄 1997年6月2日,在98年世界盃足球賽亞洲區預賽伊朗隊對馬爾地夫隊的比賽中,伊朗隊以17 0獲勝。世界盃預賽階段一場比賽進球最多的球員 7球,由伊朗隊的卡里姆 巴蓋里...