第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

Python 的閉包簡介

閉包是較難理解的概念,Python 初學者可以暫時跳過此節(jié)。學習此節(jié)時需要理解 “函數(shù)是第一類對象” 的概念,在詞條 “Python 的 lambda 表達式” 中詳細介紹了這一概念

本節(jié)首先講解理解閉包所需要的鋪墊知識,最后再引入閉包的定義。

1. 嵌套定義函數(shù)

1.1 在函數(shù)內部定義函數(shù)

Python 允許嵌套定義函數(shù),可以在函數(shù)中定義函數(shù),例如:

def outter():
    def inner():
        print('Inside inner')

    print('Inside outter')
    inner()

outter()    
  • 在第 1 行,定義函數(shù) outter
  • 在第 2 行,在函數(shù) outter 內部,定義函數(shù) inner
  • 在第 6 行,在函數(shù) outter 內部,調用函數(shù) inner

函數(shù) inner 定義在函數(shù) outter 中,被稱為函數(shù)嵌套定義。運行程序,輸出結果如下:

Inside outter
Inside inner

1.2 實現(xiàn)信息隱藏

定義在函數(shù)內部的函數(shù),對外是不可見的,例如:

def outter():
    def inner():
        print('inside inner')

    print('inside outter')        
    inner()

inner()    
  • 在第 1 行,定義了外部函數(shù) outter
  • 在第 2 行,定義了內部函數(shù) inner
  • 在第 6 行,在函數(shù) outter 中,調用函數(shù) inner
  • 在第 8 行,調用函數(shù) inner

程序運行,輸出如下:

Traceback (most recent call last):
  File "visible.py", line 8, in <module>
    inner()
NameError: name 'inner' is not defined

在第 4 行,試圖調用定義在函數(shù) outter 內部定義的函數(shù) inner,程序運行時報錯:name ‘inner’ is not defined,即找不到函數(shù) inner。

因為函數(shù) inner 是定義在函數(shù) outter 內部的,函數(shù) inner 對外部是不可見的,因此函數(shù) outter 向外界隱藏了實現(xiàn)細節(jié) inner,被稱為信息隱藏。

1.3 實現(xiàn)信息隱藏的例子

實現(xiàn)一個復雜功能的函數(shù)時,在函數(shù)內部定義大量的輔助函數(shù),這些輔助函數(shù)對外不可見。例如,假設要實現(xiàn)一個函數(shù) complex,函數(shù)的功能非常復雜,將函數(shù) complex 的功能分解為 3 個子功能,使用三個輔助函數(shù) f1、f2、f3 完成對應的子功能,代碼如下:

def f1():
    print('Inside f1')

def f2():
    print('Inside f2')

def f3():
    print('Inside f3')

def complex():
    print('Inside complex')
    f1()
    f2()
    f3()
  • 在第 1 行,定義了輔助函數(shù) f1
  • 在第 4 行,定義了輔助函數(shù) f2
  • 在第 7 行,定義了輔助函數(shù) f3
  • 在第 10 行,定義了主函數(shù) complex,它通過調用 f1、f2、f3 實現(xiàn)自己的功能

在以上的實現(xiàn)中,函數(shù) f1、f2、f3 是用于實現(xiàn) complex 的輔助函數(shù),我們希望它們僅僅能夠被 complex 調用,而不會被其它函數(shù)調用。如果可以將函數(shù) f1、f2、f3 定義在函數(shù) complex 的內部,如下所示:

def complex():
    def f1():
        print('Inside f1')
    def f2():
        print('Inside f2')
    def f3():
        print('Inside f3')

    print('Inside complex')
    f1()
    f2()
    f3()
  • 在第 2 行,在函數(shù) complex 內部定義函數(shù) f1
  • 在第 4 行,在函數(shù) complex 內部定義函數(shù) f2
  • 在第 6 行,在函數(shù) complex 內部定義函數(shù) f3
  • 在第 10 行到第 12 行,調用 f1、f2、f3 實現(xiàn)函數(shù) complex 的功能

2. 內部函數(shù)訪問外部函數(shù)的局部變量

嵌套定義函數(shù)時,內部函數(shù)可能需要訪問外部函數(shù)的變量,例子代碼如下:

def outter():
    local = 123

    def inner(local):
        print('Inside inner, local = %d'% local)

    inner(local)

outter()    
  • 在第 1 行,定義了外部函數(shù) outter
  • 在第 2 行,定義了函數(shù) outter 的局部變量 local
  • 在第 4 行,定義了內部函數(shù) inner
    • 函數(shù) inner 需要訪問函數(shù) outter 的局部變量 local
  • 在第 7 行,將函數(shù) outter 的局部變量 local 作為參數(shù)傳遞給函數(shù) inner
    • 在第 5 行,函數(shù) inner 就可以訪問函數(shù) outter 的局部變量 local

程序運行結果如下:

Inside inner, local = 123

在上面的例子中,將外部函數(shù) outter 的局部變量 local 作為參數(shù)傳遞給內部函數(shù) inner。Python 允許內部函數(shù) inner 不通過參數(shù)傳遞直接訪問外部函數(shù) outter 的局部變量,簡化了參數(shù)傳遞,代碼如下:

def outter():
    local = 123

    def inner():
        print('Inside inner, local = %d'% local)

    inner()
  • 在第 1 行,定義了外部函數(shù) outter
  • 在第 2 行,定義了函數(shù) outter 的局部變量 local
  • 在第 4 行,定義了內部函數(shù) inner
    • 函數(shù) inner 需要訪問函數(shù) outter 的局部變量 local
  • 在第 5 行,函數(shù) inner 可以直接訪問函數(shù) outter 的局部變量 local
  • 在第 7 行,不用傳遞參數(shù),直接調用函數(shù) inner()

3. 局部變量的生命周期

通常情況下,函數(shù)執(zhí)行完后,函數(shù)內部的局部變量就不存在了。在嵌套定義函數(shù)的情況下,如果內部函數(shù)訪問了外部函數(shù)的局部變量,外部函數(shù)執(zhí)行完畢后,內部函數(shù)仍然可以訪問外部函數(shù)的局部變量。示例代碼如下:

def outter():
    local = 123

    def inner():
        print('Inside inner, local = %d' % local)

    return inner

closure = outter()
closure()
  • 在第 1 行,定義了外部函數(shù) outter
  • 在第 2 行,定義了函數(shù) outter 的局部變量 local
  • 在第 4 行,定義了內部函數(shù) inner
    • 函數(shù) inner 需要訪問函數(shù) outter 的局部變量 local
  • 在第 7 行,將函數(shù) inner 作為值返回
  • 在第 9 行,調用函數(shù) outter(),將返回值保存到變量 closure 中
  • 在第 10 行,調用函數(shù) closure()

運行程序,輸出結果如下:

Inside inner, local = 123

注意:在第 10 行,調用函數(shù) closure() 時,外部函數(shù) outter 已經(jīng)執(zhí)行完,外部函數(shù) outter 將內部函數(shù) inner 返回并保存到變量 closure。調用函數(shù) closure() 相當于調用內部函數(shù) inner(),因此,在外部函數(shù) outter 已經(jīng)執(zhí)行完的情況下,內部函數(shù) inner 仍然可以訪問外部函數(shù)的局部變量 local

4. 閉包的概念

閉包的英文是 closure,維基百科中閉包的嚴謹定義如下:

在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function closures),是引用了自由變量的函數(shù)。這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外?!?維基百科

在本節(jié),以上一節(jié)具體的例子說明和理解閉包的概念,上一節(jié)的例子程序如下:

def outter():
    local = 123

    def inner():
        print('Inside inner, local = %d' % local)

    return inner

closure = outter()
closure()
  • 在第 2 行,局部變量 local 就是自由變量
  • 在第 5 行,內部函數(shù) inner 引用了局部變量 local (即自由變量)

因此,對照閉包的定義,外部函數(shù)定義了局部變量 local,引用了局部變量 local 的內部函數(shù) inner 就是閉包。閉包的獨特之處在于:外部函數(shù) outter 創(chuàng)造了局部變量 local, 即使外部函數(shù) outter 已經(jīng)執(zhí)行完,內部函數(shù) inner 仍然可以繼續(xù)訪問它引用的局部變量 local。

5. 閉包的應用

5.1 概述

閉包經(jīng)常用于 GUI 編程的事件響應處理函數(shù)。編程語言 Javascript 被用于瀏覽器的用戶界面交互,使用 Javascript 編寫事件響應處理函數(shù)時,閉包也是經(jīng)常提及的知識點。

本小節(jié)通過編寫一個簡單的 Python GUI 程序,了解為什么需要使用閉包的語法特性,才方便實現(xiàn)功能需求。

5.2 Tk 簡介

Tkinter 是 Python 的標準 GUI 庫,Python 使用 Tkinter 可以快速的創(chuàng)建 GUI 應用程序。由于 Tkinter 是內置到 python 的安裝包中,只要安裝好 Python 之后就能使用 Tkinter 庫。

由于 Tkinter 簡單易學并且不需要安裝,因此選擇使用 Tk 編寫應用閉包的例子程序。

5.3 例子 1:顯示一個窗口

下面使用 Tk 編寫一個顯示窗口的程序,代碼如下:

import tkinter

root = tkinter.Tk()
root.mainloop()
  • 在第 1 行,引入 Tk 庫,Tk 庫的名稱是 tkinter
  • 在第 3 行,tkinter.Tk 方法會創(chuàng)建一個窗口 root
  • 在第 4 行,root.mainloop 方法等待用戶的操作

運行程序,顯示輸出如下:
圖片描述

5.4 例子 2:顯示一個 button

下面使用 Tk 編寫一個顯示 button 的程序,代碼如下:

import tkinter

root = tkinter.Tk()
button = tkinter.Button(root, text = 'Button')
button.pack()
root.mainloop()
  • 在第 4 行,tkinter.Button 方法創(chuàng)建一個新的 Button,它有兩個參數(shù):第一個參數(shù) root,指定在 root 窗口中創(chuàng)建 Button;第二個參數(shù) text,指定新創(chuàng)建 Button 的標簽
  • 在第 5 行,button.pack 方法將 button 放置在 root 窗口中

運行程序,顯示輸出如下:
圖片描述

5.5 例子 3:為 button 增加一個事件處理函數(shù)

當 button 被點擊時,希望程序得到通知,需要為 button 增加一個事件處理函數(shù),代碼如下:

import tkinter

def on_button_click():
    print('Button is clicked')

root = tkinter.Tk()
button = tkinter.Button(root, text = 'Button', command = on_button_click)
button.pack()
root.mainloop()
  • 在第 3 行,定義了函數(shù) on_button_click,當用戶點擊 button 時,程序得到通知,執(zhí)行 on_btton_click
  • 在第 4 行,函數(shù) on_button_click 在控制臺打印輸出 ‘Button is clicked’
  • 在第 7 行,tkinter.Button 創(chuàng)建一個 Button,設置 3 個參數(shù)
    • 參數(shù) root,表示在 root 窗口中創(chuàng)建 button
    • 參數(shù) text,表示 button 的標簽
    • 參數(shù) command,表示當 button 被點擊時,對應的事件處理函數(shù)
  • 在第 9 行,root.mainloop 等待用戶的操作,當用戶點擊 button 時,程序會執(zhí)行 button 對應的事件處理函數(shù),即執(zhí)行 on_button_click

運行程序,顯示輸出如下:

圖片描述
當用戶點擊 button 時,執(zhí)行 on_button_click,在控制臺中打印 ‘Button is clicked’,顯示輸出如下:

圖片描述

5.6 如何實現(xiàn)計算器

由于篇幅,本節(jié)沒有實現(xiàn)一個完整的計算器,在這里僅僅討論實現(xiàn)計算器程序的關鍵要點。windows 自帶的計算器的界面如下所示:

計算器向用戶展示各種按鈕,包括:
  • 數(shù)字按鍵,0、1、2、3、4、5、6、7、9
  • 運算符按鍵,+、-、*、\、=

用戶在點擊某個按鍵時,程序得到通知:按鍵被點擊了,但是這樣的信息還不夠,為了實現(xiàn)運算邏輯,還需要知道具體是哪一個按鍵被點擊了

為了區(qū)分是哪一個按鍵被點擊了,可以為不同的按鍵設定不同的按鍵處理函數(shù),如下所示:

import tkinter

def on_button0_click():
    print('Button 0 is clicked')

def on_button1_click():
    print('Button 1 is clicked')

def on_button2_click():
    print('Button 2 is clicked')

root = tkinter.Tk()

button0 = tkinter.Button(root, text = 'Button 0', command = on_button0_click)
button0.pack()

button1 = tkinter.Button(root, text = 'Button 1', command = on_button0_click)
button1.pack()

button2 = tkinter.Button(root, text = 'Button 2', command = on_button0_click)
button2.pack()

root.mainloop()

為了節(jié)省篇幅,這里僅僅處理了 3 個按鍵。顯然,這樣的方式是很不合理的,在一個完整的計算器程序中,存在 20 多個按鍵,如果對每個按鍵都編寫一個事件處理函數(shù),就需要編寫 20 多個事件處理函數(shù)。在下面的小節(jié)中,通過使用閉包解決這個問題。

5.7 例子 4:使用閉包為多個 button 增加事件處理函數(shù)

在上面的小節(jié)中,面臨的問題是:需要為每個 button 編寫一個事件處理函數(shù)。本小節(jié)編寫一個事件處理函數(shù)響應所有的按鍵點擊事件,代碼如下:

import tkinter

def build_button(root, i):
    def on_button_click():
        print('Button %d is clicked' % i)

    title = 'Button ' + str(i)
    button = tkinter.Button(root, text = title, command = on_button_click)
    button.pack()

root = tkinter.Tk()
for i in range(3):
    build_button(root, i)
root.mainloop()
  • 在第 11 行,tkinter.Tk 創(chuàng)建窗口 root
  • 在第 12 行,使用 for 循環(huán)調用 build_button 創(chuàng)建 3 個 button
  • 在第 14 行,root.mainloop 等待用戶操作
  • 在第 3 行,定義函數(shù) build_button 創(chuàng)建 1 個 button
    • 參數(shù) root,表示在 root 窗口中創(chuàng)建 button
    • 參數(shù) i,表示 button 的序號
  • 在第 4 行,定義事件處理函數(shù) on_button_click
    • build_button 是外部函數(shù)
    • on_button_click 是內部函數(shù)
    • 在第 5 行,打印外部函數(shù) build_button 的參數(shù) i,因此 on_button_click 是一個閉包函數(shù)
  • 在第 7 行,根據(jù) button 的序號 i 設置 button 的標簽
  • 在第 8 行,創(chuàng)建一個 button,設置標簽和事件處理函數(shù)

運行程序,顯示輸出如下:

圖片描述
當用戶點擊不同的 button 時,都是執(zhí)行 on_button_click,但在控制臺中打印的字符串是不一樣的,顯示輸出如下:
圖片描述
在這個例子中,外部函數(shù) build_button 提供了參數(shù) i 用于區(qū)分 button,內部函數(shù) on_button_click 可以訪問外部函數(shù)的參數(shù)。因此,當 button 被點擊時,通過參數(shù) i 知道是哪一個 button 被點擊了,編寫 1 個事件處理函數(shù)就可以處理多個 button 的點擊事件,即使用閉包就很自然的解決了實現(xiàn)計算器程序需要面臨的問題。

6. 小結

從概念上來看這一個小節(jié)還是比較晦澀的,我在文章的開頭也說過了初學者可以先跳過這一小節(jié),等后面在轉過頭來學習。閉包這個概念非常的重要,面試中有很多面試官喜歡問閉包相關的問題,大家一定要多看幾遍,徹底掌握閉包。