1. 程式人生 > 其它 >fopen_s不接受兩個引數_面試官問我Decorator,我上去就是兩個耳刮子

fopen_s不接受兩個引數_面試官問我Decorator,我上去就是兩個耳刮子

技術標籤:fopen_s不接受兩個引數

這是上一篇文章“不懂Python裝飾器,你敢說會Python?”的後半部分。

有位同學看完上一篇文章,覺得自己掌握的很好了,就去面試。

結果被面試官一個“如何在Python中實現單例模式”的問題給當場問倒了。

氣得他上去就是兩個耳刮子,不過不是打面試官,是打自己,恨自己沒有等讀了麥叔的裝飾器第二部分再去面試。

所以大家都耐心讀完,文章最後有福利。

如果你還沒看,建議先看看上一篇文章。

你一定用過裝飾器Decorator

其實Decorator就在我們身邊,只是我們可能不知道它們是裝飾器。我來說幾個:@classmethod @staticmethod @property

有沒有一種"我靠"的衝動?!

對,這些很重要的語法,不過是裝飾器的應用而已。

來看一個程式碼例子:

classCircle:
#半徑用下劃線開頭,表示私有變數
def__init__(self,radius):
self._radius=radius

#用property裝飾器建立虛擬的半徑屬性
@property
defradius(self):
returnself._radius

#用setter裝飾器給半徑屬性新增賦值操作
@radius.setter
defradius(self,value):
ifvalue>=0:
self._radius=value
else:
raiseValueError("Radiusmustbepositive")

#用property裝飾器建立虛擬的面積屬性
@property
defarea(self):
returnself.pi()*self.radius**2

defcylinder_volume(self,height):
returnself.area*height

#類方法
@classmethod
defunit_circle(cls):
returncls(1)

#靜態方法
@staticmethod
defpi():
return3.1415926535

再來建立兩個裝飾器練練手

你不要以為你已經掌握了裝飾器,你只是聽懂了。

從聽懂到能動手寫出來,再到被面試的時候,可以流暢的說出來,那還差著二十萬八千里呢!

一定得多動手!所以抓緊時間,馬上再來建立兩個裝飾器。

程式碼除錯裝飾器

現在我們來建立一個裝飾器:它會列印函式的引數,以及返回值。

如果你有實際專案經驗,你一定會知道這個很有用。這不就是自動列印日誌嘛!是程式設計師找臭蟲的必備良藥啊。

來看看程式碼:

defdebug(func):
defwrapper_debug(*args,**kwargs):
print(f'{func.__name__}:{args},{kwargs}')
ret_val=func(*args,**kwargs)
print(f'return:{ret_val}')
returnret_val
returnwrapper_debug

@debug
defadd(a,b):
returna+b

add(1,3)
add(2,3)
add(4,3)

在wrapper_debug函式中,我們先列印所有的引數,再呼叫原函式,最後先列印返回值,再返回返回值。這裡並沒有新的語法知識,就是為了練手。

裝B神奇 - 讓程式跑慢點

曾經我還年輕,看到一個大神的程式碼裡面有這麼一行:

sleep(random(1,5))

因為有了這行程式碼,程式執行的時候挺慢的。我就問大神,為什麼要這樣。大神語重心長的跟我說:

你還年輕!我把這個程式交付給客戶,客戶會覺得有點慢,但還能忍。

忍不住了,會來找我優化效能。我一個手指頭就把效能優化上去了,客戶一定對我五體投地。而且我們公司的尾款也給我們了。

年輕人,多學著點!這就是閱歷,閱歷!

可惜我學了這麼多年,也沒學會這種閱歷。

不過有時候,因為各種原因,我們確實需要讓程式變慢一點。裝飾器就排上了用場:

importtime

defslow(func):
defwrapper_slow(*args,**kwargs):
print(f'{func.__name__}sleeping1second')
time.sleep(1)
ret_val=func(*args,**kwargs)
returnret_val
returnwrapper_slow


@slow
defadd(a,b):
returna+b

add(1,3)

執行一下,你就會很有成就感!確實慢!

上面那個真實的段子,我勸大家和我一樣,一直都學不會。日久見人心,坑人的事情不能幹。

裝飾器模板

經過前面幾個例子,我們可以總結出一個裝飾器的模板。

按照這個模板,可以輕鬆寫出裝飾器:

defdecorator(func):
defwrapper_decorator(*args,**kwargs):
#呼叫前操作
ret_val=func(*args,**kwargs)
#呼叫後操作
returnret_val
returnwrapper_decorator

按照這個模板:

  1. 修改裝飾器的名字,把decorator替換為具體的名字。
  2. 在註釋“呼叫前操作”的地方寫自己想寫的程式碼
  3. 在註釋“呼叫後操作”的地方寫自己想寫的程式碼。

帶引數的裝飾器

上面那兩個都是普通的裝飾器的應用,我們不能繼續自High下去了。我們得學習新知識了。

上面那個slow的裝飾器,如果能夠傳入到底要sleep幾秒就好了,現在是固定的1秒,這個不香。

注意區分,這裡的引數是指裝飾器的引數。和前面提到的函式自身的引數是不同的。

我想讓它多慢就多慢,然後我們再頃刻間扭轉乾坤,這樣客戶就更為我神魂顛倒了。

要讓裝飾器接受引數,需要在普通裝飾器的外面再套上一層:

importtimedefslow(seconds):defdecorator_slow(func):defwrapper_slow(*args,**kwargs):
print(f'{func.__name__}sleeping{seconds}second')
time.sleep(seconds)
ret_val=func(*args,**kwargs)returnret_valreturnwrapper_slowreturndecorator_slow#新增裝飾器的時候可以傳入要放慢幾秒的引數。@slow(2)defadd(a,b):returna+b#執行此行會停頓2秒
add(1,3)

以前的裝飾器,是函式裡面有一個內部函式(2層函式),現在這個有了3層函式:

  • 先是slow,接受秒數作為引數
  • slow裡面建立了decorator_slow函式,這個就是和原來一樣的裝飾器函式
  • wrapper_slow裡面又建立了wrapper_slow函式。

其實後面兩層就是和之前一樣的,唯一的區別是外面又加了一層。

為什麼會這樣呢?為什麼最外面一層不需要傳入func引數呢?

這是因為:

  1. 當Python發現slow(2)這個裝飾器自帶了引數時,它就不再傳入當前函式作為引數,直接呼叫slow。這是Python直譯器規定的。
  2. slow返回了一個函式,這時候Python會再把當前函式傳入進去,這時候就成為一個普通的裝飾器了。

這就是說最外面一層的功能就是為了處理裝飾器的引數的。

如果你一下子不能理解,先把程式碼敲出來,你就理解了。正所謂:熟讀唐詩三百首,不會吟詩也會吟!

再來看一個裝飾器帶引數的例子:

defrepeat(nums=3):
defdecorator_repeat(func):
defwrapper_repeat(*args,**kwargs):
for_inrange(nums):
func(*args,**kwargs)
returnwrapper_repeat
returndecorator_repeat

@repeat(3)
defrun():
print('跑步有利於身體健康,來一圈')

#這裡會重複執行3次
run()

這個裝飾和slow裝飾器一樣坑人,它會多次重複執行一個方法,並且可以動態指定要重複幾次。

細細品味一下這個3層的函式,它是如何實現帶引數的裝飾器的。這兩個例子都懂了,你就走在吊打面試官的路上了。

類裝飾器

還記得前面給自己兩個耳光的同學嗎?如果他現在去面試,還是給自己兩個耳光,還是不知道如何實現單例模式。

單例模式,是指一個類只能建立一個例項,是最常見的設計模式之一。

比如網站程式有一個類統計網站的訪問人數,這個類只能有一個例項。如果每次訪問都建立一個新的例項,那人數就永遠是1了。

在Python中可以用裝飾器實現單例模式。

前面的裝飾器都是用來裝飾函式的,或者用來裝飾類方法的,比如我們寫的slow, debug, timer; Python自帶的staticmethod, classmethod等。

那如果把裝飾器放到類名前面會怎樣呢?來看這段程式碼:

fromslowimportslow

@slow
classCounter():
def__init__(self):
self._count=0

defvisit(self):
self._count+=1
print(f'visiting:{self._count}')

c1=Counter()
c1.visit()
c1.visit()

c2=Counter()
c2.visit()
c2.visit()

這個類名叫Counter(),顧名思義就是用來做計數的。它有一個內部變數叫做_count,每次呼叫Counter的visit()方法,計數就會加1.

第一行,我們引入了前面寫的slow裝飾器,是那個普通的不帶引數的slow。裝飾器就是個函式,當然可以被import進來。

這次@slow放在Counter類名前面,而不是方法的前面,會發生什麼呢?執行上面的程式碼,會發現這樣的結果:

Countersleeping1second
visiting:1
visiting:2
Countersleeping1second
visiting:1
visiting:2

這說明只有在建立Counter例項的時候,才會sleep一秒,呼叫visit函式的時候,不會sleep。

所以,類裝飾器實際上裝飾的是類的初始化方法。只有初始化的時候會裝飾一次

用裝飾器實現單例模式

上面的執行結果很讓人失望,如果去面試,還是會給自己兩個耳刮子的。

作為一個計數器,應該計數是不斷疊加的。

可是上面的程式碼,建立了兩個計數器,自己記錄自己的。扯淡啊!

我們現在就用類裝飾器改造它:

defsingleton(cls):
'''建立一個單例模式'''
defsingle_wrapper(*args,**kwargs):
ifnotsingle_wrapper.instance:
single_wrapper.instance=cls(*args,**kwargs)
returnsingle_wrapper.instance
single_wrapper.instance=None
returnsingle_wrapper

@singleton
classCounter():
def__init__(self):
self._count=0

defvisit(self):
self._count+=1
print(f'visiting:{self._count}')


c1=Counter()
c1.visit()
c1.visit()

c2=Counter()
c2.visit()
c2.visit()

先來執行一下:

visiting:1
visiting:2
visiting:3
visiting:4

結果很滿意,雖然建立了兩個Counter,計數是記錄在一起的。這主要得益於這個新的裝飾器:

defsingleton(cls):
'''建立一個單例模式'''
defsingle_wrapper(*args,**kwargs):
#如果沒有例項,則建立例項
ifnotsingle_wrapper.instance:
single_wrapper.instance=cls(*args,**kwargs)
#返回原來的例項,或者新的例項
returnsingle_wrapper.instance
#給新建立的函式新增一個屬性儲存例項
single_wrapper.instance=None
returnsingle_wrapper

它和其他的裝飾器基本一樣,它的不同之處在於這一行:

single_wrapper.instance = None

在建立完函式後,又給函式添加了一個屬性,用來儲存例項,開始為None,就是沒有例項。

再來分析一下程式碼邏輯:

  1. 先判斷是否有例項,如果沒有就建立一個。反過來,已經有了就不用建立。
  2. 返回例項。

把這個裝飾器加到類上的時候,就相當於加到了初始化方法。

當我們建立Counter的時候,被這個裝飾器截胡,它會返回一個已經建立好的例項。如果沒有例項,它會建立一個。

也就是說,不管呼叫Counter()多少次,最終就只有一個例項。這就是實現了單例模式。

如果有點不懂,再看一遍,為的是在面試官面前揚眉吐氣。

帶狀態的裝飾器

上面的例子中,我們看到裝飾器自己儲存了一個例項,你要的時候它就給你這一個,所以才實現了單例模式。這種就叫做帶狀態的裝飾器。

我們再來看一個例子。count裝飾器會記錄一個函式被呼叫的次數:

defcount(func):
defwrapper_count():
wrapper_count.count+=1
print(f'{func.__name__}:第{wrapper_count.count}次呼叫')
func()
wrapper_count.count=0
returnwrapper_count

@count
defrun():
print('跑步有利於身體健康,來一圈')

run()
run()
run()

執行結果:

run:第1次呼叫
跑步有利於身體健康,來一圈
run:第2次呼叫
跑步有利於身體健康,來一圈
run:第3次呼叫
跑步有利於身體健康,來一圈

關鍵點就在於這一行:

wrapper_count.count = 0

給wrapper_count函式添加了count屬性,來記錄函式呼叫的次數,它也是一個有狀態的裝飾器。

多個裝飾器巢狀

一個函式只能有一個裝飾器嗎?

裝飾器的本質就是先呼叫裝飾器,裝飾器再呼叫函式。既然這樣,那麼多呼叫幾層也無妨吧。

來看這個例子:

importtime
fromslowimportslow

deftimer(func):
defwrapper():
start_time=time.perf_counter()
func()
end_time=time.perf_counter()
used_time=end_time-start_time
print(f'{func.__name__}used{used_time}')
returnwrapper

@slow
@timer
defrun():
print('跑步有利於身體健康,來一圈')

run()

這個例子中,run函式用了兩個裝飾器,slow和timer。它的執行過程就相當於:

slow(time(run()))

從上到下呼叫,先是呼叫slow,然後slow去呼叫timer,然後timer去呼叫run,所以執行結果是:

runsleeping1second
跑步有利於身體健康,來一圈
wrapper_slowused1.0026384350000002

寶藏和禮物

差不多了,理解透這些原理,你就算不給面試官兩個耳刮子,至少也不用給自己了。相關問題就算不是對答如流,也能輕鬆應對吧。

裝飾器太重要了,有很多大神寫了各種各樣的裝飾器,Python官方文件為了一份裝飾器列表,在搜尋引擎搜:PythonDecoratorLibrary。

給你幾百G視訊,也不一定有用,不如短小精悍,拿來就可以用的程式碼。對,我的福利就是上文和本文用的所有裝飾器的示例程式碼。

我覺得也沒必要下載,直接收藏就行了,需要的時候再來下載。

最後,希望你能點贊,點再看轉發。如果不能奢求三連,就隨便來點吧。謝謝您的閱讀。