函數變量閉包
阿新 • • 發佈:2018-01-01
函數。變量。閉包# -*- 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 測試通過
函數變量閉包