JZ76 刪除鏈表中重覆的結點 題目 在一個排序的鏈表中,存在重覆的結點,請刪除該鏈表中重覆的結點,重覆的結點不保留,返回鏈表頭指針。 例如,鏈表 1->2->3->3->4->4->5 處理後為 1->2->5 方法1 哈希表進行刪除 思路 演算法實現 LinkedHashMap實現順序插入,不過查 ...
5.4 函數的特殊使用方式
5.4.1 匿名函數
所謂匿名函數,即不再使用def語句這樣標準形式定義的函數。Python中可以使用lambda
關鍵字來創建匿名函數。用lambda創建的匿名函數的函數體比def
定義的函數體要簡單。語法如下:
lambda [參數1[,參數2],....參數n]]:表達式
lam_sum = lambda arg1, arg2: arg1 + arg2
print(lam_sum(10, 20))
30
上述代碼中,第一行定義了一個lambda函數,執行兩個數的和運算,並且把該lambda函數命名為lam_sum。然後通過lam_sum()函數實現求和的功能。
Lambda創建的匿名函數中只能封裝有限的邏輯進去。
lambda函數擁有自己的命名空間,且不能訪問自有參數列表之外或全局命名空間里的參數。
實際上,一般在使用匿名函數時是不會再為創建的匿名函數命名的。因為這樣失去了匿名函數的簡便性。在有些場景是需要傳入函數,需要的邏輯並不是很複雜。但是又不想再創建一個,這個時候就可以直接使用匿名函數了。如下:
print(list(map(lambda x: x * x, [1, 2, 3, 4, 5])))
[1, 4, 9, 16, 25]
5.4.2 遞歸調用
在Python定義函數時,函數體中可以調用其他函數,甚至可以調用自己。這種自己調用自己的方式叫做遞歸調用。下麵是一個遞歸式函數定義:
def recursion():
return recursion()
顯然,對於上面定義的函數,如果你運行它,你將發現運行一段時間後,這個程式崩潰了(引發異常)。
從理論上說,這個程式將不斷運行下去,但每次調用函數時,都將消耗一些記憶體。因此函數調用次數達到一定的程度(且之前的函數調用未返回)後,將耗盡所有的記憶體空間,導致程式終止並顯示錯誤消息“超過最大遞歸深度(maximum recursion depth exceeded,預設最大為1000次)”。
可以通過以下代碼修改最大遞歸深度:
import sys
sys.setrecursionlimit(99999)
這個函數中的遞歸稱為無窮遞歸(就像以 while True 打頭且不包含 break 和 return 語句的迴圈被稱為無限迴圈一樣),因為它從理論上說永遠不會結束。你想要的是能對你有所幫助的遞歸函數,這樣的遞歸函數通常包含下麵兩部分。
基線條件:滿足這種條件時函數將直接返回一個值。
遞歸條件:包含一個或多個調用,這些調用旨在解決問題的一部分。
這裡的關鍵是,通過將問題分解為較小的部分,可避免遞歸沒完沒了,因為問題終將被分解成基線條件可以解決的最小問題。
那麼如何讓函數調用自身呢?這沒有看起來那麼難懂。前面說過,每次調用函數時,都將為此創建一個新的命名空間。這意味著函數調用自身時,是兩個不同的函數[更準確地說,是不同版本(即命名空間不同)的同一個函數]在交流。你可將此視為兩個屬於相同物種的動物在彼此交流。
遞歸示例1:通過遞歸的方式求一個數的階乘
def factorial(p_int=0):
if p_int == 0: # 基線條件
return 1
else: # 遞歸條件
return p_int * factorial(p_int - 1)
print(factorial(10))
3628800
遞歸示例2:通過遞歸的方式求冪
def power(x, n):
return 1 if n == 0 else x * power(x, n - 1)
print(power(2, 10))
1024
遞歸示例3:通過遞歸的方式解決漢諾塔問題
def move(n, a='A', b='B', c='C'):
if n == 1:
print('移動', a, '-->', c)
else:
move(n - 1, a, c, b)
move(1, a, b, c)
move(n - 1, b, a, c)
move(4)
移動 A --> B
移動 A --> C
移動 B --> C
移動 A --> B
移動 C --> A
移動 C --> B
移動 A --> B
移動 A --> C
移動 B --> C
移動 B --> A
移動 C --> A
移動 B --> C
移動 A --> B
移動 A --> C
移動 B --> C
在某些特殊的問題中,如果通過普通的迴圈方式雖然也可以實現,但在使用了遞歸的方式後代碼更加簡單。邏輯也更加清楚。
理論上,所有的遞歸函數都可以寫成迴圈的方式,但迴圈的邏輯不如遞歸清晰。
使用遞歸函數需要註意防止棧溢出。在電腦中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多會導致棧溢出。
5.4.3 偏函數
參考:偏函數
介紹函數參數的時候,我們講到,通過設定參數的預設值,可以降低函數調用的難度。而偏函數也可以做到這一點。
int()函數可以把字元串轉換為整數,當僅傳入字元串時,int()函數預設按十進位轉換:
>>> int('12345')
12345
但int()函數還提供額外的base參數,預設值為10。如果傳入base參數,就可以做N進位的轉換:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假設要轉換大量的二進位字元串,每次都傳入int(x, base=2)非常麻煩,於是,我們想到,可以定義一個int2()的函數,預設把base=2傳進去:
def int2(x, base=2):
return int(x, base)
這樣,我們轉換二進位就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial就是幫助我們創建一個偏函數的,不需要我們自己定義int2(),可以直接使用下麵的代碼創建一個新的函數int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
所以,簡單總結functools.partial的作用就是,把一個函數的某些參數給固定住(也就是設置預設值),返回一個新的函數,調用這個新函數會更簡單。
註意到上面的新的int2函數,僅僅是把base參數重新設定預設值為2,但也可以在函數調用時傳入其他值:
>>> int2('1000000', base=10)
1000000
最後,創建偏函數時,實際上可以接收函數對象、*args和**kw這3個參數,當傳入:
>>> int2 = functools.partial(int, base=2)
實際上固定了int()函數的關鍵字參數base,也就是:
>>> int2('10010')
相當於:
>>> kw = { 'base': 2 }
>>> int('10010', **kw)
當傳入:
>>> max2 = functools.partial(max, 10)
實際上會把10作為args的一部分自動加到左邊,也就是:
>>> max2(5, 6, 7)
相當於:
>>> args = (10, 5, 6, 7)
>>> max(args)
10
當函數的參數個數太多,需要簡化時,使用functools.partial可以創建一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單。
5.4.4 閉包
閉包就是一種函數的嵌套,首先定義了一個函數,稱之為外部函數
。在這個外部函數體中又定義了一個內部函數
,並且這個內部函數體中使用到了外部函數的變數。外部函數最後return了內部函數。那麼這個外部函數以及內部函數就構成了一個特殊的對象,稱之為閉包。
閉包避免了使用全局變數,使得局部變數在函數外被訪問成為可能,相比較面向對象,不用繼承那麼多的額外方法,閉包占用了更少的空間。
閉包示例
a = 1
def out_fun(b):
c = 3
def in_fun(d):
print(a + b + c + d)
return in_fun
infun = out_fun(2)
infun(4)
10
可以看到,在內部函數in_fun
中訪問到了全局變數a、外部函數out_fun
的局部變數c以及參數b。
5.4.4.2 裝飾器
裝飾器(decorator)的本質:函數閉包(function closure)的語法糖(Syntactic sugar)。通過給函數裝飾,可以加強函數的功能或者增加原本函數沒有的功能。
裝飾器在第一次調用被裝飾函數時進行增強,並且只增強一次。
讓我們先從一個簡單的函數開始吧。假設現在有一個函數,用來計算從1到100累加之和,並輸出結果。為了避免計算太快,我們在使用迴圈累加時設置等待0.01秒,函數定義如下:
def mysum1():
from time import sleep
total = 0
for _ in range(101):
sleep(0.01)
total += _
print(total)
mysum1()
5050
此時,如果我們想要知道調用這個函數執行一共花了多少時間,我們可以在執行前後獲取時間再經過計算得到,也可以通過改造函數,在函數內部函數體前後獲取時間計算得到。但是這兩種方法都比較麻煩,尤其是第二種方法,需要侵入式修改函數代碼。
這個時候就可以通過為函數添加裝飾器來實現了。下麵是創建裝飾器的一般方法:
裝飾器的簡單定義
def decorator1(func):
def inner():
print('在這裡執行被裝飾函數執行前的增強操作')
func() # 執行被裝飾的函數
print('在這裡執行被裝飾函數執行前的增強操作')
return inner
從上面可以看出,裝飾器也是一個函數。只不過裝飾器接收的參數是被裝飾的函數。然後再裝飾器內部定義一個函數,該內部函數體中執行要增強的操作代碼以及執行被裝飾的函數。最後再return該內部函數。
接下來是用裝飾器來對某個函數進行裝飾。我們以上面定義的mysum1函數來進行裝飾:
裝飾器的使用
@decorator1
def mysum1():
from time import sleep
total = 0
for _ in range(101):
sleep(0.01)
total += _
print(total)
mysum1()
在這裡執行被裝飾函數執行前的增強操作
5050
在這裡執行被裝飾函數執行前的增強操作
由上面可以看出,如果要裝飾某個函數,只需要在定義這個函數時,在def語句的上一行添加@裝飾器函數
即可。
對於裝飾器裝飾一個函數:
@decorator
def myfun():
print("hello")
上面的代碼等價於:
def myfun():
print("hello")
myfun = decorator(myfun)
當一個裝飾器裝飾函數時,函數的功能增強了,因為在調用這個函數時,實際上調用的是在定義裝飾器函數時,其內部函數。而此時內部函數是由增強功能命令和原被裝飾函數組成。
創建一個統計函數運行時長的裝飾器
import time
def decorator1(func):
def inner():
begin = time.time()
func() # 執行被裝飾的函數
end = time.time()
print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3} 秒")
return inner
@decorator1
def mysum1():
from time import sleep
total = 0
for _ in range(101):
sleep(0.01)
total += _
print(total)
mysum1()
5050
函數mysum1
運行的總時間為:1.59 秒
5.4.4.2.2 被裝飾函數接收參數
在上面的例子中,通過裝飾器函數decorator
裝飾的函數是不能有輸入參數的,在實際使用中並不是很方便。
通過對裝飾器進行改造可以避免這種情況,從而使裝飾器函數有更廣泛的用途。
裝飾器定義:讓被裝飾函數接收參數
import time
def decorator2(func):
def inner(*args, **kwargs):
begin = time.time()
func(*args, **kwargs) # 執行被裝飾的函數
end = time.time()
print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3}")
return inner
需要改造的地方:
1、為裝飾器函數decorator
的內部函數inner
在定義時增加收集位置形參和收集關鍵字形參
2、在裝飾器函數decorator
的內部函數inner
函數體中,執行被裝飾器裝飾的函數func
時,通過參數解包的方式傳入參數。
裝飾帶有參數的函數:
@decorator2
def mysum2(a, b):
from time import sleep
total = a
for _ in range(total + 1, b + 1):
sleep(0.01)
total += _
print(total)
mysum2(1, 100)
5050
函數mysum1
運行的總時間為:1.56
5.4.4.2.3 裝飾器函數接收參數
通過上面對裝飾器進行改造,可以使的被裝飾的函數可以輸入參數。上面的裝飾器函數decorator2
可以計算被裝飾的函數執行時間,但是只能獲取到執行一次的時間。如果想要通過參數獲取執行任一次的時間,則需要使得裝飾器可以接收參數。
裝飾器定義:裝飾器接收參數
import time
def decorator3(n):
def inner(func):
def wrapper(*args, **kwargs):
begin = time.time()
for _ in range(iteration):
func(*args, **kwargs)
end = time.time()
print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3}")
return wrapper
return inner
需要改造的地方:
1、裝飾器函數此時並不是通過參數來傳入被裝飾的函數,而是定義裝飾器自己的參數,演示時使用的是一個位置參數n,在後續如果遇到比較複雜的情況下也可以使用關鍵字形參、*args、**kwargs等收集參數。
2、內部函數inner
用來收集被裝飾的函數。
3、內部函數inner
的內部函數wrapper
用來收集被裝飾的函數的參數。並編寫需要增強的命令。最終要執行被裝飾的函數其實就是執行這個wrapper
函數。
裝飾器接收參數:
import time
def decorator3(n):
def inner(func):
def wrapper(*args, **kwargs):
begin = time.time()
for _ in range(n):
func(*args, **kwargs)
end = time.time()
print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3}")
return wrapper
return inner
@decorator3(10)
def mysum3(a, b):
from time import sleep
total = a
for _ in range(total + 1, b + 1):
sleep(0.01)
total += _
print(total)
mysum3(1, 10)
# 等價於:mysum3 = decorator3(10)(mysum3)
55
55
55
55
55
55
55
55
55
55
函數mysum3
運行的總時間為:1.41
5.4.4.2.4 裝飾器的返回值
如果你瞭解了上一節的內容,很容易想到只要在wrapper
函數中return就是被裝飾函數的返回值。
裝飾器定義:接收被裝飾函數的返回值
import time
def decorator3(n):
def inner(func):
def wrapper(*args, **kwargs):
begin = time.time()
for _ in range(n):
func(*args, **kwargs)
end = time.time()
print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3}")
return end - begin
return wrapper
return inner
1、在上面的代碼中,return end – begin就是被裝飾器的返回值。可以通過變數進行接收。
@decorator3(3)
def mysum3(a, b):
from time import sleep
total = a
for _ in range(total + 1, b + 1):
sleep(0.01)
total += _
print(total)
total_time = mysum3(1, 10)
print(total_time)
函數
mysum3
運行的總時間為:1.42
1.4218323230743408
5.4.4.2.5 多個裝飾器裝飾同一個函數
對於某個函數,可以使用多個裝飾器對其進行裝飾,寫法如下:
@decorator1
@decorator2
def 被裝飾函數():
pass
對於被多個裝飾器裝飾的函數,其裝飾順序為由最近到遠,即decorator2會先裝飾,然後是decorator1。
從自動化辦公到智能化辦公