自定义迭代器
一 、生成器与yield
'''
我们得到一个迭代器通常都是调用可迭代对象的__iter__方法 ,例如 list.iter() 得到一个迭代器,
但是当list很大时候,就违背了python的初衷,假设我们要创造一个包含1000w个值的可迭代对象,总不可能先把
这1000w个值放入列表,然后调用__iter__() 方法吧,太占内存 ,必须得提供一种机制打破python内置的产生
迭代器的方式 ,所以自定义迭代器应运而生,即生成器。
在Python中, 一边循环一边计算的机制, 称为生成器:generator, 若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象
'''
自定义range函数
def my_range(start,stop,step=1): # 自定义range函数
print('start...')
while start < stop:
yield start
start+=step
print('end...')
g=my_range(0,3)
print(g)
<generator object my_range at 0x01E2A808>
如何得到自定义的迭代器:
在函数内一旦存在yield关键字,调用函数并不会执行函数体代码
会返回一个生成器对象,生成器即自定义的迭代器
"""
如果函数体代码中含有多个yield关键字 执行一次__next__返回后面的值并且让代码停留在yield位置
再次执行__next__基于上次的位置继续往后执行到下一个yield关键字处
如果没有了 再执行也会报错 StopIteration
"""
生成器本身就是一个迭代器
print(g.__iter__) # <method-wrapper '__iter__' of generator object at 0x01AFB878>
print(g.__next__) # <method-wrapper '__next__' of generator object at 0x01AFB878>
'''生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器'''
用next(生成器)触发生成器所对应函数的执行
print(next(g)) # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
start...
0
print(next(g)) # 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
1
print(next(g)) # 周而复始...
2
print(next(g)) # 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代
end...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
既然生成器对象属于迭代器,那么必然可以使用for循环迭代,如下:
for i in g:
print(i)
执行结果:
start...
0
1
2
end...
'''
有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于
return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值
'''
yield关键字作用
yield最简单的解释
yield可以理解为一个return操作,但是和return又有很大的区别,执行完return,当前函数就终止了,
函数内部的所有数据,所占的内存空间,全部都没有了。而yield在返回数据的同时,还保存了当前的执行
内容,当你再一次调用这个函数时,他会找到你在此函数中的yield关键字,然后从yield的下一句开始执行。
例一
def num_print():
print('yield_1')
yield 'yield_1 return'
print('yield_2')
yield 'yield_2 return'
print('yield_3')
yield 'yield_3 return'
print('yield_4')
yield 'yield_4 return'
for i in num_print():
print('i:',i)
执行结果:
yield_1
i: yield_1 return
yield_2
i: yield_2 return
yield_3
i: yield_3 return
yield_4
i: yield_4 return
'''
上边在循环处一直在调用函数,可以看出每次调用函数,他会执行到yield进行返回,然后下一次调用时
他会从上一次执行yield的后边继续执行
'''
例二(可以接收外界的传值(了解))
def play():
while True:
game = yield
print(f'玩{game}')
res = play()
res.__next__()
res.send('LOL')
执行结果:
玩LOL
生成器表达式
语法
(返回值 for 元素 in 可迭代对象 if 条件)
返回一个生成器,生成器也是一个对象,属于中间值
g = (i ** 2 for i in range(5)) # 将使用生成器表达式得到的生成器对象赋给一个变量g
print(g)
'''输出:
<generator object <genexpr> at 0x109835b10>
'''
print(g.__next__())
"""生成器内部的代码只有在调用__next__迭代取值的时候才会执行"""
主要功能是,代码优化,节省存储空间。
生成器表达式和列表生成式的区别
模块
简介
在Python中有一个概念叫做模块(module),这个和C语言中的头文件以及Java中的包很类似,比如在
Python中要调用sqrt函数,必须用import关键字引入math这个模块
说的通俗点:模块就好比是工具包,要想使用这个工具包中的工具(就好比函数),就需要导入这个模块
模块的来源
目前有3个来源
1.内置的模块
无需下载 解释器自带 直接导入使用即可
2.自定义模块
自己写的代码 封装成模块 自己用或者发布到网上供别人使用
3.第三方模块
别人写的发布到网上的 可以下载使用的模块(很多牛逼的模块都是第三方)
导入模块的方法
第一种:import
在Python中用关键字import来引入某个模块,比如要引用模块math,就可以在文件最开始的地方用
import math来引入。
当解释器遇到import语句,如果模块在当前的搜索路径就会被导入。
在调用math模块中的函数时,必须这样引用:
模块名.函数名
这种方式必须加上模块名调用,因为可能存在这样一种情况:在多个模块中含有相同名称的函数,此时如
果只是通过函数名来调用,解释器无法知道到底要调用哪个函数。所以如果像上述这样引入模块的时候,调用
函数必须加上模块名。
有时候我们只需要用到模块中的某个函数,只需要引入该函数即可,此时可以用下面方法实现:
from模块名import函数名1,函数名2....
'''
通过这种方式引入的时候,调用函数时只能给出函数名,不能给出模块名,但是当两个模块中含有相同
名称函数的时候,后面一次引入会覆盖前一次引入。也就是说假如模块A中有函数function( ),在模块B中也
有函数function( ),如果引入A中的function在先、B中的function在后,那么当调用function函数的时
候,是去执行模块B中的function函数。
'''
如果想一次性引入math中所有的东西,还可以通过from math import *来实现
第二种:from…import
Python的from语句让你从模块中导入一个指定的部分到当前命名空间中
语法如下:
from 模块名 import name,func1,func2(需要导入的函数或者变量)
例如,要导入模块fib的fibonacci函数,使用如下语句:
from fib import fibonacci
注意:
不会把整个fib模块导入到当前的命名空间中,它只会将fib里的fibonacci单个引入
"""
1.重复导入也只会导入一次
2.使用模块名称空间中的名字不需要加模块名前缀 直接使用即可
3.但是from...import的句式会产生名字冲突的问题
在使用的时候 一定要避免名字冲突
4.使用from...import的句式 只能使用import后面出现的名字
from...import...可以简单的翻译成中文
从...里面拿...来用 没有提到的都不能用 指名道姓
"""
补充
导入模块起别名,导入模块是对模块进行重命名,也就是给模块起一个别名。
import 模块名 as mod
'''
在后续使用时就可以使用这个别名
比如模块名或者变量名很复杂 可以起别名简写
'''
from 模块名 import *
'''
*表示md里面所有的名字 from...import的句 式也可以导入所有的名字
如果模块文件中使用了__all__限制可以使用的名字 那么*号就会失效 依据__all__后面列举的名字
'''
图文介绍两种导包