Python 中的閉包與裝飾器

来源:http://www.cnblogs.com/jiaoyu121/archive/2017/06/23/7069337.html
-Advertisement-
Play Games

閉包(closure)是函數式編程的重要的語法結構。閉包也是一種組織代碼的結構,它同樣提高了代碼的可重覆使用性。 如果在一個內嵌函數里,對在外部函數內(但不是在全局作用域)的變數進行引用,那麼內嵌函數就被認為是閉包(closure)。 定義在外部函數內但由內部函數引用或者使用的變數稱為自由變數。 總 ...


閉包(closure)是函數式編程的重要的語法結構。閉包也是一種組織代碼的結構,它同樣提高了代碼的可重覆使用性。

如果在一個內嵌函數里,對在外部函數內(但不是在全局作用域)的變數進行引用,那麼內嵌函數就被認為是閉包(closure)。

定義在外部函數內但由內部函數引用或者使用的變數稱為自由變數。

總結一下,創建一個閉包必須滿足以下幾點:

  • 1. 必須有一個內嵌函數
  • 2. 內嵌函數必須引用外部函數中的變數
  • 3. 外部函數的返回值必須是內嵌函數

1.閉包使用示例

先看一個閉包的例子:

In [10]: def func(name):
    ...:     def in_func(age):
    ...:         print 'name:',name,'age:',age
    ...:     return in_func
    ...: 

In [11]: demo = func('feiyu')

In [12]: demo(19)
name: feiyu age: 19

這裡當調用 func 的時候就產生了一個閉包——in_func,並且該閉包持有自由變數——name,因此這也意味著,當函數func的生命周期結束之後,name這個變數依然存在,因為它被閉包引用了,所以不會被回收。

在 python 的函數內,可以直接引用外部變數,但不能改寫外部變數,因此如果在閉包中直接改寫父函數的變數,就會發生錯誤。看以下示例:

實現一個計數閉包的例子:

def counter(start=0):
    count = [start] 
    def incr():
        count[0] += 1
        return count
    return incr

a = counter()
print 'a:',a

In [32]: def counter(start=0):
    ...:     count = start
    ...:     def incr():
    ...:         count += 1
    ...:         return count
    ...:     return incr
    ...: 

In [33]: a = counter()

In [35]: a()  #此處會報錯

UnboundLocalError: local variable 'count' referenced before assignment

應該像下麵這樣使用:

In [36]: def counter(start=0):
    ...:     count = [start]
    ...:     def incr():
    ...:         count[0] += 1
    ...:         return count
    ...:     return incr
    ...: 

In [37]: count = counter(5)

In [38]: for i in range(10):
    ...:     print count(),
    ...:     
[6] [7] [8] [9] [10] [11] [12] [13] [14] [15]

2.使用閉包的陷阱

In [1]: def create():
   ...:     return [lambda x:i*x for i in range(5)]  #推導式生成一個匿名函數的列表
   ...: 

In [2]: create()
Out[2]: 
[<function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>]

In [4]: for mul in create():
   ...:     print mul(2)
   ...:     
8
8
8
8
8

結果是不是很奇怪,這算是閉包使用中的一個陷阱吧!來看看為什麼?

在上面的代碼當中,函數create返回一個list裡面保存了4個函數變數,這4個函數都共同的引用了迴圈變數i, 也就是說它們共用著同一個變數ii是會改變的,當函數調用時,迴圈變數i已經是等於4了,因此4個函數返回的都是8。如果,需要在閉包使用迴圈變數的值的話,把迴圈變數作為閉包的預設參數或者是通過偏函數來實現。實現的原理也很簡單,就是當把迴圈變數當參數傳入函數時,會申請新的記憶體。示例代碼如下:

In [5]: def create():
   ...:         return [lambda x,i=i:i*x for i in range(5)] 
   ...: 
In [7]: for mul in create():
   ...:     print mul(2)
   ...:     
0
2
4
6
8

3,閉包與裝飾器

裝飾器就是一種的閉包的應用,只不過其傳遞的是函數:

def addb(func):
    def wrapper():
        return '<b>' + func() + '</b>'
    return wrapper

def addli(func):
    def wrapper():
        return '<li>' + func() + '</li>'
    return wrapper 

@addb         # 等同於 demo = addb(addli(demo)) 
@addli        # 等同於 demo = addli(demo)
def demo():
    return 'hello world'

print demo()    # 執行的是 addb(addku(demo))

在執行時,首先將demo函數傳遞給addli進行裝飾,然後將裝飾後的函數傳遞給addb進行裝飾。所以最後返回的結果是:

<b><li>hello world</li></b>

4.裝飾器中的陷阱

當你寫了一個裝飾器作用在某個函數上,這個函數的重要的元信息比如名字、文檔字元串、註解和參數簽名都會丟失。

def out_func(func):
    def wrapper():
        func()
    return wrapper

@out_func
def demo():
    """
        this is  a demo.
    """
    print 'hello world.'

if __name__ == '__main__':
    demo()
    print "__name__:",demo.__name__
    print "__doc__:",demo.__doc__

看結果:

hello world.
__name__: wrapper
__doc__: None

函數名字和文檔字元串都變成了閉包的信息。好在可以使用 functools 庫中的 @wraps 裝飾器來註解底層包裝函數。

from functools import wraps

def out_func(func):
    @wraps(func)
    def wrapper():
        func()
    return wrapper

自己試試結果吧!

學習過程中遇到什麼問題或者想獲取學習資源的話,歡迎加入學習交流群
626062078,我們一起學Python!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文目錄: 1.1 tcpdump選項 1.2 tcpdump表達式 1.3 tcpdump示例 tcpdump採用命令行方式對介面的數據包進行篩選抓取,其豐富特性表現在靈活的表達式上。 不帶任何選項的tcpdump,預設會抓取第一個網路介面,且只有將tcpdump進程終止才會停止抓包。 例如: s ...
  • 轉載請註明出處:http://www.houxiurong.com/?post=27 Mac預設是安裝了ssh工具軟體的。 先用mac的 終端工具生成 id_rsa 和id_rsa.pub 秘鑰,生成方式如下: 1、當前用戶目錄下,檢查SSH公鑰 cd ~/.ssh 看看存不存在.ssh,如果存在的 ...
  • 學習單片機過程中遇到兩個問題: 1、寫好串口代碼,手邊沒有硬體調試; 2、串口收發出現奇怪的問題,無法定位; 經過百度,百度,再百度後,終於找到瞭解決方法,那就是:串口收發模擬。不多說,總結如下: 實驗需要的軟體:VSPD(Virtual Serial Port Driver)、串口調試助手、Kei ...
  • 1.看似針對同樣一段查詢表ef達式,重覆執行卻沒有被記錄下來。其實這是正常情況,因為ef並沒有重覆去執行 相同sql查詢。 2.MiniProfiler結合MVC過濾器進行 攔截記錄Sql,示例代碼: 3.以上的miniprofiler並不能 攔截到 sql語句查詢,需要使用 minprofiler ...
  • 一、WCF簡介 1、什麼是WCF? WCF的全稱是:Windows Communication Foundation。從本質上來說,它是一套軟體開發包,是微軟公司推出的符合SOA思想的技術框架。 2、WCF中包括哪些東西? WCF為程式員提供了豐富的功能,其中包括:托管、服務實例管理、非同步、安全、事 ...
  • <div style="float: left;border-radius:70%; height: 80px; overflow:hidden;"> <img class="mui-icon-image" src="../../img/002 劉運忠.jpg" style="height: 80p ...
  • <div id="segmented" class="mui-segmented-control"> <a class="mui-control-item mui-active" href="#item1mobile"> 考勤記錄 </a> <a class="mui-control-item" h ...
  • //論文要用到其改進演算法,在此先demo測試一下using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...