學到裝飾器,然後總結一下理解裝飾器需要的知識

一.python的作用域

當然,Python與大多數編程語言一樣,搜索變數值的時候,即命名空間的規則,會採用就近原則.

  • 具體來說,由近及遠依次為: 本地作用域(Local) --> 外部嵌套函數作用域(Enclosing Local) --> 全局/模塊作用域(Global) --> 內置作用域(Built-in).

a = abs(-1) # abs:Built-in
b = 3 # b:Global
def f1():
print(b) # b:Global
a = 3 # a:Local
print(a)
def f2():
a = 3 # a:Enclosing
def f3():
b = 3 # b:Local
print(b)

  • Local 與 Enclosing 是一個相對的概念. 在函數 f1 中, a 是一個 Local 變數, 而在 f2 中 a 是一個 Enclosing 變數.
  • 只有模塊、類、函數才會引入新的作用域. 而 if for while 語句不會引入新的作用域.
  • 全局作用域中的變數對於下層作用域比如函數來說, 是一個只讀變數.

a = 4
def foo():
a = 3
foo()
print(a) # 4

函數內部不是修改全局變數 a 的值,而是重新定義了一個本地變數 a.所以全局變數 a 的值沒有改變

a = 4
def foo():
a = a + 3
foo()
# local variable a referenced before assignment

在函數 foo 內部, a = a + 3 這個表達式的存在會讓 Python 編譯函數的定義體時,它判斷 a 是局部變數,因為在函數中給它賦值了。

  • 內部作用域中要修改外部作用域變數的值時,要用 global、nonlocal 關鍵字聲明外部作用域變數

a = 3
def foo():
global a # 使用 global 聲明 a ,便可以在函數中修改 a 的值
a = 4
foo()
print(a)
# 4
def f1():
a = 3
def f2():
nonlocal a # 使用 nonlocal 聲明 a, 便可以在嵌套的函數內部修改 a 的值
a = a + 1
print(a)
f2()

二.閉包與自由變數

什麼是閉包?

閉包指延伸了作用域的函數,其中包含函數定義體中引用、但是不在定義體中定義的非全局變數。函數是不是匿名的沒有關係,關鍵是它能訪問定義體之外定義的非全局變數

總結:閉包是個函數,它能夠訪問函數體之外定義的非全局變數,而這個非全局變數指的就是自由變數.

下面舉一個例子, 定義一個 avg 函數,參數為一個值, 不斷累加的計算從開始到現在所接收的全部值的平均值

def make_average():
series = []
def average(value):
series.append(value)
total = sum(series)
return total / len(series)
return average
avg = make_average() # 1
avg(10) # 2
# 10
avg(20) # 3
# 15

說明:1調用 make_average 返回一個 average 函數對象.這就是一個閉包函數,因為 avg 可以訪問 average 函數定義體之外的 series .

注意:這裡嵌套函數 average 並沒有改變 series, 只是修改它的值, 因為 series 是一個可變的列表.所以並不會報錯.那麼如果 series 是一個不可變對象呢? 會發生什麼?

上面的例子效率比較低, 沒一次都得 sum.我們難道不可以保存每一步計算的 total 嗎?

def make_average():
count = 0
total = 0
def average(value):
count += 1
total += value
return total / count
return average
avg = make_average()
avg(10)
# local variable count referenced before assignment

說明: 在嵌套函數 average 內部有 count += 1 ,此表達式等價為 count = count + 1.在編譯階段, 會把內部的 count 解釋為一個本地變數, 所以如果沒有 nonlocal 聲明的話, 會報錯 local variable count referenced before assignment.

簡單的處理當然不行, 我們需要使用 nonlocal 將 count 和 total 變成自由變數

def make_average():
count = 0
total = 0
def average(value):
nonlocal count, total
count += 1
total += value
return total / count
return average
# 開始調用.返回一個函數對象,並且這個對象是一個閉包
avg = make_average()
avg(10) # 10
avg(20) # 15

三.裝飾器

裝飾器用來裝飾一個函數,為函數添加額外的功能,一般來說並不是核心功能.

裝飾器接收一個函數作為參數,裝飾器可能會處理被裝飾的函數,然後把它返回,或者將其替換成另一個函數或可調用對象。

舉例, 我們定義一個裝飾器,用來計算並且顯示每個函數運行的時間

import time
def decorate(func):
def wrapper(*args):
to = time.perf_counter()
result = func(*args)
t1 = time.perf_counter() - to
print(f運行時間為:{t1})
return result
return wrapper

讓我們裝飾一下別的函數

@decorate
def foo(n):
i = 0
while i < n*n:
i += 1
return i
print(foo(600))
# 程序的運行時間為:0.022877089999383315
# 360000

`@decorate `是一個語法糖, 等同於 func = decorate(func), 所以此時 func 是 wrapper 函數的引用.

如何證明?

foo.__name__
# wrapper

這是一個瑕疵啊,我們需要改進.由此我們需要使用一些標準庫裝飾器.

使用 functools.wraps 裝飾器把相關的屬性(func._name 和 func.doc_)從 func 複製到 wrapper 中

from functools import wraps
import time

def decorate(func):
@wraps(func) # 將func的一些屬性覆蓋到wrapper中去
def wrapper(*args):
to = time.perf_counter()
result = func(*args)
t1 = time.perf_counter() - to
print(f運行時間為:{t1})
return result
return wrapper

尾聲

腳踏實地的前進, 希望自己學得越來越多, 然後分享出來嘍.

推薦閱讀:

相關文章