一文讀懂python裝飾器由來(二)

2021-02-20 Python中文社區

--  Illustrations by Charlie Davis  --

上一篇文章主要以一步一步演進的方式介紹了裝飾器的工作原理以及使用(沒看的小夥伴可以關注一下 一文讀懂Python裝飾器由來(一)),其實只要認真學習上一篇文章,已經能夠滿足日常對裝飾器的使用了。但是,若想真正理解裝飾器,並進行更高階的使用還要了解其他一些知識:

python中,函數是一等對象;

區分導入時執行和運行時執行;

閉包和 nonlocal 聲明;

下面我們逐個介紹:

第一點,在 Python 中,函數是一等對象,這在上一篇其實已經提到了。「一等對象」滿足下述條件: 

a.在運行時創建; 

b.能賦值給變量或數據結構中的元素; 

c.能作為參數傳給函數; 

d.能作為函數的返回結果;

Python 中的整數、字符串和字典等都是一等對象,大家對比著理解一下,在此不再過多介紹。 第二點,函數裝飾器在導入模塊時立即執行,而被裝飾的函數只在明確調用時運行。看下面的例子:

al = []

def deco(func):

   print('running deco and parm is{}'.format(func))

   al.append(func)

   return func

@deco

def f1():

   print('running f1()')

@deco

def f2():

   print('running f2()')

def f3():

   print('running f3()')

def main():

   print('running main()')

   print('al ->', al)

   f1()

   f2()

   f3()

if __name__=='__main__':

   main()

輸出:

running deco and parm is<function f1 at 0x00000000006C2AE8>

running deco and parm is<function f2 at 0x00000000011E6510>

running main()

al -> [<function f1 at 0x00000000006C2AE8>, <function f2 at 0x00000000011E6510>]

running f1()

running f2()

running f3()

我們簡單定義了一個裝飾器,把傳進來的參數(函數名)添加到列表,然後再返回該函數名。觀察輸出結果,在運行main函數之前,deco就已經運行了(輸出了2次,因為f1和f2都用deco進行了裝飾),之後對列表的輸出也印證了這一點,而不管是被裝飾的f1、f2還是未被裝飾的f3都是在明確的調用之後才執行的。這就是Python 程式設計師所說的導入時和運行時之間的區別。 第三點,閉包可以說是行為良好的裝飾器賴以生存的關鍵。閉包其實並不難以理解,因為它只存在於嵌套函數中。還是看例子:

def get_averager():

   nums = []

   def averager(new_value):

       nums.append(new_value)

       total = sum(nums)

       return total/len(nums)

   return averager

avg = get_averager()

print(avg)

print(avg(10))

print(avg(11))

print(avg(12))

輸出:

<function get_averager.<locals>.averager at 0x0000000000672AE8>

10.0

10.5

11.0

定義一個嵌套函數,作用是計算累計傳入參數的平均值。通過輸出結果我們可以看到avg是getaverager()返回的averager,通過不斷的調用avg(),返回當前的平均值。這裡面有個問題是我們之前沒有探討的:nums是外層函數中的變量,那麼在getaverager()返回完畢之後,它的本地作用域應該一併消失,那為什麼avg中還可以使用呢?這就是閉包的作用了。其實,閉包就是指函數作用域延伸了(從外層函數延伸到內層函數)。延伸的值保存在內層函數的code屬性中:

>>> def get_averager():

   nums = []

   def averager(new_value):

       nums.append(new_value)

       total = sum(nums)

       return total/len(nums)

   return averager

>>> avg = get_averager()

>>> avg.__code__.co_freevars

('nums',)

我們注意到上面這個例子把所有值存儲在歷史列表中,然後在每次調用 averager 時使用 sum 求和。更好的實現方式是,只存儲目前的總值和元素個數,然後使用這兩個數計算均值。依照這個思路我們可以對代碼進行優化,但是在此之前我們需要看一個簡單的例子:

>>> b = 99

>>> def f(t):

   print(t)

   print(b)

   b = 2  

>>> f(10)

各位可以想像一下,這個輸出會是什麼?

10

99

是不是這個?其實不然,真實的結果是這樣:

>>> f(10)

10

Traceback (most recent call last):

 File "<pyshell#42>", line 1, in <module>

   f(10)

 File "<pyshell#41>", line 3, in f

   print(b)

UnboundLocalError: local variable 'b' referenced before assignment

>>>

這個結果可能讓你驚訝,但事實就是如此。因為Python 編譯函數的定義體時,由於b在函數中給它賦值了,因此它判斷 b 是局部變量。後面調用 f(10) 時, f 的定義體會獲取並列印局部變量 b的值,但是嘗試獲取局部變量 b的值時,發現 b 沒有綁定值。這不是缺陷,而是設計選擇:Python 不要求聲明變量,但是假定在函數定義體中賦值的變量是局部變量。了解了這一點,我們來優化一下之前計算平均值的例子:

def get_averager():

   count = 0

   total = 0

   def averager(new_value):

       count += 1

       total += new_value

       return total / count

   return averager

邏輯上看沒啥問題,但是有了之前的鋪墊,你可能會發現一些問題:內層函數對外層函數中的變量進行了重新賦值。我們來運行一下代碼,就會發現報錯:

UnboundLocalError: local variable 'count' referenced before assignment

而優化前的例子沒遇到這個問題,因為nums是列表,我們只是調用 nums.append,也就是說,我們利用了列表是可變的對象這一事實。但是對數字、字符串、元組等不可變類型來說,只能讀取,不能更新。如果嘗試重新綁定,例如 count = count + 1,其實會隱式創建局部變量 count。 為了解決這個問題,Python 3 引入了 nonlocal 聲明,如果為 nonlocal 聲明的變量賦予新值,閉包中保存的綁定會更新。

>>> def get_averager():

   count = 0

   total = 0

   def averager(new_value):

       nonlocal count, total

       count += 1

       total += new_value

       return total / count

   return averager

>>> avg = get_averager()

>>> avg(10)

10.0

>>> avg(11)

10.5

>>> avg(12)

11.0

以上三點就是對裝飾器基礎知識的補充,希望對大家有所幫助。

讚賞作者

最近熱門文章

用Python更加了解微信好友

如何用Python做一個騷氣的程式設計師

用Python爬取陳奕迅新歌《我們》10萬條評論的新發現

用Python分析蘋果公司股價數據

Python自然語言處理分析倚天屠龍記

▼ 點擊下方閱讀原文免費成為社區會員

相關焦點

  • 一文讀懂@Decorator裝飾器——理解VS Code源碼的基礎(上)
    其實不止VS Code,Angular、Node.js框架Nest.js、TypeORM、Mobx(5) 和Theia等都深度用到了裝飾器語法,為了讀懂各大優秀開源項目,讓我們先一起來把@Decorator裝飾器的原理以及用法徹底弄懂。
  • 一文讀懂Python裝飾器,這是一個會打扮的裝飾器
    但是,它也有很多較難掌握的高級功能,比如裝飾器(decorator)。很多初學者一直不理解裝飾器及其工作原理,在這篇文章中,我們將介紹裝飾器的來龍去脈。在 Python 中,函數是一種非常靈活的結構,我們可以把它賦值給變量、當作參數傳遞給另一個函數,或者當成某個函數的輸出。裝飾器本質上也是一種函數,它可以讓其它函數在不經過修改的情況下增加一些功能。
  • 【進階】一文讀懂Python裝飾器,搞清來龍去脈!
    如下所示我們用 @dec 裝飾器修飾函數 func ():@decdef func():  pass理解裝飾器的最好方式是了解裝飾器解決什麼問題,本文將從具體問題出發一步步引出裝飾器,並展示它的優雅與強大。設置問題為了解裝飾器的目的,接下來我們來看一個簡單的示例。
  • 老司機教你 5 分鐘讀懂 Python 裝飾器
    寫在前面在介紹python裝飾器之前,首先介紹python的一個概念,對象。在python裡,所有的一切皆對象。常用的python對象有整型對象,浮點型對象,字符串對象,列表對象,元組對象,字典對象等。其中一個比較特殊的對象是函數對象。
  • 一文讀懂Python closure
    無意中發現幾篇很nice的Python英文推文,思路、結構都清晰。在此,結合自己的理解,稍作翻譯並進行內容講解,雖然都是英文的,但是木有想像中的難的,小夥子加油哈!This can be illustrated by following example:譯:對於一個function(不妨記為f2),假設其外部還包了一層function(不妨記為f1),這f1的變量,則f1、f2構成了簡單的nested functionsf1的中的變量相對與f2中的變量而言就是non-local variables
  • Python 裝飾器
    > print("裝飾器加載成功!")f1()運行結果:裝飾器加載開始f1函數執行.裝飾器加載成功!代碼剖析首先不管outer()函數,按照python正常的執行順序從上往下執行,先執行的是@outer,這裡是一個語法糖,Python立刻執行這個函數。
  • 推薦8個炫酷的 Python 裝飾器!
    收錄於話題 #python可以從這樣的裝飾器中受益的函數的一個很好的例子是遞歸函數,例如計算階乘的函數:def factorial(n): return n * factorial(n-1) if n else 1遞歸在計算時間上可能非常困難,但添加此裝飾器有助於顯著加快此函數的連續運行速度
  • Python裝飾器入門學習
    裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼並繼續重用。 從這裡也可以看出裝飾器的參數傳入順序,從上到下,裝飾器參數,裝飾器修飾函數,和函數參數內置裝飾器內置裝飾器與普通裝飾器原理上不同,返回的是一個類對象,@propertyclass TestEntiy(object):    def __init__(self):        super(TestEntiy, self
  • Python裝飾器
    這個時候裝飾器就開始閃亮登場了!初識裝飾器首先我們來看一個例子,然後來解釋什麼是裝飾器。decorator_blog.py[line:10]INFO add logging is run裝飾器語法糖在python中相信使用過unittest或pytest單元測試框架的同學對於 @符號一定不會陌生, 比如我們經常在unittest中會看到如下類似的用法:@
  • python系列之裝飾器(Decorator)
    python裝飾器的作用:在不改動原函數的情況下對其進行功能或特性的擴展。裝飾器本身也是一個函數,只是這個函數返回的是一個閉包。
  • python裝飾器的詳細解析[轉載]
    收錄於話題 #python(但是生效需要再次執行函數)import timedef deco(func): start_time = time.time() f() end_time = time.time() execution_time = (end_time - start_time)*1000 print("time
  • 獨家 | 一文讀懂Adaboost
    優點:非常容易訓練,實現起來比較容易泛化錯誤率低(預測性能好),不易過擬合不需要調節很多參數,最多修改一下基礎模型的數量適用範圍廣:二分類問題,多分類問題,回歸問題 缺點: Adaboost的實際應用
  • 來簡單聊聊python的裝飾器呀~
    裝飾器是個啥?要了解裝飾器,自然要先明白它到底是個啥東西啦。簡單來說,裝飾器其實就是一個函數,這個函數接受其他函數作為參數,並將其以一個新的修改後的函數作為替換。舉個例子?讓我們來舉個例子以更好地理解上面那句話的含義。
  • 淺談Python裝飾器
    文 | 蒹葭來源|微信公眾號 Qtest之道
  • 5分鐘掌握 Python 中的裝飾器
    python中的裝飾器用於修飾函數,以增強函數的行為:記錄函數執行時間,建立和撤銷環境,記錄日誌等。裝飾器可以在不修改函數內部代碼的前提下實現以上增強行為。如下代碼建立一個計時裝飾器,隨後描述其工作原理。
  • Selenium2+python自動化55-unittest之裝飾器(@classmethod)
    這就需要用到裝飾器(@classmethod)來解決了。 一、裝飾器1.用setUp與setUpClass區別setup():每個測試case運行前運行teardown():每個測試case運行完後執行setUpClass():必須使用@classmethod 裝飾器,所有case運行前只運行一次tearDownClass():必須使用@classmethod裝飾器,所有case
  • Python--- 裝飾器
    裝飾器可以對一個函數、方法或者類進行加工。在Python中,我們有多種方法對函數和類進行加工,比如在Python閉包中,我們見到函數對象作為某一個函數的返回結果。相對於其它方式,裝飾器語法簡單,代碼可讀性高。因此,裝飾器在Python項目中有廣泛的應用。
  • 推薦 8 個炫酷的 Python 裝飾器
    裝飾器可以用來縮短代碼、加速代碼並徹底改變代碼在 Python 中的行為方式。不用說,這當然可以派上用場!今天我想炫耀一些我認為值得一試的裝飾器。有很多裝飾器,但我選擇了一些我認為具有最酷功能的裝飾器。
  • 一分鐘讀懂Python安裝指南
    來源:計量經濟學服務中心文字版本為:2018年6月推文
  • python筆記36-裝飾器之wraps
    前言前面一篇對python裝飾器有了初步的了解了,但是還不夠完美,領導看了後又提出了新的需求,希望運行的日誌能顯示出具體運行的哪個函數__doc__) # func_b --> world裝飾器加函數名稱日誌在裝飾器裡面添加2行代碼,列印正在運行函數的名稱和docstring內容import timedef runtime(func): '''runtime decorators''' def wrapper(*args, **kwargs): '''wrapper