1. 程式人生 > 實用技巧 >python| 閉包函式及裝飾器

python| 閉包函式及裝飾器

閉包函式

什麼是閉包函式呢?

  • 閉: 封閉
  • 包: 包裹
    比如手機是閉包函式(內層函式),被手機包裝盒 (外層函式) 包裹起來,
    手機可以使用包裝盒中的東西,內層函式可以引用外層函式的名字。
  • 閉包函式必須在函式內部定義
  • 閉包函式可以引用外層函式的名字.

閉包函式是 函式巢狀, 函式物件, 名稱空間與作用域的 結合體

1
2
3
4
5
6
7
8
9
10
 def func(y):
x = 100
# inner是閉包函式
def inner():
print(x)
print(y)
return inner

inner = func()
inner()

裝飾器

什麼是裝飾器?

  • 裝飾: 裝飾,修飾 。
  • 器: 工具。
  • 裝飾的工具。

“開放封閉”: 裝飾器必須要遵循 “開放封閉” 原則。

  • 開放:
    對函式功能的新增是開放的。

  • 封閉:
    對函式功能修改是封閉的。

裝飾器的作用?

  • 在不修改被裝飾物件原始碼與呼叫方式的前提下, 新增新的功能。

裝飾器的定義必須遵循
不修改被裝飾物件原始碼
不修改被裝飾物件呼叫方式

為什麼使用裝飾器

可以解決程式碼冗餘問題,提高程式碼的可擴充套件性。

程式碼示例:

- 無裝飾器實現執行時間統計
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def download_movie():
print('開始下載電影...')
# 模擬電影下載時間 3秒
time.sleep(3) # 等待3秒
print('電影下載成功...')
return '小澤.mp4'



start_time = time.time() # 獲取當前時間戳
download_movie()
end_time = time.time() # 獲取當前時間戳
print(f'消耗時間: {end_time - start_time}')
# 問題: 多個被裝飾物件,需要寫多次統計時間的程式碼,導致程式碼冗餘。
- 使用函式實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def time_record(func):
start_time = time.time() # 獲取當前時間戳
# 寫死了,該功能只能給一個函式使用
# download_movie()
func()
end_time = time.time() # 獲取當前時間戳
print(f'消耗時間: {end_time - start_time}')
return None

# 被裝飾物件的呼叫方式
res = time_record(download_movie)
print(res) # None

# 問題, 更改了呼叫方式.

裝飾器: 初級版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


def time_record(func):
def inner(*args, **kwargs):
# 統計開始
start_time = time.time()

# 被裝飾物件, 問題1: 有返回值, 問題2: 不確定引數的 個數
res = func(*args, **kwargs) # func() ---> download_movie()
# 當被統計的函式執行完畢後,獲取當前時間
end_time = time.time()
# 統計結束,列印統計時間
print(f'消耗時間: {end_time - start_time}')

return res

return inner


download_movie = time_record(download_movie) # inner
# name = 'egon'
# download_movie(name)
download_movie(name='egon')

裝飾器, 終極版.

考慮被裝飾物件,有引數,有返回值的情況.

1
2
3
4
5
6
7
8
9

def time_record(func): # 引數用於接收被裝飾物件
def inner(*args, **kwargs) # 接收所有的被裝飾物件的引數
# 呼叫前新增功能
res = func(*args, **kwargs) # 呼叫被裝飾物件, 並接收返回值.
# 呼叫後新增功能
return res # 返回被裝飾物件的返回值

return inner # 返回inner

裝飾器語法糖,

裝飾器使用的兩種方式.

  • 裝飾器使用得時候 , 需要兩步,
    • 呼叫裝飾器函式, 並將被裝飾物件傳入,將返回值用 “呼叫函式”的名字賦值
    • 呼叫被重新賦值後的 “名字”
  • 程式碼演示
    1
    2
    download_movie= time_record(*args, **kwargs)
    download_movie()

    語法糖

    即在被裝飾物件的上方 用@+裝飾器 來進行使用裝飾器
    語法糖是python直譯器提供的, 用來簡便使用裝飾器的一種方式.

注意, 使用語法糖時 , 裝飾器的定義要在被裝飾物件之前, 不然會報錯.

裝飾器的疊加.

當一個被裝飾物件, 需要多個功能時, 應該使用多個不同功能的裝飾器, 而==不是在一個裝飾器中新增多個功能==.

裝飾器疊加原則.

  • 裝飾時 是 從下往上
  • 呼叫時 是 從上往下

有參裝飾

當我們需要判斷使用者的許可權, 就需要獲取使用者的身份資訊, 並傳入裝飾器中,進行判斷, 這就需要有參裝飾器. 程式碼示例如下.

有參裝飾器的定義

1
2
3
4
5
6
7
8
9
10
def outher(需要傳入的引數)
def wrapper(func): # 引數用於接收被裝飾物件
def inner(*args, **kwargs) # 接收所有的被裝飾物件的引數
# 呼叫前新增功能
res = func(*args, **kwargs) # 呼叫被裝飾物件, 並接收返回值.
# 呼叫後新增功能
return res # 返回被裝飾物件的返回值

return inner # 返回inner
return wrapper

有參裝飾器的呼叫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def outher(需要傳入的引數)
def wrapper(func): # 引數用於接收被裝飾物件
def inner(*args, **kwargs) # 接收所有的被裝飾物件的引數
# 呼叫前新增功能
res = func(*args, **kwargs) # 呼叫被裝飾物件, 並接收返回值.
# 呼叫後新增功能
return res # 返回被裝飾物件的返回值

return inner # 返回inner
return wrapper


@outer(傳入引數)
def func():
pass

func()

warps 的使用.

在每個函式內, 我們都是需要寫註釋 , 來表明此函式所實現的功能,和用法. 使用裝飾器的時候, 也是需要添加註釋, 表明裝飾器實現的是某個功能.

  • 當裝飾器和被裝飾物件同時有註釋的時候, 呼叫被裝飾物件的.doc() 方法就不能看到其註釋說明.
  • 程式碼示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

    from functools import wraps


    def wrapper(func):
    def inner(*args, **kwargs):
    '''
    this is the doc from inner
    :param args:
    :param kwargs:
    :return:
    '''
    res = func()
    return res
    return inner


    @wrapper
    def func1():
    '''
    this is doc from func1
    :return:
    '''
    pass


    print(func1.__doc__)


    # 輸出結果

    this is the doc from inner
    :param args:
    :param kwargs:
    :return:
    由此可見, 在使用裝飾器後, 被裝飾物件的註釋被汙染, 因此可以用, wraps 進行修復.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29


from functools import wraps


def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
'''
this is the doc from inner
:param args:
:param kwargs:
:return:
'''
res = func()
return res
return inner


@wrapper
def func1():
'''
this is doc from func1
:return:
'''
pass


print(func1.__doc__)

在inner的上方使用@wraps(func) 並將被裝飾函式傳入wraps , 即可