python学习八 递归函数 高级特征(切片,迭代),生成器,迭代器

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_43426335/article/details/98086356

递归函数

>>> def fact(n):
...     if n==1:
...             return 1
...     return n*fact(n-1)
...
>>> fact(4)
24

计算过程
===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
理论上,所有的递归函数都可以写成循环的方式,但循环逻辑不如递归。

要注意栈溢出
解决栈溢出的一个很好的办法是使用尾递归
尾递归是指:在函数返回的时候调用自身本身,并且,return语句不能包含表达式,
这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
将上面递归改为尾递归代码如下

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)

遗憾的是,大部分编译器都没对尾递归做优化,python解释器也没有,所以任何递归函数都存在栈溢出的问题。

高级特性

切片

取一个list前三个元素
笨方法:
[L[0], L[1], L[2]]
取前n个时可以用for…in…循环
切片
[0:3]表示,从索引0开始取,直到索引3为止
如果第一个是0,还可以省略
L[:3]
L[-10:]表示最后面10个数
L[:10:2]表示取前十个,每两个取一个
L[::5]表示所有数每五个取一个

对tuple也可以切片操作
(0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)
字符串也可以切片操作
‘ABCDEFG’[:3]
‘ABC’

去除字符串中首尾的空格要借助str的strip函数实现

>>> a='       adf      '
>>> a.strip()
'adf'

迭代

通过for循环来遍历list或tuple,这种遍历我们称为迭代

迭代dict
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
...     print(key)
...
a
c
b
迭代字符串
>>> for ch in 'ABC':
...     print(ch)
...
A
B
C
输出value
>>> for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)
...
0 A
1 B
2 C
输出成对的
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
...     print(x, y)
...
1 1
2 4
3 9

列表生成式

要生成[11,22,33,…1010]怎么做
1)循环

>>> l=[]
>>> for x in range(1,11):
...     l.append(x*x)
...
>>> l
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

但是太麻烦了
列表生成式可以用一行语句代替上面的list

扫描二维码关注公众号,回复: 7198870 查看本文章
>>> [x*x for x in range(1,11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

要把x*x放到前面(放在前面的是输出目标,后面哪个是x变量,后面的在变,前面给的也就会变喽),后面跟for循环
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

>>> [x*x for x in range(1,11) if x%2==0]
[4, 16, 36, 64, 100]

还可以使用两层循环(类似于c语言中的for循环),可以生成全排列

>>> [m+n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

三层及以上也可以,只是用的比较少了

列表生成式 简单点的应用,列出当前文件下所有的文件和目录名

os是一个包
>>> import os
>>> [d for d in os.listdir('.')]
['DLLs', 'Doc', 'include', 'Lib', 'libs', 'LICENSE.txt', 'NEWS.txt', 'python.exe', 'python3.dll', 'python37.dll', 'pythonw.exe', 'Scripts', 'tcl', 'Tools', 'vcruntime140.dll']

for循环可以同时迭代多个变量
dict.items() 返回可遍历的(键,值)元组数组

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
...     print(k, '=', v)
...
y = B
x = A
z = C

列表生成式也可以使用两个变量来生成list

k+'='+v
此处加号相当于连接符吧
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

借助列表生成式来将list中所有的字符串变成小写

>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

生成器generator

一边循环一边计算的机制成为生成器

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
创建一个generator
1)把列表生成式的[]改为()
	创建一个list与generator的区别仅仅在于外面的[]和  (),L是一个list,g是一个generator
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

如何打印出list的每一个元素
通过next()函数

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
以此类推
直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

正确的方法是使用for循环,generator也是可迭代对象

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
... 
0
1
4
9
16
25
36
49
64
81

我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,

如果推算的算法比较复杂,用类似列表生成器的for无法实现时,还可以借助函数
例如,斐波拉契数列,列表生成式是无法表示出来的,可以借助函数

函数写法
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

注意赋值语句
a,b=b,a+b
相当于t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

改为genenrator
将print(b)改为yield b 就可以了 
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

普通函数调用和generator函数区别:
普通函数调用直接返回结果:

>>>r=abs(6)
>>>r
6

generator函数实际返回一个generator对象:

>>>g=fib(6)
>>>g
<generator object fib at 0x1022ef948>

迭代器Iterator

可以被next()函数调用并不断返回下一个值的对象称为迭代器Iterator
可以直接作用于for循环的数据类型有以下几种:
1)集合数据类型,list,tuple,dict,set,str等;
2)generator 包括生成器和带yeild的generator函数。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable对象;
可以用isinstance()判断一个对象是否为Inter able对象:

#先导入头文件!!!!
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIterator错误后表示无法继续返回下一个值了

可用isinstance()判断一个对象是否为Iterator对象:

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是generator对象 但list,dict,str虽然是Iterable,却不是Itertor
想把list,dict,str等Iterable变成Iterator可以使用iter()函数:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
解释iterator和iterable
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

猜你喜欢

转载自blog.csdn.net/qq_43426335/article/details/98086356