python入门06——杂货铺(迭代器/生成器/pickle)

一、迭代器

迭代是Python最强大的功能之一,是访问集合元素的一种方式。

迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。

迭代器对象可以使用常规for语句进行遍历:

list = [1, 2, 3, 4]
it = iter(list)  # 创建迭代器对象
for i in it:  # 迭代器对象可以用常规的for语句进行遍历
    print(i, end=" ")
## 结果为1 2 3 4

也可以使用 next() 函数,next()函数是系统提供的一种内置方法,它的本质就是调用obj.__next__()

import sys  # 导入sys模块
list = [1, 2, 3, 4]
it = iter(list)  # 创建迭代器对象
while True:
    try:
        print(next(it))  # 不断输出list元素,该步骤等价于it.__next__()
    except StopIteration:  # StopIteration 异常用于标识迭代的完成
        sys.exit()
## 结果为1
##      2
##      3
##      4
1. 创建一个迭代器

把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__()__next__()
__iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。
__next__() 方法(Python 2 里是 next())会返回下一个迭代器对象。

# 创建一个返回数字的迭代器,初始值为 1,逐步递增 1:
class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    x = self.a
    self.a += 1
    return x
 
myclass = MyNumbers()
myiter = iter(myclass)  # 把一个类作为一个迭代器
 
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
## 结果为1
##      2
##      3
##      4
2. StopIteration异常

StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

# 在 5 次迭代后停止执行:
class MyNumbers:
    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        if self.a <= 5:
            x = self.a
            self.a += 1
            return x
        else:
            raise StopIteration  # 在5次循环后停止


myclass = MyNumbers()
myiter = iter(myclass)

for x in myiter:
    print(x)
## 结果为1
##      2
##      3
##      4
##      5
3. 迭代器协议

迭代器协议:对象必须提供一个__next__()方法,执行该方法要么返回迭代中的下一项,要么就引起StopIteration异常以终止迭代(只能往后走不能往前退)。
可迭代对象:实现了迭代器协议的对象。(如何实现:对象内部定义一个__iter__()方法)

可迭代对象执行obj.__iter__()得到的结果就是迭代器对象。
迭代器对象指的是即内置有__iter__()又内置有__next__()方法的对象

注意:迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象。

4. 强大的for循环

for循环的本质:循环所有对象,全都是使用迭代器协议。
for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的__iter__()方法将其转换成一个迭代器,然后用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了。
一个简单的for循环如下所示:

li = [1, 2, 3]
for i in li:  # i_1i = li.__iter__()  i_1i.__next__()
    print(i)

其实它的本质等同于以下实现逻辑:

iter_li = li.__iter__()
print(iter_li)  # <list_iterator object at 0x000002C2DD3E1E20> 是一个列表迭代器对象
# 迭代器对象遵循迭代器协议,所以可以采用迭代器对象的__next__()方法
print(iter_li.__next__())  # 1 
print(iter_li.__next__())  # 2
print(iter_li.__next__())  # 3

总结:for循环的工作原理:①执行in后对象的 li.iter()方法,得到一个迭代器对象iter_li;②执行next(iter_li),将得到的值赋值给i,然后执行循环体代码;③重复过程2,直到捕捉到异常StopIteration,结束循环。

字典类型for循环的实现逻辑与上述一致,或许你还能进一步理解为什么for循环默认循环字典的key值。

dic = {
    'key1': 10,
    'key2': 20,
    'key3': 30
}

iter_d = dic.__iter__()
print(iter_d)  # <dict_keyiterator object at 0x0000027419867270>
print(iter_d.__next__())  # key1
5. 迭代器的优缺点

优点:

  • 提供一种统一的、不依赖于索引的迭代方式
  • 惰性计算,节省内存(执行完一条obj.__next__()立即释放内存)

缺点:

  • 无法获取长度(只有在next完毕才知道到底有几个值)
  • 一次性的,只能往后走,不能往前退

二、生成器

在 Python 中,使用了 yield 的函数被称为生成器(generator)。生成器可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(而其他的数据类型需要调用自己内置的__iter__()方法),所以生成器就是可迭代对象。

def test():
    yield 1007

t = test()
print(t)  # <generator object test at 0x000001C6D0C906D0>

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

调用一个生成器函数,返回的是一个迭代器对象。
一个更直观的例子:

# 1、只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,并且不会执行函数内部代码
def func():
    print('====>first')
    yield 1
    print('====>second')
    yield 2
    print('====>third')
    yield 3
    print('====>end')

g = func()  # 此时g是一个生成器对象<generator object func at 0x0000028D67AD06D0>
print(g.__iter__())  # g可以使用迭代器的方法
print(g.__next__())

# 2、所以生成器就是迭代器,因此可以这么取值
print(next(g))  # 和上述print(g.__next__())等价
print(next(g))  # 此次执行结果从上次迭代的位置开始(迭代状态暂时保存),也就是说上面的first、second那些输出不会多次打印

运行结果如下所示:
在这里插入图片描述
yield总结:

把函数做成迭代器
对比return,可以返回多次值,可以挂起/保存函数的运行状态

1. 补充知识
① 三元表达式

h = '变量1' if a>b else '变量2' ——> 如果a>b的结果为真,h=“变量1”,如果为假,h=“变量2”。

name = input('姓名>>: ')
res = '美丽' if name == 'zhai' else '自信'
print(res)
# 姓名>>: zhai
# 美丽
② 列表解析

原本想要按照我们定义的格式生成一个列表需要做如下处理:

egg_list = []
for i in range(10):
    egg_list.append('鸡蛋%s' % i)

print(egg_list)  # ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']

若我们希望代码更加简洁,则可以直接在[ ]中按格式赋值。

li = ['鸡蛋%s' % i for i in range(10)]
print(li)  # ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']

我们也可以在列表解析过程中使用三元表达式以得到想要的输出,如下:

li = ['鸡蛋%s' % i for i in range(10) if i > 5]
print(li)  # ['鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
2. 生成器表达式

若生成了很多的鸡蛋放在列表中,对内存来说是个巨大的考验。可测试改为range(10000)会有啥效果。但是我们仍希望能成功读取较多的鸡蛋数,于是我们将上述式子改为一个生成器表达式,如下:

laomuji = ('鸡蛋%s' % i for i in range(10))  # 将[]改为()
print(laomuji)  # <generator object <genexpr> at 0x00000165CF7706D0>

这里可以理解为从之前的生一筐鸡蛋变成给你一只老母鸡,用的时候就下蛋。这也是生成器的特性。

  • 生成器表达式并不真正的创建数字列表,而是返回一个生成器对象,此对象在每次计算出一个条目后,把这个条目"产生"(yield)出来。
  • 生成器表达式使用了"惰性计算“或称作”延时求值"的机制。
  • 当序列过长并且每次只需要获取一个元素时,应该考虑生成器表达式而不是列表解析。
iter_t = (i for i in range(1000))
print(iter_t)
print(iter_t.__next__())  # 0
print(iter_t.__next__())  # 1
3. 例题&练习

以下实例使用 yield 实现斐波那契数列:

def fibonacci(n):  # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n):
            return
        yield a  # 暂停保存当前所有的运行信息
        a, b = b, a + b
        counter += 1
# a=0,b=1,counter=0 [save:a=0] a=1,b=1,counter=1
# [save:a=1] a=1,b=2,counter=2
# [save:a=1] a=2,b=3,counter=3
# [save:a=2] a=3,b=5,counter=4 exit

f = fibonacci(3)  # f 是一个迭代器,由生成器返回生成

while True:
    try:
        print(next(f), end=" ")
    except StopIteration:
        sys.exit()
## 结果为 0 1 1 2

一个例子帮助理解yield:

def product():
    for i in range(100):
        print('正在生产包子……')
        yield('包子%s' % i)
        print('开始卖包子……\n--------------------')

pro = product()
print(pro.__next__())
print(pro.__next__())
print(pro.__next__())

在这里插入图片描述

三、pickle模块

模块 pickle 实现了对一个 Python 对象结构的二进制序列化和反序列化。

1. pickle提供的函数对

pickle 模块提供了以下函数对:

  1. dumps(object) 返回一个字符串,它包含一个 pickle 格式的对象;
  2. loads(string) 返回包含在 pickle字符串中的对象;
  3. dump(object, file)将对象写到文件,这个文件可以是实际的物理文件,但也可以是任何类似于文件的对象,这个对象具有 write()方法,可以接受单个的字符串参数;
  4. load(file) 返回包含在 pickle 文件中的对象。
import pickle

定义一个列表类型,并将列表中的数据存入my_list.pkl文件中。

my_list = [123, 3.14, 'zhang', ['another list']]
pickle_file = open('my_list.pkl', 'wb')  # b表示字节类型
pickle.dump(my_list, pickle_file)  # 将pickle_file写入到文件my_list中
pickle_file.close()  # 与正常文件类似,需要关闭才能写入

执行后,与当前python文件相同的文件路径下会出现一个名为my_list.pkl的文件。
在这里插入图片描述
我们可以读取该文件中保存的数据:

pickle_file = open('my_list.pkl', 'rb')  # 以只读的方式打开该字节文件
my_list2 = pickle.load(pickle_file)  # 返回包含在pickle_file文件中的对象
print(my_list2)  # 输出对象
# 输出结果为[123, 3.14, 'zhang', ['another list']]
2. pickle的实际应用

【例】将查询城市天气脚本中的city数据用pickle存起来。
weather_search.py脚本如下:

import urllib.request
import json

# 建立城市字典
city = {
    '北京': '101010100',
    '海淀': '101010200',
    '朝阳': '101010300',
    '顺义': '101010400',
    '怀柔': '101010500',
    '通州': '101010600',
    '昌平': '101010700',
    '延庆': '101010800',
    '丰台': '101010900',
    '石景山': '1010101100',
    '大兴': '10101011200',
    '密云': '10101011300',
    '门头沟': '10101011400',
    '平谷': '10101011500',
    '八达岭': '10101011600',
    '佛爷顶': '10101011700',
    '汤河口': '10101011800',
    '密云上甸子': '10101011900',
    '斋堂': '10101012000',
    '霞云岭': '10101012100',
}
password = input('请输入城市:')
name1 = city[password]
File1 = urllib.request.urlopen('http://m.weather.com.cn/data/' + name1 + '.html')
weatherHTML = File1.read().decode('utf-8')  # 读入打开的url
weatherJSON = json.JSONDecoder().decode(weatherHTML)  # 创建json
weatherInfo = weatherJSON[' weatherinfo']
# 打印信息
print('城市: ', weatherInfo['city'])
print('时间: ', weatherInfo['date_ y'])
print('24小时天 气: ')
print('温度: ', weatherInfo['temp1'])
print('天气: ', weatherInfo['weather1'])
print('风速: ', weatherInfo['wind1'])
print('紫外线: ', weatherInfo['index_ _uv'])
print('穿衣指数: ', weatherInfo['index_ _d'])
print('48小时天气: ')
print('温度:', weatherInfo['temp2'])
print('天气: ', weatherInfo['weather2'])
print('风速: ', weatherInfo['wind2'])
print('紫外线: ', weatherInfo['index48_ _uv'])
print('穿衣指数: ', weatherInfo[' index48_ _d'])
print('72小时天气: ')
print('温度: ', weatherInfo[' temp3'])
print('天气: ', weatherInfo['weather3'])
print('风速: ', weatherInfo[' wind3'])
input('按任意键退出……')

将city中的城市信息存到pickle中:

import pickle

pickle_file = open('city_data.pkl', 'wb')
pickle.dump(city, pickle_file)  # 将city中的数据对象写入pickle_file中
pickle_file.close()

原先的脚本就可以更加清晰明了,代码可读性也有所增强。使用pickle后的脚本代码如下:

import urllib.request
import json
import pickle

#将本来的city数据用打开pkl文件的方式读入
pickle_file = open('city_data.pkl', 'rb')
city = pickle.load(pickle_file)

password = input('请输入城市:')
name1 = city[password]
File1 = urllib.request.urlopen('http://m.weather.com.cn/data/' + name1 + '.html')
weatherHTML = File1.read().decode('utf-8')  # 读入打开的url
weatherJSON = json.JSONDecoder().decode(weatherHTML)  # 创建json
weatherInfo = weatherJSON[' weatherinfo']
# 打印信息
print('城市: ', weatherInfo['city'])
print('时间: ', weatherInfo['date_ y'])
print('24小时天 气: ')
print('温度: ', weatherInfo['temp1'])
print('天气: ', weatherInfo['weather1'])
print('风速: ', weatherInfo['wind1'])
print('紫外线: ', weatherInfo['index_ _uv'])
print('穿衣指数: ', weatherInfo['index_ _d'])
print('48小时天气: ')
print('温度:', weatherInfo['temp2'])
print('天气: ', weatherInfo['weather2'])
print('风速: ', weatherInfo['wind2'])
print('紫外线: ', weatherInfo['index48_ _uv'])
print('穿衣指数: ', weatherInfo[' index48_ _d'])
print('72小时天气: ')
print('温度: ', weatherInfo[' temp3'])
print('天气: ', weatherInfo['weather3'])
print('风速: ', weatherInfo[' wind3'])
input('按任意键退出……')
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值