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