递归函数, 匿名函数, yield from
递归函数
函数执行流程
http://pythontutor.com/visualize.html#mode=edit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
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 |
1) 全局帧中生成foo1,foo2,foo3,main函数对象.
2) main函数调用.
3)main中查找内建函数print压栈,将常量字符串压栈,调用函数,弹出栈顶.
4)main中全局查找函数foo1压栈,将常量100,101压栈,调用函数foo1,创建栈帧.print函数压栈,字符串和变量b,b1压栈,调用函数,弹出栈顶,返回值.
5)main中全局查找foo2函数压栈,将常量200压栈,调用foo2,创建栈帧.foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧.foo3完成print函数调用后返回.foo2恢复调用,执行print后,返回值.main中foo2调用结束弹出栈顶,main继续执行print函数调回,弹出栈顶.main函数返回. |
递归Recursion
递归动态图示: http://codingpy.com/article/10-gifs-to-understand-some-programming-concepts/
函数直接或间接调用自身就是递归.
递归需要有边界条件,递归前进段,递归返回段.
递归一定要有边界条件.
当边界条件不满足的时候,递归前进.
当边界条件满足的时候,递归返回.
斐波那契数列:
如果设F(n)为该数列的第n项,(n∈N-1),那么这句话可以写成:F(n) = F(n-1) + F(n-2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# F(0) = 0, F(1) = 1, F(n) = F(n-1)+F(n-2) pre = 0 cur = 1 print(pre, cur, end=' ') n = 4
for i in range(n-1): pre, cur = cur, pre + cur print(cur, end=' ')
# 运行结果: 0 1 1 2 3 |
# 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(4): print(fib(i), end = ' ')
# 运行结果: 1 1 2 3
# 解析: fib(3) + fib(2). fib(3)调用fib(3), fib(2), fib(1). fib(2)调用fib(2), fib(1). fib(1)是边界. |
递归要求:
递归一定要有退出条件,递归调用一定要执行到这个退出条件.没有退出条件的递归调用,就是无限调用.
递归调用的深度不宜过深:
python对递归调用的深度做了限制.
超过递归深度限制,抛出RecursionError:maxinum recursion depth exceeded超出最大深度.
sys.getrecursionlimit().
递归的性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# for循环. import datetime start = datetime.datetime.now() pre = 0 cur = 1 print(pre, cur, end=' ') n = 35 for i in range(n-1): pre, cur = cur, pre + cur print(cur, end=' ') delta = (datetime.datetime.now() - start).total_seconds() print(delta)
# 运行结果: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 0.0 |
# 递归: import datetime n = 35 start = datetime.datetime.now() def fib(n): return 1 if n < 2 else fib(n-1) + fib(n-2) for i in range(n): print(fib(i), end=' ') delta = (datetime.datetime.now() - start).total_seconds() print(delta)
# 运行结果: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 8.26452 |
循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果.
fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果,而且给定一个n都要进行2n次递归,深度越深,效率越低.
为了获取斐波那契数列需要外面再套一个n次的循环,效率就更低了.
递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就溢出了.
斐波那契数列的改进:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
pre = 0 cur = 1 print(pre, cur, end = ' ') 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(4) |
# 改进 1)左边的fib函数和循环的思想类似. 2)参数n是边界条件,用n来计数. 3)上一次的计算结果直接作为函数的实参. 4)效率很高. 5)和循环相比,性能近似,所以并不是说递归一定效率低下,但是递归有深度限制. |
间接递归
1 2 3 4 5 6 7 8 |
# 间接递归 def foo1(): foo2()
def foo2(): foo1()
foo1() |
间接递归,是通过别的函数调用了函数自身.
但是,如果构成了循环递归是非常危险的,但是往往这种情况在复杂代码情况下,还是可能发生这种调用.要用代码规范来避免这种递归调用的发生.
递归总结
递归是一种很自然的表达,符合逻辑思维.
递归相对运行效率低,每一次调用函数都要开辟栈帧.
递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了.
如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微要复杂一些,但是只要不是死循环,可以多次迭代直至算出结果.
绝大多数递归,都可以使用循环实现.
即使递归代码很简洁,但是能不用则不用递归.
匿名函数
匿名,即没有名字.
匿名函数,即没有名字的函数.
没有名字如何定义?
没有名字如何调用?
如果能调用,如何使用?
python借助lambda表达式构建匿名函数.
格式:
lambda 参数列表: 表达式
如:
lambda x: x**2
(lambda x: x**2)(4) # 调用.
foo = lambda x,y:(x+y)**2 # 不推荐这么用.
foo(2,1)
def foo(x,y): # 建议使用普通函数.
return (x+y)**2
foo(2,1)
使用lambda关键字来定义匿名函数.
参数列表不需要小括号.
冒号是用来分割参数列表和表达式的.
不需要使用return,表达式的值,就是匿名函数返回值.
lambda表达式(匿名函数)只能写在一行上,被称为单行函数.
用途: 在高阶函数传参时,使用lambda表达式,往往能简化代码.
1 2 3 4 5 6 7 8 9 10 |
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))) [x for x in (lambda *args: map(lambda x: x+1, args))(*range(5))] # 高阶函数. [x for x in (lambda *args: map(lambda x: (x+1,args), args))(*range(5))] |
生成器
生成器generator
生成器指的是生成器对象,可以由生成器表达式得到,也可以使用yield关键字得到一个生成器函数,调用这个函数得到一个生成器对象.
生成器函数:
函数体中包含yield语句的函数,返回生成器对象.
生成器对象,是一个可迭代对象,是一个迭代器.
生成器对象,是延迟计算,惰性求值的.
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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 * |
y = (i for i in range(5)) print(type(y)) print(next(y)) print(next(y))
# 运行结果: <class 'generator'> 0 1 |
普通的函数调用fn(),函数会立即执行完毕,但是生成器函数可以使用next函数多次执行.
生成器函数等价于生成器表达式,只不过生成器函数可以更加的复杂.
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def gen(): print('line 1') yield 1 print('line 2') yield 2 print('line 3') return 3 next(gen()) # line 1 next(gen()) # line 1 g = gen() print(next(g)) # line 1 print(next(g)) # line 2 # print(next(g)) # StopIteration, 此处生成器找不到yield所以抛异常. print(next(g, 'End')) # 没有元素给个缺省值.
# 运行结果: line 1 line 1 line 1 1 line 2 2 line 3 End |
在生成器函数中,使用多个yield语句,执行一次后会暂停执行,把yield表达式的值返回.
再次执行,会执行到下一个yield语句.
return语句依然可以终止函数运行,但return语句的返回值不能被获取到.
return会导致无法继续获取下一个值,抛出StopIteration异常.
如果函数没有显示的return语句,如果生成器函数执行到结尾,一样会抛出异常StopIteration.
生成器函数:
包含yield语句的生成器函数生成生成器对象的时候,生成器函数的函数体不会立即执行.
next(generator)会从函数的当前位置向后执行之后碰到的第一个yield语句,会弹出值,并暂停函数执行.
再次调用next函数,和上一条一样的处理过程.
没有多余的yield与能被执行,继续调用next函数,会抛异常StopIteration.
生成器应用
生成器函数:
包含yield语句的生成器函数生成生成器的时候,生成器函数的函数体不会立即执行.
next(generator)会从函数的当前位置向后执行到之后碰到的一个yield语句,会弹出值,并暂停函数执行.
再次调用next函数,和上一条一样的处理过程.
没有多余的yield语句能被执行,继续调用next函数,会抛异常StopIterator.
判断一个函数是否为generator函数: 使用isgeneratorfunction判断.
In [6]: from inspect import isgeneratorfunction
In [7]: isgeneratorfunction(func)
Out[7]: True
要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator.
>>> from collections import Iterable
>>> isinstance(fab, Iterable)
False
>>> isinstance(fab(5), Iterable)
True
在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def counter(): i = 0 while True: i += 1 yield i def inc(c): return next(c) c = counter() print(inc(c)) print(inc(c)) # 对同一函数对象进行操作.
# 运行结果: 1 2 |
def counter(): i = 0 while True: i += 1 yield i def inc(): c = counter() return next(c) print(inc()) print(inc()) print(inc()) # 对三个不同的函数对象进行操作.
# 运行结果: 1 1 1 |
计数器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def inc(): def counter(): i = 0 while True: i += 1 yield i c = counter() return lambda : next(c) foo = inc() print(foo()) print(foo())
# 运行结果: 1 2 |
Lambda表达式是匿名函数. Return返回的是一个匿名函数.
左边第8行等价于: def _inc(): return next(c) return _inc |
处理递归问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def fib(): x = 0 y = 1 while True: yield y x, y = y, x+y
foo = fib() for _ in range(5): print(next(foo))
for _ in range(100): # print(next(foo)) next(foo)
print(next(foo))
# 运行结果: 1 1 2 3 5 6356306993006846248183 |
# 等价于下面代码:
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)
fib1(5) |
协程coroutine:
生成器的高级用法.
比进程,线程轻量级.
是在用户空间调度函数的一种实现.
python3 asyncio就是协程实现,已经加入到标准库.
python3.5使用async,await关键字直接原生支持协程.
协程调度实现思路:
有2个生成器A,B.
next(A)后,A执行到yield语句暂停,然后执行next(B),B执行到yield语句也暂停,就再次调用next(A),再调用next(B),周而复始,就实现了调度的效果.
可以引入调度的策略来实现切换的方式.
协程是一种非抢占式调度.
另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。
通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:
1 2 3 4 5 6 7 8 9 |
def read_file(fpath): BLOCK_SIZE = 1024 with open(fpath, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return |
yield from
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def inc(): for x in range(1000): yield x
foo = inc() print(next(foo)) print(next(foo)) print(next(foo))
# 运行结果: 0 1 2
|
# 左边等同于如下代码: def inc(): # for x in range(1000): # yield x yield from range(1000)
foo = inc() print(next(foo)) print(next(foo)) print(next(foo)) |
yield from是python3.3出现的新的语法.
yield from iterable是for item in iterable: yield item形式的语法糖.
1 2 3 4 5 6 7 8 9 10 11 12 |
# 从可迭代对象中一个个拿元素:
def counter(n): # 生成器,迭代器 for x in range(n): yield x
def inc(n): yield from counter(n)
foo = inc(10) print(next(foo)) print(next(foo)) |