python基础八(迭代器、生成器、生成式、递归、匿名函数、面向过程编程)

发布时间 2023-04-06 12:05:02作者: coder雪山

一 迭代器

1、什么是迭代器
迭代器指的是迭代取值的工具,迭代是一个重复的过程,每次重复都是基于上一次的结果而
继续的,单纯的重复并不是迭代

2、为何要有迭代器
迭代器是用来迭代取值的工具,而涉及到把多个值循环取出来的类
有:字符串、列表、元组、字典、集合、打开文件
l=['lq','zd','xiaobao']
i=0
while i<len(l):
print(l[i])
i+=1

上述迭代取值的方式只适用于有索引的数据类型:列表、字符串、元组
为了解决基于索引迭代器取值的局限性
python必须提供一种是能够不依赖于索引的取值方式,这就是迭代器

3、如何用迭代器
可迭代对象:但凡内置有__iter__方法的都称之为可迭代的对象
s = ''
s.__iter__()
l = []
l.__iter__()
t = ()
t.__iter__()
s = {1, 2, 3}
s.__iter__()
d = {'k1': 1, }
d.__iter__()
with open(r'a.txt', mode='rt', encoding='utf-8') as f:
    f.__iter__()
    pass
调用可迭代对象下的__iter__方法会将其装换成迭代器对象
d = {'a': 1, 'b': 2, 'c': 3}
d_iterator = d.__iter__()
print(d_iterator)
print(d_iterator.__next__())
print(d_iterator.__next__())
print(d_iterator.__next__())
print(d_iterator.__next__())  # 抛出异常stopiteration
# <dict_keyiterator object at 0x000002124897B7C0>
# a
# b
# c

while True:
    try:
        print(d_iterator.__next__())
    except StopIteration:
        break

print('--->')  # 在一个迭代器取值取不干净的情况下,再对其取值取不到了
while True:
    try:
        print(d_iterator.__next__())
    except StopIteration:
        break
4、可迭代对象与迭代器对象详解
1) 可迭代对象("可以装换成迭代器的对象”):内置有__inter__方法对象
可迭代对象.__inter__():得到迭代器对象

2) 迭代器对象:内置有__next__方法并且内置有__iter__方法的对象
迭代器对象.__next__():得到迭代器的下一个值
迭代器对象.__iter__():得到迭代器的本身,说白了调了跟没调一个样子

5、可迭代对象和迭代器对象的区分
可迭代对象:字符串、列表、元组、集合、字典、文件对象
迭代器对象:文件对象
d = {'a': 1, 'b': 2, 'c': 3}
d_iterator = d.__iter__()
print(d_iterator.__next__())
# a
print(d_iterator is d_iterator.__iter__()) # True
6、for循环的工作原理:while循环叫条件循环,for循环可以叫迭代器循环
d = {'a': 1, 'b': 2, 'c': 3}
# 1、d.__iter__()得到一个迭代器对象
# 2、迭代器对象.__next__()拿到一个返回值,然后将返回值赋值给k
# 3、循环往复步骤2,直到到抛出StopIteration异常for循环会捕捉异常然后结束循环
for k in d:
    print(k)
# a
# b
# c

print(list("hello"))
# ['h', 'e', 'l', 'l', 'o']
7、迭代器优缺点总结
基于索引的迭代取值,所有迭代的状态都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,
所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存:
优点:
1)、为序列和非序列类型提供了一种统一的迭代取值方式。
2)、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,
同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,
受内存大小的限制,可以存放的值的个数是有限的。
缺点:
1)、除非取尽,否则无法获取迭代器的长度
2)、只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,
否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,
如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

二 生成器

如何得到自定义的迭代器:
在函数内一旦存在yield关键字,调用函数并不会执行函数体代码
会返回一个生成器对象,生成器即自定义的迭代器
def func():
    print('第一次')
    yield 1
    print('第二次')
    yield 2
    print('第三次')
    yield 3
    print('第四次')
g = func()
print(g)
# <generator object func at 0x000001C516009B30>
# 生成器就是迭代器
g.__iter__()
g.__next__
# 会触发函数体代码的运行,然后遇到yield停下来,将yield后的值
# 当做本次调用的结果返回
res1 = g.__next__()  # g.__next__等同于next(g)    #next(g)方法后,才执行yield上面的代码体,res1才是yield后面的返回值
print(res1)
# 第一次
# 1
res2 = g.__next__()
print(res2)
# 第二次
# 2
# 例:放无穷个数
def func():
    count = 0
    while True:
        count += 1
        yield count
g = func()
print(next(g))
print(next(g))
list(g)

三 yield表达式

x=yield 返回值
1、
# 带有yield的函数运行流程,func调用,赋值给g,只是生成了生成器,没有触发函数代码体运行,g.send(),才触发函数代码体运行。
# g.send()赋值给res,res才是yield的返回值。
def func(name):
    print('准备吃饭了:{}'.format(name))
    while True:
        # x拿到的是yield接受到的值
        x = yield
        print('今天吃:{}'.format(x))
g = func('xiaobao')
g.send(None)  # 等同于next(g),触发yield上面的代码运行。
g.send('包子')
g.send('油条')
g.send('排骨')
# 准备吃饭了:xiaobao
# 今天吃:包子
# 今天吃:油条
# 今天吃:排骨
2、综合应用
def func(name):
    food_list = []
    print('准备吃饭了:{}'.format(name))
    while True:
        # x拿到的是yield接受到的值
        x = yield food_list
        print('今天吃:{}'.format(x))
        food_list.append(x)
g = func('xiaobao')
res1 = g.send(None)  # 等同于next(g),触发yield上面的代码运行。
print(res1)
res2 = g.send('包子')
print(res2)
res3 = g.send('油条')
print(res3)
res4 = g.send('排骨')
print(res4)
'''
准备吃饭了:xiaobao
[]
今天吃:包子
['包子']
今天吃:油条
['包子', '油条']
今天吃:排骨
['包子', '油条', '排骨']
'''

四 生成式

1、列表生成式

列表生成式是python为我们提供的一种简化代码的解决方案,用来快速生成列表,语法如下(就是for循环加l.append()的简化)
'''
[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]

#类似于
res=[]
for item1 in iterable1:
if condition1:
for item2 in iterable2:
if condition2
...
for itemN in iterableN:
if conditionN:
res.append(expression)
'''
# 列表生成式案例
l = ['xiaobao_', 'lq_', 'zd']
# 把所有小字母全变成大写
new_l = [item.upper() for item in l]
print(new_l)
# ['XIAOBAO_', 'LQ_', 'ZD']
# 把所有的名字去掉后缀_
new_l = [item.replace('_', '') for item in l if True]  # 可以不用加if True
print(new_l)
# ['xiaobao', 'lq', 'zd']
2、字典生成式
items = [('xiaobao', 6), ('zd', 32), ('lq', 35), ('lr', 40)]
dic = {k: v for k, v in items if k != "lr"}  # 解压赋值
print(dic)
3、集合生成式
4、生成器表达式
g = (i for i in range(10) if i > 2)  # 没有元组生成器,元组不可改,没有.append()
print(g, type(g))  # !!!!!!!强调!!!!!  此刻g内部一个值没有(老母鸡,还没有下蛋),需要next(g),才产值。
for i in g:
    print(i)
应用:统计day19中笔记中字符个数
# 方法一:
with open(r'笔记.txt', mode='r', encoding='utf-8') as f:
    res = 0
    for line in f:
        # print(len(line))
        res += len(line)
    print(res)
    # 577
# 方法二
with open(r'笔记.txt', mode='r', encoding='utf-8') as f:
    res = sum([len(line) for line in f])  # sum()里面需要一个可迭代对象,方法二是列表
    print(res)
    # 577
# 方法三(效率最高)
with open(r'笔记.txt', mode='r', encoding='utf-8') as f:
    # res=sum((len(line) for line in f )) # sum()里面需要一个可迭代对象,方法三是生成器
    res = sum(len(line) for line in f)  # 简化,sum()是触发了next()了的,生成器才产值
    print(res)
    # 577
    print(len(line) for line in f)  # <generator object <genexpr> at 0x000001BEFA49F510>

四 递归函数

1、函数递归调用:是函数嵌套调用的一种特殊形式
具体是指:
在调用一个函数的过程中又直接或者间接的调用本身
def f1():
    print('from f1')
    f1()
f1()
# 在调用f1的过程中,又调用f2,而在调用f2的过程中又调用f1,这就是间接调用函数f1本身
def f1():
    print('from f1')
    f2()
def f2():
    print('from f2')
    f1()
f1()
从上图可以看出,两种情况下的递归调用都是一个无限循环的过程,但在python对函数的递归调用的深度做了限制,
因而并不会像大家所想的那样进入无限循环,会抛出异常,要避免出现这种情况,就必须让递归调用在满足某个特定条件下终止。
1)可以使用sys.getrecursionlimit()去查看递归深度,默认值为1000,虽然可以使用
sys.setrecursionlimit()去设定该值,但仍受限于主机操作系统栈大小的限制
2)python不是一门函数式编程语言,无法对递归进行尾递归优化。
# 一段代码的循环运行的方案有两种
# 方式一:while、for循环
while True:
    print(1111)
    print(2222)

# 方式二:递归的本质就是循环:
def f1():
    print(111)
    print(222)
    f1()
f1()
2、需要强调的一点是:
递归调用不应该无限的调用下去,必须在满足某种条件结束递归
def func(n):
    if n == 10:
        return
    print(n)
    n += 1
    func(n)
func(0)
3、递归的两个阶段
回溯:一层一层调用下去
递推:满足某种结束条件,结束递归调用,然后一层一层返回
# 例:问了五个人,第五的一个人说收入比第四的一个人高1000元,第一个人的收入3000元
def salary(n):
    if n == 1:
        return 3000
    return salary(n - 1) + 1000
res = salary(3)
print(res)
# 5000
4、递归的应用
l = [[1, 2], 3, [4, [5, [6, 7]]]]
def foo(items):
    for i in items:  # for循环相当于自带了一个return
        if type(i) is list:
            # 如果是列表,应该再循环、再判断,即重新运行本身的代码
            foo(i)
        else:
            print(i, end=' ')
foo(l)  # 打印结果1 2 3 4 5 6 7
使用递归,我们只需要分析出要重复执行的代码逻辑,然后提取进入下一次递归调用的条件或者说递归结束的条件即可,代码实现起来简洁清晰

五 算法之二分法

算法:是高效解决问题的方法
需求:有一个按照从小到大顺序排列的数字列表
需要从该数字列表中找到我们想要的那个一个数字
如何做更高效
nums = [1, 2, 3, 5, 7, 22, 33, 34]
find_num = 7

# 方案一:整体遍历效率太低
for num in nums:
    if num == find_num:
        print('find it')
        break

# 方案二:二分法
# 找中间值mid_num
nums = [1, 2, 3, 5, 7, 22, 33, 34]
def binary_num(find_num, l):
    print(l)

    mid_index = len(l) // 2
    if len(l) == 0:
        print('num not in list')
        return
    if find_num > l[mid_index]:
        # 接下来查找列表右半部分
        # 列表1=列表切右半部分
        l = l[mid_index + 1:]
        binary_num(find_num, l)
    elif find_num < l[mid_index]:
        # 接下来查找列表左半部分
        l = l[:mid_index]
        binary_num(find_num, l)
    else:
        print('find it')
binary_num(33, nums)

六 匿名函数

1、def用于定义有名函数
#  func=函数的内存地址
def func(x, y):
    return x + y
2、lamdab用于定义匿名函数
lambda x, y: x + y  # x+y前的return省略
print(func)  # <function func at 0x000001D758E27430>
print(lambda x, y: x + y)  # <function <lambda> at 0x000001D758E271F0>
3、调用匿名函数
# 方式一:
(lambda x, y: x + y)(1, 2)  # 调用,内存地址的调用
res = (lambda x, y: x + y)(1, 2)  # 返回值
print(res)
# 3
# 方式二: func = lambda x, y: x + y # “匿名”的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的 res = func(1, 2) print(res)
# 3
4、匿名用于临时调用一次的场景:更多的是将匿名与其他函数配合使用
匿名函数与有名函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放(垃圾回收机制),
所以匿名函数用于临时使用一次的场景,匿名函数通常与其他函数配合使用。
5、匿名函数使用
salaries = {
    'siry': 3000,
    'tom': 7000,
    'lili': 10000,
    'jack': 2000
}

# 要想取得薪水的最大值和最小值,我们可以使用内置函数max和min

def func(k):
    return salaries[k]
# 函数max会迭代字典salaries,每取出一个“人名”就会当做参数传给指定的匿名函数,然后将匿名函数的返回值当做比较依据,
# 最终返回薪资最高的那个人的名字

res = max(salaries, key=func)  # 返回值=func('siry')
print(res)

# 用匿名函数替换
res = max(salaries, key=lambda k: salaries[k])  # max(salaries,key=lambda k:salaries[k],reverse=True),排序反过来。
print(res)
# lili
res = min(salaries, key=lambda k: salaries[k])
print(res)
# jack
# -------sorted
# 同理,我们直接对字典进行排序,默认也是按照字典的键去排序的
res = sorted(salaries)
print(res)
# ['jack', 'lili', 'siry', 'tom']

# 按值排序,按照值得从小到大,得到一个键的列表
res = sorted(salaries, key=lambda k: salaries[k])
print(res)
# ['jack', 'siry', 'tom', 'lili']
# ------函数map
array = [1, 2, 3, 4, 5]
res = map(lambda x: x ** 2, array)
# map会依次迭代array,得到的值依次传给匿名函数(也可以是有名函数),而map函数得到的结果仍然是迭代器。
print(res)
# <map object at 0x1033f45f8>
print(list(res))    # 使用list可以依次迭代res,取得的值作为列表元素
# [1, 4, 9, 16, 25]
# ------函数array
对array进行合并操作,比如求和运算,这就用到了reduce函数reduce函数可以接收三个参数,一个是函数,
第二个是可迭代对象,第三个是初始值# reduce在python2中是内置函数,在python3中则被集成到模块functools中,需要导入才能使用
>>> from functools import reduce 
>>> res=reduce(lambda x,y:x+y,array)
>>> res
15解析:1 没有初始值,reduce函数会先迭代一次array得到的值作为初始值,
作为第一个值数传给x,然后继续迭代一次array得到的值作为第二个值传给y,
运算的结果为32 将上一次reduce运算的结果作为第一个值传给x,然后迭代一次array得到的结果作为第二个值传给y,
依次类推,知道迭代完array的所有元素,得到最终的结果15也可以为reduce指定初始值
>>> res=reduce(lambda x,y:x+y,array,100)

>>> res
115要求三:对array进行过滤操作,这就用到了filter函数,比如过滤出大于3的元素
>>> res=filter(lambda x:x>3,array)
解析:filter函数会依次迭代array,得到的值依次传给匿名函数,如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器。
>>> list(res) 
[4, 5]

提示:我们介绍map、filter、reduce只是为了带大家了解函数式编程的大致思想,
在实际开发中,我们完全可以用列表生成式或者生成器表达式来实现三者的功能。

七 面向过程编程

编程思想/范式

面向过程的编程思想:
核心是“过程”二字,过程既流程,指的是做事的步骤:先什么,再什么,后干什么
基于该思想编写程序就好比在设计一条流水线

优点:复杂的问题流程化、进而简单化
缺点:扩展性非常差

面向过程的编程思想应用场景解析:
1、不是所有的软件都需要频繁更迭:比如编写脚本
2、即便是一个软件需要频繁更迭,也并代表这个软件所有的组成部分都是需要一起更迭