码迷,mamicode.com
首页 > 其他好文 > 详细

函数变量闭包

时间:2017-12-31 23:28:09      阅读:158      评论:0      收藏:0      [点我收藏+]

标签:函数。变量。闭包

# -*- 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 测试通过


函数变量闭包

标签:函数。变量。闭包

原文地址:http://blog.51cto.com/11927232/2056276

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!