在Python語言中,可以在函數中定義函數。 這種在函數中嵌套定義的函數也叫內部函數。我們來看下面的代碼:
創新互聯建站堅持“要么做到,要么別承諾”的工作理念,服務領域包括:網站設計制作、成都做網站、企業官網、英文網站、手機端網站、網站推廣等服務,滿足客戶于互聯網時代的廣州網站設計、移動媒體設計的需求,幫助企業找到有效的互聯網解決方案。努力成為您成熟可靠的網絡建設合作伙伴!
上述代碼中,定義了函數greet,在函數greet內部又定義了一個函數inner_func, 并調用該函數打印了一串字符。
我們可以看到,內部函數inner_func的定義和使用與普通函數基本相同。需要注意的是變量的作用域,在上述代碼中,函數參數name對于全局函數greet是局部變量,對內部函數inner_func來說則是非局部變量。內部函數對于非局部變量的訪問規則類似于標準的外部函數訪問全局變量。
從這個例子我們還可以看到內部函數的一個作用,就是通過定義內部函數的方式將一些功能隱藏起來,防止外部直接調用。常見的場景是,在一個復雜邏輯的函數中,將一些小的任務定義成內部函數,然后由這個外層函數使用,這樣可以使代碼更為清晰,易于維護。這些內部函數只會在這個外層函數中使用,不能被其他函數或模塊使用。
在Python語言中, 函數也是對象,它可以被創建、賦值給變量,或者作為函數的返回值。我們來看下面這個例子。
在上述代碼中,在函數gen_greet內部定義了inner_func函數,并返回了一個inner_func函數對象。外部函數gen_greet返回了一個函數對象,所以像gen_greet這樣的函數也叫工廠函數。
在內部函數inner_func中,使用了外部函數的傳參greet_words(非局部變量),以及函數的參數name(局部變量),來打印一個字符串。
接下來,調用gen_greet("Hello")創建一個函數對象say_hello,緊接著調用say_hello("Mr. Zhang"),輸出的結果為:Hello, Mr. Zhang!
同樣的,調用gen_greet("Hi")創建一個函數對象say_hi,調用say_hello("Mr. Zhang"),輸出的結果為:Hi,Tony!
我們可以發現,gen_greet返回的函數對象具有記憶功能,它能夠把所需使用的非局部變量保存下來,用于后續被調用的時候使用。這種保存了非局部變量的函數對象被稱作閉包(closure)。
那么閉包是如何實現的呢?其實并不復雜,函數對象中有一個屬性__closure__,它就是在創建函數對象時用來保存這些非局部變量的。
__closure__屬性是一個元組或者None類型。在上述代碼中,我們可以通過下面方式查看:
函數的嵌套所實現的功能大都可以通過定義類的方式來實現,而且類是更加面向對象的代碼編寫方式。
嵌套函數的一個主要用途是實現函數的裝飾器。我們看下面的代碼:
在上述代碼中,logger函數返回函數with_logging,with_logging則是打印了函數func的名稱及傳入的參數,然后調用func, 并將參數傳遞給func。其中的@wraps(func)語句用于復制函數func的名稱、注釋文檔、參數列表等等,使得with_logging函數具有被裝飾的函數func相同的屬性。
代碼中接下來用@logger對函數power_func進行修飾,它的作用等同于下面的代碼:
可見,裝飾器@符其實就是上述代碼的精簡寫法。
通過了解了嵌套函數和閉包的工作原理,我們在使用過程中就能夠更加得心應手了。
結果:
結果:
1,匿名函數能夠完成基本的簡單功能,當作為實參時,傳遞的是函數的引用,只有功能
2,普通函數能夠完成較為復雜的功能,當作為實參時,傳遞的是函數的引用,只有功能
3,閉包能夠完成較為復雜的功能,當作為實參時,傳遞的是這個閉包中的函數以及數據,所以是功能+數據
4,實例對象能夠完成較為復雜的功能,當作為實參時,傳遞的是這個很多功能以及很多數據,所以是功能+數據
python中的閉包從表現形式上定義為:如果在一個內部函數里,對在外部作用域(但不是在全局作用域)的變量進行引用,那么內部函數就被認為是閉包(closure),也就是說內層函數引用了外層函數的變量然后返回內層函數的情況就稱之為閉包。
閉包的特點是返回的函數還引用了外層函數的局部變量。所以,要正確使用閉包,就要確保引用的局部變量在函數返回后不能變。
Python中的閉包不是一個簡單一說就能明白的概念,這個是需要在深入學習當中理解的。在一些語言中,函數中可以嵌套定義另外一個,如果內部函數引用了外部函數的變量,就會產生閉包。閉包是可以用在一個函數與一組私有的變量之間創建關聯關系。在給定函數中被多次調用的,這些私有變量能夠保持持久性;
例子:
def make_printer(msg):
def printer():
print msg # 夾帶私貨(外部變量)
return printer # 返回的是函數,帶私貨的函數
說人話就是當某個函數被當成對象返回的時候,夾帶了外部變量,就形成了一個閉包。還是不懂的話,去黑馬程序員論壇,有視頻也有文字教學。找不到對話框直接問就好了
1. 閉包的概念
首先還得從基本概念說起,什么是閉包呢?來看下維基上的解釋:
復制代碼代碼如下:
在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。
....
上面提到了兩個關鍵的地方: 自由變量 和 函數, 這兩個關鍵稍后再說。還是得在贅述下“閉包”的意思,望文知意,可以形象的把它理解為一個封閉的包裹,這個包裹就是一個函數,當然還有函數內部對應的邏輯,包裹里面的東西就是自由變量,自由變量可以在隨著包裹到處游蕩。當然還得有個前提,這個包裹是被創建出來的。
在通過Python的語言介紹一下,一個閉包就是你調用了一個函數A,這個函數A返回了一個函數B給你。這個返回的函數B就叫做閉包。你在調用函數A的時候傳遞的參數就是自由變量。
舉個例子:
復制代碼代碼如下:
def func(name):
def inner_func(age):
print 'name:', name, 'age:', age
return inner_func
bb = func('the5fire')
bb(26) # name: the5fire age: 26
這里面調用func的時候就產生了一個閉包——inner_func,并且該閉包持有自由變量——name,因此這也意味著,當函數func的生命周期結束之后,name這個變量依然存在,因為它被閉包引用了,所以不會被回收。
另外再說一點,閉包并不是Python中特有的概念,所有把函數做為一等公民的語言均有閉包的概念。不過像Java這樣以class為一等公民的語言中也可以使用閉包,只是它得用類或接口來實現。
更多概念上的東西可以參考最后的參考鏈接。
2. 為什么使用閉包
基于上面的介紹,不知道讀者有沒有感覺這個東西和類有點相似,相似點在于他們都提供了對數據的封裝。不同的是閉包本身就是個方法。和類一樣,我們在編程時經常會把通用的東西抽象成類,(當然,還有對現實世界——業務的建模),以復用通用的功能。閉包也是一樣,當我們需要函數粒度的抽象時,閉包就是一個很好的選擇。
在這點上閉包可以被理解為一個只讀的對象,你可以給他傳遞一個屬性,但它只能提供給你一個執行的接口。因此在程序中我們經常需要這樣的一個函數對象——閉包,來幫我們完成一個通用的功能,比如后面會提到的——裝飾器。
3. 使用閉包
第一種場景 ,在python中很重要也很常見的一個使用場景就是裝飾器,Python為裝飾器提供了一個很友好的“語法糖”——@,讓我們可以很方便的使用裝飾器,裝飾的原理不做過多闡述,簡言之你在一個函數func上加上@decorator_func, 就相當于decorator_func(func):
復制代碼代碼如下:
def decorator_func(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@decorator_func
def func(name):
print 'my name is', name
# 等價于
decorator_func(func)
在裝飾器的這個例子中,閉包(wrapper)持有了外部的func這個參數,并且能夠接受外部傳過來的參數,接受過來的參數在原封不動的傳給func,并返回執行結果。
這是個簡單的例子,稍微復雜點可以有多個閉包,比如經常使用的那個LRUCache的裝飾器,裝飾器上可以接受參數@lru_cache(expire=500)這樣。實現起來就是兩個閉包的嵌套:
復制代碼代碼如下:
def lru_cache(expire=5):
# 默認5s超時
def func_wrapper(func):
def inner(*args, **kwargs):
# cache 處理 bala bala bala
return func(*args, **kwargs)
return inner
return func_wrapper
@lru_cache(expire=10*60)
def get(request, pk)
# 省略具體代碼
return response()
不太懂閉包的同學一定得能夠理解上述代碼,這是我們之前面試經常會問到的面試題。
第二個場景 ,就是基于閉包的一個特性——“惰性求值”。這個應用比較常見的是在數據庫訪問的時候,比如說:
復制代碼代碼如下:
# 偽代碼示意
class QuerySet(object):
def __init__(self, sql):
self.sql = sql
self.db = Mysql.connect().corsor() # 偽代碼
def __call__(self):
return db.execute(self.sql)
def query(sql):
return QuerySet(sql)
result = query("select name from user_app")
if time now:
print result # 這時才執行數據庫訪問
上面這個不太恰當的例子展示了通過閉包完成惰性求值的功能,但是上面query返回的結果并不是函數,而是具有函數功能的類。有興趣的可以去看看Django的queryset的實現,原理類似。
第三種場景 , 需要對某個函數的參數提前賦值的情況,當然在Python中已經有了很好的解決訪問 functools.parial,但是用閉包也能實現。
復制代碼代碼如下:
def partial(**outer_kwargs):
def wrapper(func):
def inner(*args, **kwargs):
for k, v in outer_kwargs.items():
kwargs[k] = v
return func(*args, **kwargs)
return inner
return wrapper
@partial(age=15)
def say(name=None, age=None):
print name, age
say(name="the5fire")
# 當然用functools比這個簡單多了
# 只需要: functools.partial(say, age=15)(name='the5fire')
看起來這又是一個牽強的例子,不過也算是實踐了閉包的應用。
網站名稱:python閉包函數作用 python閉包原理
鏈接地址:http://m.kartarina.com/article2/dodeioc.html
成都網站建設公司_創新互聯,為您提供關鍵詞優化、網站導航、響應式網站、Google、搜索引擎優化、全網營銷推廣
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯