1. 程式人生 > >函數變量閉包

函數變量閉包

函數。變量。閉包

# -*- coding:utf-8 -*- # --函數-- # --遞歸函數--- # 在函數的內部,可以調用其他函數,如果一個函數在內部調用自身本身,這個函數就是遞歸函數 # 舉例,計算階乘,用fact(n)表示,可以看出 # fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n # 所以fact(n)可以表示為n*fact(n-1),只有n=1時需要特殊處理 # 於是,fact(n)用遞歸的方式寫出來就是: def facts(n): if n == 1: return 1; return n * facts(n-1); print facts(4); #24 print facts(1); #1 ''' facts(4) ===> 4 * facts(3) ===> 4 * (3 * facts(2)) ===> 4 * (3 * (2 * facts(1))) ===> 4 * (3 * (2 * 1)) ===> 4 * (3 * 2) ===> 4 * 6 ===> 24 遞歸函數的優點就是定義簡單,邏輯清晰,理論上,左右的遞歸函數都可以寫成循環的方式,但是 循環不如遞歸清晰 使用遞歸函數要註意防止棧溢出,在計算機中,函數的調用是通過棧stack這種數據結構實現的,當 進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限 的,所以遞歸調用的次數過多就會導致棧溢出,如fact(2000) ''' # print fact(2000); # maximum recursion depth exceeded ''' 解決遞歸調用棧溢出的方法就是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,把循環 看成是一種特殊的尾遞歸函數也是可以的 尾遞歸是指,在函數返回的時候,調用自身本身,並且return語句不能包含表達式。這樣,編譯器 或者解析器就可以把尾遞歸做優化,使遞歸本身無論被調用多少次,都只占用一個棧幀,不會出現 棧溢出的情況 上例中fact(n)函數由於return n*fact(n-1)引入了乘法表達式,所以就不是尾遞歸了,改成 尾遞歸,主要是把每一步的乘積傳入到遞歸函數中 ''' def fact(n): return fact_iter(n, 1) def fact_iter(num, product): if num == 1: return product return fact_iter(num - 1, num * product) print fact(4) #24 ''' 可以看到,return fact_iter(num - 1,num*product)僅返回遞歸函數本身,num-1和num*product 在函數調用前就會被計算,不影響函數調用。 fact(4)對應的fact_iter(4,1)的調用如下 ===> fact_iter(4, 1) ===> fact_iter(3, 4) ===> fact_iter(2, 12) ===> fact_iter(1, 24) 24 尾遞歸調用時如果做了優化,棧不會增長,因此,無論調用多少次都不會棧溢出。 但是,大多數編程語言沒有針對尾遞歸做優化,python解釋器也沒有做優化,所以,即使把上面的fact() 函數改成尾遞歸的方式,也會棧溢出 ''' #---------------------------------------------------------------------- #--局部變量----lacal variables def func(x): print 'x is:', x; x = 2 print 'Changed local x to', x; x = 50 func(x) print 'x is still', x; ''' x is: 50 Changed local x to 2 x is still 50 在函數中,第一次使用x值的時候,python使用函數聲明的形參的值 接著把值2賦給x,x是函數的局部變量,所以,當在函數內改變x的值的時候,在主塊中定義的 x不受影響,在最後一個print中,證明了主塊中的x值沒有受影響 ''' # --全局變量---global statement i = 50; def func(): global i; print 'i is:',i; i=2; print'change global i to :',i; func(); print 'value of i is:',i; ''' i is: 50 change global i to : 2 value of i is: 2 global 語句被用來聲明i是全局的,因此在函數內把值賦給i是,主塊中也改變 也可以用同一個global語句指定多個全局變量,global a,b,c ''' # ---非局部域------python 3中 nonlocal定義 # 函數形參和內部變量都存儲在locals名字空間中,下面來看一看把 def txt(a,*args,**kwargs): s = 'hello world'; print locals(); print txt(*xrange(1,5),b = 3,c = 'ccc') ''' {'a': 1, 's': 'hello world', 'args': (2, 3, 4), 'kwargs': {'c': 'ccc', 'b': 3}} 這是寫在一行的為了方便看清,我把他分開 除非使用global、nonlocal特別聲明,否則在函數內部使用的賦值語句,總在locals名字空間 中新建一個對象關聯。 註:賦值是名字指向新的對象,而非通過名字來改變對象狀態。 ''' # --例 i = 7; print hex(id(i)); #0x4e764c8L 獲取i的內存地址,並以16進制輸出 def test (): i='hi'; print hex(id(i)),i; #0x5469bc0L hi,兩個i指向不同的對象 print test(); print i; #7 外部變量沒變 #如果僅僅是引用外部變量,那麽按照LEGB順序在不同域查找該名字 # 順序:locals--enclosing function--globals-- _builtins_ ''' 命名空間:命名空間是對變量名的分組劃分 不同組的相同名稱的變量視為兩個獨立的變量,因此隸屬於不同的分組(即命名空間)的變量名可重復。 命名空間可以存在多個,使用命名空間,表示該命名空間中查找當前名稱 locals:函數內部名字空間,包括局部變量和形參 enclosing function:外部嵌套函數的名字空間 globals:函數定義所在模塊的名字空間 _builtins_:內置模塊的名字空間 ''' __builtins__.b = 'builtins' g = 'globals' def enclose(): e = 'enclosing' def test(): l = 'locals' print l; print e; print g; print b; return test; t = enclose(); print t(); ''' locals enclosing globals builtins ''' ''' 獲取外部空間的名字可以了,但如果想將外部名字關聯到一個新對象,就要用global關鍵字,指明 要修改的是globals名字空間。python3中提供了nonlocal關鍵字,用來修改外部嵌套函數名字 空間,2.7沒有 ''' #----------------------- x = 30; print hex(id(x)); #0x46962a0L 這裏每次運行都不一樣,一個地址只能存一個對象 def test(): global x,y; #聲明x,y是globals名字空間中的。 x = 70; #globals()['x'] = 70 y = 'hello world' #globals()['y'] = '...' 新名字 print hex(id(x)); print test(); #引用的是外部變量x print x,hex(id(x)); #70 0x50566a8L x被修改,外部的x指向新對象70 print x,y; # 70 hello world globals 名字空間中出現了y #-------------------閉包——------------------------------------------- #閉包是指:當函數離開創建環境後,依然保持有上下文狀態,比如下面的a和b,在離開text函數後,依然 # 持有text.x對象 def text(): x = [1,2]; print hex(id(x)); def a(): x.append(3); print hex(id(x)); def b(): print hex(id(x)),x; return a,b; a,b = text(); #0x496c608L text.x a() # 0x496c608L 指向test.x b() # 0x496c608L [1, 2, 3] # text在創建a,b時,將他們所引用的外部對象X添加到func_closure列表中,因為x引用計數增加了,所以 # 就算text堆棧幀沒有了,x對象也不會被回收 print a.func_closure; #(<cell at 0x00000000059CD318: list object at 0x0000000005A4C608>,) print b.func_closure; #(<cell at 0x00000000059CD318: list object at 0x0000000005A4C608>,) # 為什麽用function.func_closure ,而不是堆棧幀的名字空間,因為text僅僅返回兩個函數對象,並沒有調用他 # 們,自然不可能為他們創建堆棧幀,這樣一來,就導致每次返回的a,b都是新建對象,否則這個閉包狀態就被覆蓋了 def test(x): def a(): print x; print hex(id(a)); return a; a1 = test(100); #0x5669898L 每次創建a都提供不同的參數。 a2 = test('hi'); #0x5669a58L 可看到兩次返回的函數對象不同 print a1(); #100 a1的狀態沒有被a2破壞 print a2(); # hi print a1.func_closure; #(<cell at 0x00000000050DE078: int object at 0x0000000004AF6BA0>,) print a2.func_closure; # (<cell at 0x00000000050DE0A8: str object at 0x0000000004FD9BC0>,) # a1 a2 持有的閉包列表是不同的 print a1.func_code is a2.func_code; #True 這很好理解,字節碼沒必要有多個 print a1.func_code; print a2.func_code; # <code object a at 0000000004C7F030, file "D:/pycharmhome/venv/demo6.12.28.py", line 204> # 通過func_code可以獲知閉包所引用的外部名字 # co_cellvars:被內部函數引用的名字列表; # co_freevars:當前函數引用外部的名字列表 print test.func_code.co_cellvars; #('x',)被內涵書a引用的名字 print a.func_code.co_freevars; #('x',) a引用外部函數test中的名字 #使用閉包還要註意 延遲獲取 def test(): for i in range(3): def a(): print i; yield a; a,b,c = test(); a(),b(),c() ''' 2 2 2 為什麽輸出都是2,首先test只是返回函數對象,並沒有執行,其次。test完成for循環時,i已經等於2,所以 執行a,b,c時,他們持有i自然也等於2 ''' #----------------------------------------------------------------------------- #------堆棧幀 # python堆棧幀基本上就是對X86的模擬,用指針BP,SP,IP寄存器,堆棧幀成員包括函數執行所需的名字空間, # 調用堆棧鏈表,異常狀態等 ''' typedef struct _frame { PyObject_VAR_HEAD struct _frame *f_back; // 調?用堆棧 (Call Stack) 鏈表 PyCodeObject *f_code; // PyCodeObject PyObject *f_builtins; // builtins 名字空間 PyObject *f_globals; // globals 名字空間 PyObject *f_locals; // locals 名字空間 PyObject **f_valuestack; // 和 f_stacktop 共同維護運?行幀空間,相當於 BP 寄存器。 PyObject **f_stacktop; // 運?行棧頂,相當於 SP 寄存器的作?用。 PyObject *f_trace; // Trace function PyObject *f_exc_type, *f_exc_value, *f_exc_traceback; // 記錄當前棧幀的異常信息 PyThreadState *f_tstate; // 所在線程狀態 int f_lasti; // 上?一條字節碼指令在 f_code 中的偏移量,類似 IP 寄存器。 int f_lineno; // 與當前字節碼指令對應的源碼?行號 ... ... PyObject *f_localsplus[1]; // 動態申請的?一段內存,?用來模擬 x86 堆棧幀所在內存段。 } PyFrameObject; ''' # 可以用sys._gerframe(0)或inspect.currentframe()獲取當前堆棧幀,其中_getframe()深度參 # 數為0表示當前函數,1表示調用堆棧的上個函數。除用於調試外,還可以利用堆棧幀做別的 #---權限管理 # 通過調用堆棧檢查函數caller以實現權限管理 def save(): f = _gerframe(1) if not f.f_code.co_name.endswith('_logic'): #檢車XX的名字,限制調用者身份 raise exception('error'); #還可以有檢查跟多信息 print 'ok' # ---------------------------------------------------------------- #-------上下文 # 通過調用堆棧,可以隱室向整個執行流程傳遞上下文對象。inspect.stack ?frame.f_back更方便 import inspect; def get_context(): for f in inspect.stack(): #循環調用堆棧列表 context = f[0].f_locals.get('context') ; #查看該堆棧名字空間中是否有目標 if context:return context; #找到就返回,並終止循環 def controller(): context = 'contextobject'; #將context添加到locals名字空間 model(); def model(): print get_context(); #通過調用堆棧超找context controller(); #contextobject 測試通過


函數變量閉包