當前位置:首頁 » 編程語言 » python閉包函數

python閉包函數

發布時間: 2023-04-04 12:03:59

『壹』 求幫助,python閉包和返回函數問題

(1)unpack tuple和list, 可以讓函數返回多個值

def count():
return (1, 2, 3) # 或者 return [1, 2, 3]

# 把列表解包,把1 2 3 分別賦值給 a b c
a, b, c = count()
print a, b, c
# 輸出 1, 2, 3

(2)假設你知道Python的dict類型。Python中,在函數中定義一個變數的時候,會在一個隱藏的叫locals的dict裡面插入key-value,其中key是變數名,value是變數值。而引用一個變數的時候,則首先會在這個叫locals的dict裡面,根據變數名作為key,去查對應的值。

var = 1 # 你可以認為這里進行了 locals['var'] = 1 的操作
print var # 在對var變數進行求值的時候,就在locals['var']裡面找var變數對應的值

(3)for循環中,每次循環只是給 `i` 重新綁定值

for i in (1, 2, 3):
print i

print i
# 一次輸入 1 2 3 3

每次`for i in (1, 2, 3)`相當於在`print i`之前,進行了
`locals['i'] = 1`
`locals['i'] = 2`
`locals['i'] = 3`
的操作
所以最後的`print i`再去locals字典裡面找`i`的時候,就變成 3 了。
(4)閉包是 一個函數加上這個函數引用的外部變數

var = 1
def f():
print var
# 這里的閉包是函數 f 和 f 引用的外部變數 var

def count():
var2 = 2
def f():
print var2
# 這里的閉包是函數 f 和 f 引用的外部變數 var2
return f

拿第一個函數 f 來說。在 f 運行的時候,解釋器拿著'var'這個字元串去locals字典裡面找,發現找不到,於是在closure字典裡面找,最後closure字典裡面找,你可以認為就是找closure['var'],然後發現找到對應的值。count裡面的 f 函數同理。
(為了容易理解,我這里說謊了。實際上 f 壓根沒有closure,count裡面的 f 才有。其實closure壓根不是像locals那樣的字典)
(5)函數定義時,函數只是記錄變數的名字。
要區分什麼是名字,什麼是值。
`i = 1`這里 i 只是名字,只是一個字元串 'i' 。這句話運行完,locals['i'] = 1,就說 i 對應的值是1

def count():
fs = []
for i in range(1, 4):
# 定義一個函數,等價於運行了 locals['f'] = 真正生成的函數
# 每次循環,這里都會重新生成一個函數,然後把重新生成的函數賦值給 locals['f']
def f():
return i * i # 引用了'i'這個名字,但並不是引用了'i'對應的值

# 等價於 locals['fs'].append(locals['f'])
# f 不是函數,它只是一個名字'f'。f 引用的東西,也就是locals['f']才是真正的函數
fs.append(f)
# 於是這個for循環生成了三個函數,這三個函數是沒有名字的,這個函數運行完後,它們跟'f'這個名字就毛關系都沒有了(是的我說慌了,但可以先不管)
# 把整個列表返回,這個列表包含了三個函數
return fs

# count()返回三個函數的列表,unpack 列表的語法把列表中的三個函數抽出來,重新給他們命名為 f1, f2, f3
# 也就是說,
# locals['f1'] = 列表中的第1個函數
# locals['f2'] = 列表中的第2個函數
# locals['f3'] = 列表中的第3個函數
# 這三個函數跟'f'這個名字現在毛關系都沒有。(其實是有的,但為了說明需要簡化,現在你可以完全不管括弧裡面說的話)
f1, f2, f3 = count()
print f1(), f2(), f3()
# 好了我們運行它們,輸入都是 9

# def f():
# return i * i

這是因為 f1 現在對應的函數,裡面引用了 'i' 這個字元串,我們根據 'i '這個字元串去找它對應的值,先找到 f 當前的locals字典,發現沒有,因為函數定義的時候沒有定義 i 變數。然後再去closure['i']裡面找,因為Python是通過closure字典實現閉包的(就當它是對的好不好),所以我們可以在closure['i']找到值,這個值就是我們上一次運行的時候count函數裡面殘留的locals['i'],而由於for循環三遍之後,locals['i'] == 3,所以找到 i 的值就是3。所以最後輸出都是9

『貳』 「低門檻 手把手」python 裝飾器(Decorators)原理說明

本文目的是由淺入深地介紹python裝飾器原理

裝飾器(Decorators)是 Python 的一個重要部分

其功能是, 在不修改原函數(類)定義代碼的情況下,增加新的功能

為了理解和實現裝飾器,我們先引入2個核心操作:

在這個例子中,函數hi的形參name,默認為'world'

在函數內部,又定義了另一個函數 howdoyoudo,定義這個函數時,將形參name作為新函數的形參name2的默認值。

因此,在函數內部調用howdoyoudo()時,將以調用hi時的實參為默認值,但也可以給howdoyoudo輸入其他參數。

上面的例子運行後輸出結果為:

這里新定義的howdoyoudo可以稱作一個「閉包」。不少關於裝飾器的blog都提到了這個概念,但其實沒必要給它取一個多專業的名字。我們知道閉包是 函數內的函數 就可以了

當我們進行 def 的時候,我們在做什麼?

這時,hi函數,列印一個字元串,同時返回一個字元串。

但hi函數本身也是一個對象,一個可以執行的對象。執行的方式是hi()。

這里hi和hi()有本質區別,

hi 代表了這個函數對象本身

hi() 則是運行了函數,得到函數的返回值。

作為對比,可以想像以下代碼

此時也是b存在,可以正常使用。

我們定義2個函數,分別實現自加1, 自乘2,

再定義一個函數double_exec,內容是將某個函數調用2次

在調用double_exec時,可以將函數作為輸入傳進來

輸出結果就是

7

27

同樣,也可以將函數作為輸出

輸出結果為

6

10

有了以上兩個核心操作,我們可以嘗試構造裝飾器了。

裝飾器的目的: 在不修改原函數(類)定義代碼的情況下,增加新的功能

試想一下,現在有一個原函數

在不修改原函數定義代碼的情況下,如果想進行函數內容的添加,可以將這個函數作為一個整體,添加到這樣的包裹中:

我們定義了一個my_decorator函數,這個函數進行了一種操作:

對傳入的f,添加操作(運行前後增加列印),並把添加操作後的內容連同運行原函數的內容,一起傳出

這個my_decorator,定義了一種增加前後列印內容的行為

調用my_decorator時,對這個行為進行了操作。

因此,new_function是一個在original_function上增加了前後列印行為的新函數

這個過程被可以被稱作裝飾。

這里已經可以發現,裝飾器本身對於被裝飾的函數是什麼,是不需要考慮的。裝飾器本身只定義了一種裝飾行為,這個行為是通過裝飾器內部的閉包函數()進行定義的。

運行裝飾前後的函數,可以清晰看到裝飾的效果

我們復現一下實際要用裝飾器的情況,我們往往有一種裝飾器,想應用於很多個函數,比如

此時,如果我們想給3個print函數都加上裝飾器,需要這么做

實際調用的時候,就需要調用添加裝飾器的函數名了

當然,也可以賦值給原函數名

這樣至少不需要管理一系列裝飾前後的函數。

同時,在不需要進行裝飾的時候,需要把

全部刪掉。

事實上,這樣並不方便,尤其對於更復雜的裝飾器來說

為此,python提供了一種簡寫方式

這個定義print1函數前的@my_decorator,相當於在定義完print1後,自動直接運行了

不論採用@my_decorator放在新函數前,還是顯示地重寫print1 = my_decorator(print1),都會存在一個問題:

裝飾後的函數,名字改變了(其實不止名字,一系列的索引都改變了)

輸出結果為:

這個現象的原因是,裝飾行為本身,是通過構造了一個新的函數(例子中是wrap_func函數)來實現裝飾這個行為的,然後把這個修改後的函數賦給了原函數名。

這樣,會導致我們預期的被裝飾函數的一些系統變數(比如__name__)發生了變化。

對此,python提供了解決方案:

經過這個行為後,被裝飾函數的系統變數問題被解決了

輸出結果為

剛才的例子都比較簡單,被裝飾的函數是沒有參數的。如果被裝飾的函數有參數,只需要在定義裝飾行為時(事實上,這個才更通用),增加(*args, **kwargs)描述即可

之前的描述中可以感受到,對於例子中的裝飾行為(前後加列印),函數被裝飾後,本質上是調用了新的裝飾函數wrap_func。

因此,如果原函數需要有輸入參數傳遞,只需要在wrap_func(或其他任意名字的裝飾函數)定義時,也增加參數輸入(*args, **kwargs),並將這些參數,原封不動地傳給待裝飾函數f。

這種定義裝飾行為的方式更具有普遍性,忘記之前的定義方式吧

我們試一下

輸出

這里需要注意的是,如果按照以下的方式定義裝飾器

那麼以下語句將不會執行

因為裝飾後實際的函數wrap_func(雖然名字被改成了原函數,系統參數也改成了原函數),運行到return f(*args, **kwargs) 的時候已經結束了

因為裝飾器my_decorator本身也是可以輸入的,因此,只需要在定義裝飾器時,增加參數,並在後續函數中使用就可以了,比如

此時裝飾器已經可以有輸入參數了

輸出

你可能發現,為什麼不用簡寫版的方法了

因為以上代碼會報錯!!

究其原因,雖然

等價於

但是,

並不等價於

這本身和@語法有關,使用@my_decorator時,是系統在應用一個以單個函數作為參數的閉包函數。即,@是不能帶參數的。

但是你應該發現了,之前的@wraps(f)不是帶參數了嗎?請仔細觀察以下代碼

通過一層嵌套,my_decorator_with_parma本質上是返回了一個參數僅為一個函數的函數(my_decorator),但因為my_decorator對my_decorator_with_parma來說是一個閉包,my_decorator_with_parma是可以帶參數的。(這句話真繞)

通過以上的定義,我們再來看

可以這么理解,my_decorator_with_parma(msg='yusheng')的結果是原來的my_decorator函數,同時,因為my_decorator_with_parma可以傳參,參數實際上是參與了my_decorator的(因為my_decorator對my_decorator_with_parma是閉包), my_decorator_with_parma(msg='yusheng') 全等於 一個有參數參加的my_decorator

因此,以上代碼等價於有參數msg傳遞的

比較繞,需要理解一下,或者乾脆強記這種範式:

以上範式包含函數的輸入輸出、裝飾器的輸入,可以應對大部分情況了。

實驗一下:

輸出

以上是一個log裝飾器,利用datetime統計了函數的耗時,

並且,裝飾器可以進行輸出文件操作,如果給出了文件路徑,則輸出文件,否則就列印。

利用這個裝飾器,可以靈活地進行耗時統計

不設置輸出文件地址,則列印。運行結果為:

也可以輸出到文件

輸出結果為

同時在當前目錄生成了一個test.log 文件,內容為:

以上的裝飾器都是以函數形式出現的,但我們可以稍做改寫,將裝飾器以類的形式實現。

這個裝飾器類Log 上個例子里的裝飾器函數log功能是一樣的,同時,這個裝飾器類還可以作為基類被其他繼承,進一步增加功能。

原文 http://www.cnblogs.com/yushengchn/p/15636944.html

『叄』 Python編程常用技巧

鏈接:https://pan..com/s/1Nb7euy7yqFQqALbBgKzlVw

提取碼:dfsm

Python 編程高手之路。本課程分五個階段,詳細的為您打造高手之路,本課程適合有一定python基礎的同學。

用Python可以做什麼?可以做日常任務,比如自動備份你的MP3;可以做網站,很多著名的網站就是Python寫的。總之就是能幹很多很多事。

課程目錄:

第一階段

第一章:用戶交互

第二章:流程式控制制

第三章:數據類型

第四章:字元編碼

第五章:文件處理

第二階段

第六章:函數概述

第七章:閉包函數

......

熱點內容
配音秀緩存在手機哪裡 發布:2024-11-02 14:23:27 瀏覽:294
linux下載gcc 發布:2024-11-02 14:13:47 瀏覽:344
寫演算法交易 發布:2024-11-02 13:57:09 瀏覽:208
安卓怎麼下載鴻蒙 發布:2024-11-02 13:36:13 瀏覽:663
加密狗rsa 發布:2024-11-02 13:20:44 瀏覽:560
實用java教程 發布:2024-11-02 13:07:39 瀏覽:930
ide文件夾 發布:2024-11-02 12:51:37 瀏覽:559
python中字典的用法 發布:2024-11-02 12:40:42 瀏覽:28
安卓怎麼下載zine 發布:2024-11-02 12:40:38 瀏覽:793
深入理解java虛擬 發布:2024-11-02 12:40:36 瀏覽:557