[toc]
python 函数(三)
变量名解析原则LEGB
- Local本地作用域、局部作用域的local命名空间,函数调时创建,调用结束消亡;
- Enclosing, Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的全名空间;
- Global,全局作用域,即一个模块的命名空间。模块被import 时创建,解释器退出时消亡;
- Build-in,内置模块的命名空间,生命周期从python解释器启动时创建 到解释器退出时消亡。例如
print(open)
,print
和open
都是内置的变量; - 所以一个名词的查找顺序就是
LEGB
;
函数执行流程
def foo1(b,b1=3): print("foo1 called",b,b1) def foo2(c): foo3(c) print("foo2 called",c) def foo3(d): print("foo3 called", d) def main(): print("main called") foo1(100,101) foo2(200) print("main ending") main() main called foo1 called 100 101 foo3 called 200 foo2 called 200 main ending
- 全局帧中生成foo1、foo2、foo3、main函数对象;
- main函数调用;
- main中查找内建函数print压栈,将常量字符串压栈,调用函数,弹出栈顶;
- main中全局查找函数foo1压栈,将常量100、101压栈,调用函数foo1,创建栈帧。print函数压栈,字符串和亦是b、b1压栈、调用函数,弹出栈顶,返回值。
- main中全局查找foo2函数压栈,将常量200压栈,调用foo2,创建栈帧。foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧。foo3完成print函数调用后返回。foo2恢复调用。执行print后,返回值。main中foo2调用结束弹出栈顶,继续执行print函数调用,弹出栈顶。main函数返回。
递归Recursion
- 函数直接或者间接调用自身就是 递归;
- 递归需要有边界条件、递归前进段、递归返回段;
- 递归一定要有边界操作;
- 当办界条件不满足的时候,递归前进;
当边界条件满足的时候,递归返回;
斐波那契数列
Fibonacci number: 1,1,2,3,5,8,13,21,34,55,89,144,...
- 如果设F(n)为该数列的第n项(n∈N* ),那么这句话可以写成如下形式:
F(n)=F(n-1)+F(n-2)
F(0)=0, F(1)=1,F(n)=F(n-1)+F(n-2)
pre = 0 cur = 1 print(pre,cur,end=' ') n = 10 for i in range(n-1): pre, cur = cur,pre+cur print(cur,end=' ') #结果: 0 1 1 2 3 5 8 13 21 34 55
递归实现
F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)
def fib(n): return 1 if n < 2 else fib(n-1) + fib(n-2) for i in range(10): print(fib(i), end=' ') # 1 1 2 3 5 8 13 21 34 55
解析:
fib(3) + fib(2) fib(3) 调用fib(3)、fib(2)、fib(1) fib(2) 调用fib2(2)、fib(1) fib(1) 是边界
递归要求
- 递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用;
- 递归调用的深度不宜过深;
- Python 对递归调用的深度做了限制,以保护解释器;
- 超过递归深度限制,抛出
RecurisonError: maxinum recursion depth exceeded
超出最大深度; sys.getrecursionlimit()
In [1]: import sys In [2]: print(sys.getrecursionlimit()) 3000 In [4]: sys.setrecursionlimit(1000) In [5]: print(sys.getrecursionlimit()) 1000
优化改进
fib函数和循环的思想类似;
参数n是边界条件,用n来计数;
上一次的计算结果直接作为函数的实参;
效率很高;
和循环比较,性能相近。所以并不是递归一定是效率底下,但是递归有深度的限制;
pre = 0 cur = 1 print(pre, cur) def fib(n, pre=0,cur=1): pre, cur = cur, pre + cur print(cur, end=' ') if n == 2 : return fib(n-1, pre, cur) fib(10) # 输出 1 2 3 5 8 13 21 34 55
间接递归
如
def foo1(): foo2() def foo2(): foo1() foo1()
间接递归,是通过别的函数调用了函数自身;
但是,如果构成了循环递归调用是非常危险的,但是往往这种情况下代码复杂的情况下,还是可能发生这种调用。要用代码的规范来避免这种递归调用的发生。
递归总结
- 递归是一种很自然的表达,符合逻辑思维;
- 递归相对运行效率低,每一次调用函数都要开辟栈帧;
- 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了;
- 如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只要不是死trggsk以多次迭代直至算出结果;
- 绝大多数递归,都可以使用循环实现;
- 即使递归代码很简洁,但是能不用则不用递归。
递归练习
- 求n的阶乘
def fac(n): if n == 1 : return 1 return n * fac(n-1) def fac1(n, p =1 ): if n == 1: return p p *= n print(p) fac1(n-1,p) return p def fac2(n, p = None): if p is None: p = [1] if n == 1 : return p[0] p[0] *= n print(p[0]) fac2(n-1, p) return p n = 10 print(fac(n)) print(fac1(n)) print(fac2(n)) # 输出 3628800 10 90 720 5040 30240 151200 604800 1814400 3628800 10 10 90 720 5040 30240 151200 604800 1814400 3628800 [3628800]
- 将一个数逆序放入列表中,例如
1234 => [4,3,2,1]
num = 1234 def revert(num, target=[]): if num: target.append(num[len(num) -1]) # target.append(num[-1:]) revert(num[:len(num) -1]) return target print(revert(str(num)))
- 解决猴子吃桃问题
猴子第一天摘下苦干个桃子,当即吃了一半,还不过瘾,又多吃了一个,第二天早上又剩下的桃子吃掉一半,又多吃了一个。以后每天早上都各吃了前一天剩下的一半零一个。到第10天早上想吃时,只剩下一个桃子了。求第一天共摘多少个桃子。
def peach(days=10): if days == 1: return 1 return(peach(days-1)+1)*2 print(peach())
注意这里必须是10, 因为return(peach(days-1)+1)*2 立即拿不到结果,必须通过再一次进入函数时判断是不是到了最后一天。
也就是当前使用的值是由下一次函数调用得到,所以要执行10次函数调用。
换种方式表达
def peatch(days=1): if days == 10: return 1 return(peatch(days+1)+1)*2 print(peatch())
匿名函数
Python 借助Lambda表达式构建匿名函数
例
In [1]: (lambda x:x*2)((4,2)) Out[1]: (4, 2, 4, 2)
- 使用lambda 关键字来定义匿名函数;
- 参数列表不需要小括号;
- 冒号是用来分割参数列表和表达式的;
- 不需要使用return,表达式的值,就是匿名函数返回值;
- lambda表达式(匿名函数)只能写在一行上,被称为单行函数
- 用途
在高阶函数传参时,使用lambda表达式,往往能简化代码;
print((lambda :0)()) print((lambda x,y=3: x + y )(5)) print((lambda x,y=3: x + y)(5,6)) print((lambda x, *, y=30: x + y)(5)) print((lambda x, *, y=30: x + y)(5, y=10)) print((lambda *args: (x for x in args))(*range(5))) print((lambda *args:[x+1 for x in args])(*range(5))) print((lambda *args: [x + 2 for x in args])(*range(5))) print((lambda *args: { x + 2 for x in args })(*range(5))) ###高阶函数 lst = [x for x in (lambda *args: map(lambda x: x+1,args))(*range(5))] print(lst) lst2 = [x for x in (lambda *args: map(lambda x:(x+1,args),args))(*range(5))] print(lst2) 0 8 11 35 15 <generator object <lambda>.<locals>.<genexpr> at 0x10742dcf0> [1, 2, 3, 4, 5] [2, 3, 4, 5, 6] {2, 3, 4, 5, 6} [1, 2, 3, 4, 5] [(1, (0, 1, 2, 3, 4)), (2, (0, 1, 2, 3, 4)), (3, (0, 1, 2, 3, 4)), (4, (0, 1, 2, 3, 4)), (5, (0, 1, 2, 3, 4))]
生成器
生成器generator
生成器指的是生成器对象,可以由生成器表达式得到,也可以使用yeild关键字得到一个生成器函数,调用这个函数得到一个生成器对象生成器函数
函数体中包含yield语句的函数,返回生成器对象;
生成器对象,是一个可迭代对象,是一个迭代对象;
生成器对象,是延迟计算、惰性求值的。
def inc(): for i in range(5): yield i print(type(inc)) print(type(inc())) x = inc() print(type(x)) print(next(x)) for m in x: print(m, '*') for m in x: print(m, '**') # 输出 <class 'function'> <class 'generator'> <class 'generator'> 0 1 * 2 * 3 * 4 *
In [1]: y = (i for i in range(5)) In [2]: print(type(y)) <class 'generator'> In [3]: print(next(y)) 0 In [4]: print(next(y)) 1
- 普通的函数调用fn(), 函数会立即执行完毕,但是生成器函数可以使用next函数多次执行;
- 生成器函数等价于生成器表达式,只不过生成器函数可以更加的复杂;
In [5]: def gen(): ...: print('line 1') ...: yield 1 ...: print('line 2') ...: yield 2 ...: print('line 3') ...: return 3 In [6]: next(gen()) line 1 Out[6]: 1 In [7]: next(gen()) line 1 Out[7]: 1 In [8]: g = gen() In [9]: print(next(g)) line 1 1 In [10]: print(next(g)) line 2 2 In [11]: print(next(g)) line 3 --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-11-1dfb29d6357e> in <module>() ----> 1 print(next(g)) StopIteration: 3 In [12]: print(next(g, 'End')) End
- 在生成器函数中,使用多个yield语句,执行一次后会暂停执行,把yeild表达式的值返回;
- 再次执行会执行到下一个yield语句;
- return语句依然可以终止函数运行,但是return语句的返回值不能被获取到;
- return会导致无法继续获取一下个值,抛出StopIteration异常;
- 如果函数没有显示的return语句,如果生成器函数执行到结尾,一样会抛出StopIteration异常;
生成器函数
- 包含yield语句的生成器函数生成生成器对象,生成器函数的函数体不会立即执行
- next(generator)会从函数的当前位置向后执行到之后踫到的第一个yeild语句,会弹出值,并暂停函数执行;
- 再次调用next函数,和上一条一样的处理过程;
- 没有多余的yield语句的能被执行,继续调用next函数,会抛出StopIteration异常;
生成器应用
In [13]: def counter(): ...: i = 0 ...: while True: ...: i +=1 ...: yield i In [14]: def inc(c): ...: return next(c) In [15]: c = counter() In [16]: print(inc(c)) 1 In [17]: print(inc(c)) 2 In [18]: print(inc(c)) 3 In [19]: print(inc(c)) 4
In [21]: def counter(): ...: i = 0 ...: while True: ...: i += 1 ...: yield i ...: In [22]: def inc(): ...: c = counter() ...: return next(c) In [23]: print(inc()) 1 In [24]: print(inc()) 1 In [25]: print(inc()) 1
计数器
def inc(): def counter(): i = 0 while True: i +=1 yield i c = counter() return lambda : next(c) foo = inc() print(foo()) print(foo()) print(foo()) # 输出 1 2 3
lambda 表达式是匿名函数
return 返回的是一个匿名函数
等价于下面的代码
def inc(): def counter(): i = 0 while True: i +=1 yield i c = counter() def _inc(): return next(c) return _inc foo = inc() print(foo()) print(foo()) print(foo())
处理递归问题
def fib(): x = 0 y = 1 while True: yield y x,y = x, x+y foo = fib() for _ in range(5): print(next(foo)) for _ in range(100): next(foo) print(next(foo))
等价于下面的代码
pre = 0 cur = 1 print(pre, cur, end=' ') def fib1(n, pre=0, cur=1): pre, cur = cur, pre + cur print(cur, end=' ') if n ==2 : return fib1(n-1,pre,cur)
- 协程coroutine
- 生成器的高级用法 ;
- 比进程、线程轻量级;
- 是在用户空间调度函数的一种实现;
- Python3 asyncio就是协程实现,已经加入到标准库;
- Python3.5使用async、await关键字直接原生支持协程;
- 协程高度器实现思路;
- 有2个生成器A、B
- next(A)后,A执行到了yeild语句暂停,然后去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),再调用next(B)在,周而复始,就实现了调度的效果;
- 可以引入调度的策略来实现切换的方式;
- 协程是一种非抢占调度
yield from
- 举例
In [26]: def inc(): ...: for x in range(1000): ...: yield x ...: In [27]: foo = inc() In [28]: print(next(foo)) 0 In [29]: print(next(foo)) 1 In [30]: print(next(foo)) 2
等价于下面的代码
In [31]: def inc(): ...: yield from range(1000) In [32]: foo = inc() In [33]: print(next(foo)) 0 In [34]: print(next(foo)) 1 In [35]: print(next(foo)) 2
- yield from 是Python 3.3 出现的新的语法
yield from iterable 是 for item in iterable: yield item 形式的语法粮
可迭代对象中一个个拿元素
In [36]: def counter(n): ...: for x in range(n): ...: yield x In [37]: def inc(n): ...: yield from counter(n) In [38]: foo = inc(10) In [39]: print(next(foo)) 0 In [40]: print(next(foo)) 1 In [41]: print(next(foo)) 2
格言
知止而后有定,定而后能静,静而后能安,安而后能虑,虑而后能得 数语,尽矣;