首先給出閉包函數的必要條件:
閉包函數必須返回一個函數對象
閉包函數返回的那個函數必須引用外部變量(一般不能是全局變量),而返回的那個函數內部不一定要return
幾個典型的閉包例子:
# ENV>>> Python 3.6 # NO.1 def line_conf(a, b): def line(x): return a * x + b return line # NO.2 def line_conf(): a = 1 b = 2 def line(x): print(a * x + b) return line # NO.3 def _line_(a,b): def line_c(c): def line(x): return a*(x**2)+b*x+c return line return line_c
不包括print語句的代碼是4行,閉包寫法是6行,看起來有點不對勁啊?怎么閉包實現需要的代碼量還多呢?別急,我現在有個需求:
再定義100條直線!
那么現在誰的代碼量更少呢?很明顯這個是可以簡單計算出來的,采用閉包的方式添加一條直線只需要加一行代碼,而普通做法需要添兩行代碼,定義100條直線兩種做法的代碼量差為:100+6 -(100*2+4) = -98。需要注意的是,實際環境中定義的單個函數的代碼量多達幾十上百行,這時候閉包的作用就顯現出來了,沒錯,大大提高了代碼的可復用性!
注意:閉包函數引用的外部變量不一定就是其父函數的參數,也可以是父函數作用域內的任意變量,如“熱身”中的第二段代碼:
二、如何顯式地查看“閉包”
接上面的代碼塊: L = line_conf() print(line_conf().__closure__) #(<cell at 0x05BE3530: int object at 0x1DA2D1D0>, # <cell at 0x05C4DDD0: int object at 0x1DA2D1E0>) for i in line_conf().__closure__: #打印引用的外部變量值 print(i.cell_contents) #1 ; #2 __closure__屬性返回的是一個元組對象,包含了閉包引用的外部變量。 若主函數內的閉包不引用外部變量,就不存在閉包,主函數的_closure__屬性永遠為None: def line_conf(): a = 1 b = 2 def line(x): print(x+1) #<<<------ return line L = line_conf() print(line_conf().__closure__) # None for i in line_conf().__closure__: #拋出異常 print(i.cell_contents)
三、為何叫閉包?
如你所見,line_A對象作為line_conf返回的閉包對象,它引用了line_conf下的變量b=1,在print時,全局作用域下定義了新的b變量指向20,最終結果仍然引用的line_conf內的b。這是因為,閉包作為對象被返回時,它的引用變量就已經確定(已經保存在它的__closure__屬性中),不會再被修改。
是的,閉包在被返回時,它的所有變量就已經固定,形成了一個封閉的對象,這個對象包含了其引用的所有外部、內部變量和表達式。當然,閉包的參數例外。
四、閉包可以保存運行環境
思考下面的代碼會輸出什么?
關於這個問題的深入探討(python新手理解起來可能需要點時間),我們先看下面的代碼(2019/5/19增):
_list = [] for i in range(3): def func(): return i+1 func.__doc__ = i func.__hash__ = i func.__repr__ = i func.__defaults__ = tuple([i]) #這個屬性必須是tuple類型 func.__name__ = f'{i}' func.hello = i #自定義一個屬性並賦值 # 不能再玩了 _list.append(func) for f in _list: print(f.__doc__, f.__hash__, f.__repr__, f.__defaults__, f.__name__, f.hello, f(), ) # 輸出 # 0 0 0 (0,) 0 0 3 # 1 1 1 (1,) 1 1 3 # 2 2 2 (2,) 2 2 3
代碼中我在保存函數時,修改了函數的一些屬性(前幾個叫做magic method,是函數對象默認擁有的),使它們等於循環內的變量i,hello屬性顯然是我自定義的一個屬性,也讓它等於了i。
然后,我們循環打印每個函數的這些屬性,可以發現,咦~ 這些屬性居然可以保存這個變量i :)
嗯,是的,函數的一些基本屬性在定義時就會有一個初始的確定值(不論這個值是由可變或不可變對象構成,都是一個完整拷貝,不受源變量變動影響); 閉包保存這個變量的原理是一樣的,它用的是函數的__closure__屬性,這個屬性還有一點特殊,它是只讀的,不能由人為修改。(function還有一個__code__屬性,這個對象很牛)
這部分內容是對閉包和函數對象的更深一層的探討,理解后更上一層樓;
不過當你不知道這些屬性時是做什么用時,最好不要修改它們。
五、閉包的實際應用
現在你已經逐漸領悟“閉包”了,趁熱打鐵,再來一個小例子:
看到這里,你也可以試着自己寫出一個簡單的閉包函數。
OK,現在來看一個真正在實際環境中會用到的案例:
1、【閉包實現快速給不同項目記錄日志】
import logging def log_header(logger_name): logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(name)s] %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') logger = logging.getLogger(logger_name) def _logging(something,level): if level == 'debug': logger.debug(something) elif level == 'warning': logger.warning(something) elif level == 'error': logger.error(something) else: raise Exception("I dont know what you want to do?" ) return _logging project_1_logging = log_header('project_1') project_2_logging = log_header('project_2') def project_1(): #do something project_1_logging('this is a debug info','debug') #do something project_1_logging('this is a warning info','warning') # do something project_1_logging('this is a error info','error') def project_2(): # do something project_2_logging('this is a debug info','debug') # do something project_2_logging('this is a warning info','warning') # do something project_2_logging('this is a critical info','error') project_1() project_2()
原文地址https://blog.csdn.net/sc_lilei/article/details/80464645