13 Python函数进阶

一、迭代器

1.1 什么是迭代器

迭代器指的是迭代取值的工具,迭代是一个重复的过程,每次重复都是基于上一次的结果而继续的

单纯的重复并不是迭代(举例:更新换代)

1.2 为何要有迭代器

迭代器是用来迭代取值的工具,而涉及到吧多个值循环取出来的类型有:列表,字符串,元祖,字典,集合,文件

l = [1,2,3]
i = 0
while i<len(l):
	print(l[i])
	i+=1

上述迭代取值的方式只适用于有索引的数据类型:列表,字符串,元祖

为了解决基于索引迭代取值的局限性。python必须提供一种能够不依赖于索引的取值方式,这就是迭代器

1.3 可迭代对象

从语法形式上讲,内置有__iter__方法的对象都是可迭代对象,字符串、列表、元组、字典、集合、打开的文件都是可迭代对象

1.4 迭代器对象

调用obj.iter()方法返回的结果就是一个迭代器对象(Iterator)。迭代器对象是内置有iternext方法的对象,打开的文件本身就是一个迭代器对象,执行迭代器对象.iter()方法得到的仍然是迭代器本身,而执行迭代器.next()方法就会计算出迭代器中的下一个值。 迭代器是Python提供的一种统一的、不依赖于索引的迭代取值方式,只要存在多个“值”,无论序列类型还是非序列类型都可以按照迭代器的方式取值

d = {'a': 1, 'b': 2}
d_iter = iter(d)
count = 0
while count < len(d):
    print(next(d_iter))
    count += 1
print('%' * 20)
d_iter = iter(d)   # 解决办法,再来一次
count = 0
while count < len(d):
    print(next(d_iter))
    count += 1

可迭代对象与迭代器对象详解

可迭代对象(可以转换成迭代器的对象):
	内置有__iter__方法对象
	可迭代对象.__iter__():得到迭代器对象
迭代器对象:
	内置有__next__方法并且内置有__iter__方法的对象
	迭代器对象.__next__():得到迭代器的下一个值
	迭代器对象.__iter__():得到迭代器的本身,说白了调了和没调一个样
测试:
d = {'a':1,'b':2}
d_iter = iter(d)
print(d_iter is iter(d_iter))

可迭代对象:字符串,列表,元祖,字典,集合,文件对象
迭代器对象:文件对象

1.5 迭代器的优缺点

基于索引的迭代取值,所有迭代的状态都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存:

1.5.1 优点

  1. 为序列和非序列类型提供了一种统一的迭代取值方式
  2. 惰性计算:节省内存。迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的

1.5.3 缺点

  1. 除非取尽,否则无法获取迭代器的长度。无法获取长度(只有在next完毕才知道到底有几个值)
  2. 一次性的,只能往后走,不能往前退。只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值

二、 for循环原理

有了迭代器后,我们便可以不依赖索引迭代取值了,使用while循环的实现方式如下

goods=['mac','lenovo','acer','dell','sony']
i=iter(goods) #每次都需要重新获取一个迭代器对象
while True:
    try:
        print(next(i))
    except StopIteration: #捕捉异常终止循环
        break

for循环又称为迭代循环,in后可以跟任意可迭代对象,上述while循环可以简写为

goods=['mac','lenovo','acer','dell','sony']
for item in goods:   
    print(item)

for 循环在工作时,首先会调用可迭代对象goods内置的iter方法拿到一个迭代器对象,然后再调用该迭代器对象的next方法将取到的值赋给item,执行循环体完成一次循环,周而复始,直到捕捉StopIteration异常,结束迭代

for循环的工作原理

# for循环可以称之为迭代循环
d = {'a':1,'b':2}
for k in d:
	print(k)
	
# 1. d.__iter__()得到一个迭代器对象
# 2. 迭代器对象.__next__()拿到一个返回值,然后将该返回值赋值给k
# 3. 循环往复步骤2,直到抛出异常,for循环户捕捉异常然后结束循环

三、生成器

就是自定义的迭代器

def func():
	print('第一次')
	yield 1
	print('第二次')
	yield 2

g = func()
print(g)
# g 就是生成器,就是自定义的迭代器
# 会触发函数体代码的运行,然后遇到yield停下来
# 将yield后的值当做本次调用的结果返回
res = next(g)
print(res)

如何得到自定义的迭代器
在函数体内一旦存在yield关键字,调用函数并不会执行函数体代码
会返回一个生成器对象,生成器即自定义的迭代器

# 应用案例
def my_range(start, stop, step=1):
    while start < stop:
        yield start
        start += step


for x in my_range(1, 7, 2):
    print(x)

有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,
但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数
用来返回多次值

def dog(name):
    print('狗准备吃东西')
    while True:
        # x 拿到的是yield接收的值
        # yield可以接收到外部send传过来的数据并赋值给x
        x = yield
        print('狗哥%s吃了%s' % (name, x))

g = dog('旺旺')
# 针对表达式形式的yield,生成器对象必须事先被初始化一次,
# 让函数挂起在food=yield的位置,等待调用g.send()方法为函数体传值
# g.send(None)等同于next(g)
# next(g)
g.send(None)  # 等同于next(g)
g.send('一根骨头')

这里,最难理解的就是generator和函数的执行流程不一样
函数是顺序执行,遇到return语句或者最后一行函数语句就返回
而变成generator的函数,在每次调用next()的时候执行,
遇到yield语句暂停并返回数据到函数外,再次被next()调用时从上次返回的yield语句处继续执行

四、三元表达式

三元表达式是python为我们提供的一种简化代码的解决方案,语法如下

res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值
def max2(x,y):
    if x > y:
        return x
    else:
        return y

res = max2(1,2)

# 用三元表达式可以一行解决
x=1
y=2
res = x if x > y else y # 三元表达式

五、列表生成式

列表生成式是python为我们提供的一种简化代码的解决方案,用来快速生成列表

[expression for item1 in iterable1 if condition1]
或者:
[expression for item1 in iterable1]
l = ['muyi_nb', 'yimu_nb', 'lao_nb']
# 将所有小写字母转为大写
new_l = [x.upper() for x in l]
print(new_l)
# 取出所有的_nb
new_l = [x.split('_')[0] for x in l]
print(new_l)
new_l = [x.replace('_nb', '') for x in l if x=='yimu_nb']
print(new_l)
# 字典生成式
items = [('name','muyi'),('age',18),('sex','man')]
res = {k:v for k,v in items if k!='sex'}
print(res)

# 集合生成式
keys = ['name','age','sex']
set1 = {key for key in keys}
print(set1,type(set1))

六、生成器表达式

创建一个生成器对象有两种方式,一种是调用带yield关键字的函数,另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将[]换成()

(expression for item in iterable if condition)

对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象

>>> [x*x for x in range(3)]
[0, 1, 4]
>>> g=(x*x for x in range(3))
>>> g
<generator object <genexpr> at 0x101be0ba0>

对比列表生成式,生成器表达式的优点自然是节省内存(一次只产生一个值在内存中)

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g) #抛出异常StopIteration

如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成

with open('db.txt','rb') as f:
    nums=(len(line) for line in f)
    total_size=sum(nums) # 依次执行next(nums),然后累加到一起得到结果=

七、函数的递归

函数的递归调用:是函数嵌套调用的一种特殊形式

具体是指:在调用一个函数的额过程中又直接或者间接的调用本身

递归函数:就是函数内部自己调用自己

递归的本质就是循环

一段代码的循环运行的方案有两种

  1. while for 循环
  2. 递归的本质就是循环

递归最重要的就是找到出口(停止的条件),因为python会一直开辟内存空间

def f1(n):
    if n<5:
        print(n)
        n +=1
        f1(n)

f1(0)
# 递归最大的层级是1000层
import sys

# 查看层级
res = sys.getrecursionlimit()
print(res)
# 设置层级
sys.setrecursionlimit(1000)
# 虽然可以设置,但不建议去设置

扩展:在有些编程语言中,有尾递归优化,通过某种优化方式,在每进入进入下一层递归之前将之前的内存销毁,不会开辟更多内存空间,python没有

需要强调的一点:递归调用不应该无限的调用下去,必须在满足某种条件下结束递归调用

递归调用的两个阶段

  • 回溯:一层一层调用下去
  • 递推:满足某种结束条件,结束递归调用,然后一层一层返回
ata = [1, [2, [3, [4, [5]]]]]

# 第一步:
for x in data:
    if type(x) is list:
        # 如果是列表,应该再循环,在判断,即重新运行本身的代码
        pass
    else:
        print(x)

# 用递归
def f1(data):
    for x in data:
        if type(x) is list:
            # 如果是列表,应该再循环,在判断,即重新运行本身的代码
            f1(x)
        else:
            print(x)

f1(data)

八、面向过程编程思想/范式

面向就是心中一直思考的意思

核心是“过程”二字,过程即流程,指的是做事的步骤,先什么,再什么

基于该思想编写程序就好比在设计一条流水线

  • 优点:复杂的问题流程化,进而简单化
  • 缺点:扩展性非常差,(比如流水线,牵一发而动全身,一个要改,全部要改)

程序的可扩展性极差,因为一套流水线或者流程就是用来解决一个问题,就好比生产汽水的流水线无法生产汽车一样,即便是能,也得是大改,而且改一个组件,与其相关的组件可能都需要修改,这就造成了连锁反应,而且这一问题会随着程序规模的增大而变得越发的糟糕。

注意:

  1. 不是所有的软件都需要频繁更迭,比如编写脚本
  2. 即便是一个软件需要频繁更迭,也不代表这个软件所有的组成部分都需要一起更迭
面向过程的程序设计一般用于那些功能一旦实现之后就很少需要改变的场景, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程去实现是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护, 那还是用面向对象最为方便。

九、匿名函数

对比使用def关键字创建的是有名字的函数,使用lambda关键字创建则是没有名字的函数,即匿名函数,语法如下

lambda 参数1,参数2,...: expression
# 1、定义
lambda x,y,z:x+y+z

#等同于
def func(x,y,z):
    return x+y+z

# 2、调用
# 方式一:
res=(lambda x,y,z:x+y+z)(1,2,3)

# 方式二:
func=lambda x,y,z:x+y+z # “匿名”的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的
res=func(1,2,3)

匿名函数与有名函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放,所以匿名函数用于临时使用一次的场景,匿名函数通常与其他函数配合使用

十、 map、reduce、filter

函数map、reduce、filter都支持迭代器协议,用来处理可迭代对象,我们以一个可迭代对象array为例来介绍它们三个的用法

array=[1,2,3,4,5]

要求一:对array的每个元素做平方处理,可以使用map函数

map函数可以接收两个参数,一个是函数,另外一个是可迭代对象,具体用法如下

res=map(lambda x:x**2,array)
print(res)
print(list(res))

解析:map会依次迭代array,得到的值依次传给匿名函数(也可以是有名函数),而map函数得到的结果仍然是迭代器

要求二:对array进行合并操作,比如求和运算,这就用到了reduce函数

reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值

# reduce在python2中是内置函数,在python3中则被集成到模块functools中,需要导入才能使用
from functools import reduce

array=[1,2,3,4,5]
res=reduce(lambda x,y:x+y,array)
print(res)

解析:

  1. 没有初始值,reduce函数会先迭代一次array得到的值作为初始值,作为第一个值数传给x,然后继续迭代一次array得到的值作为第二个值传给y,运算的结果为3
  2. 将上一次reduce运算的结果作为第一个值传给x,然后迭代一次array得到的结果作为第二个值传给y,依次类推,知道迭代完array的所有元素,得到最终的结果15

也可以为reduce指定初始值

from functools import reduce

array=[1,2,3,4,5]
res = reduce(lambda x,y:x+y,array,100)
print(res)

要求三:对array进行过滤操作,这就用到了filter函数,比如过滤出大于3的元素

array=[1,2,3,4,5]
res = filter(lambda x: x > 3, array)
print(list(res))

解析:filter函数会依次迭代array,得到的值依次传给匿名函数,如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器

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

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值