利用世界盃,讀懂 Python 裝飾器

2021-09-11 13:24:43 字數 4751 閱讀 2703

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球,由伊朗隊的卡里姆 巴蓋里...