一 可迭代对象,迭代器和生成器

 

可迭代对象

迭代器

生成器

定义

可迭代 (Iterable):指某个容器类型数据可被for循环遍历获取内部所有成员。那么这个类型数据就称之为可迭代。

对象:也叫实例(Instance),可以理解为是一个记录和管理数据的容器,它的成员是属性(变量,属性的值就是数据)和方法(method, 就是操作该对象以及对象内部数据的函数)。

从使用角度来说,能被for循环遍历输出内部每一个成员的都是可迭代对象。

从语法形式来说,能调用__iter__方法的数据对象就是可迭代对象。

迭代器:增强版的可迭代对象。实现了迭代器协议的对象。即任何一个对象,只要内部实现了__iter__()__next__()就是一个迭代器(iterator)。

->迭代器一定是可迭代对象,可迭代对象不一定是迭代器

生成器是一个特殊迭代器,也是一个特殊函数,内部可以有1个或多个yield关键。
因为生成器是一个特殊函数,所以也具备函数的特点,有参数,有返回值等等。

实例

判断一个数据是否是可迭代对象

set_data = {'a',2,'c','b'}
for i in setvar:
print(i)

# dir() 获取对象所有内置方法
data = dir(set_data)
print(data)
print('__iter__' in data) # True

set_data = {"A", "B", "D", 100}
# data = iter(set_data)    # 方法一. 可迭代对象转化迭代器
data = set_data.__iter__() # 方法二
print(data) #<set_iterator object at 0x107826040>
res = data.__next__()
print(res)  #B
res = data.__next__()
print(res)  #A
res = next(data) # next(data),就是 data.__next(),本质上没有区别,仅仅写法不同。
print(res)  #100
res = next(data)
print(res)  #D
res = next(data)
print(res) #StopIteration

 # 迭代器中所有值都被提取完成后,再次取值,python会以抛出一个StopIteration异常告诉我们,没有值了。这并不代表错误发生,而是一种迭代完成的标志,防止出现无限循环。

#迭代器的特性就是能够调用__next__方法依次计算出迭代器中的每一个值 ,然后每一个值重复的放在同一个内存空间

"""生成器的声明"""
# 生成器函数
def gen():
    yield 1
    yield 3
    yield "abc"

"""生成器的基本使用"""
# g1 = gen() # 生成器函数的执行结果,是一个生成器对象(generator object)。
# print(g1)  # <generator object gen at 0x0000021C18CC9510>
#
# # 对生成器的使用,主要可以依靠for循环和next()操作,开发中基本不适用next,所以重点是for循环
# for item in g1:
#     print(item)

# 循环的结果
# 1
# 3
# abc
# for item in g1:
#     print("ssss")
#     print(item)
因为这个生成器会记录和保存程序执行的状态,因此,当生成器结束了以后,就不会再逆向取值,所以下面的代码拿不到数据的,因此上面已经取完了

# 每次调用生成器函数,都会产生一个新的生成器对象。
g2 = gen()
for item in g2:
print(item)

比较

优点:

  1. 访问速度快。因此一切数据都在内存中
  2. 访问方式灵活,可多次、重复、任意选择范围访问。
  3. 内置方法和函数比较多。

缺点:

  1. 耗费内存,数据一次性存储在内存空间。
  2. 取值过于灵活,不限制方向,有时候不当的操作会出现异常(例如:index out of range)

优点:

  1. 惰性序列,惰性执行,挤牙膏,每次取一条数据放同一个内存中,直到取值完毕
  2. 节省内存空间,可用于遍历读取大文件,海量数据

缺点:

  1. 访问速度慢。取值,赋值,取值,赋值,不断重复,所以会比寻常的可迭代对象直接从内存中取值肯定要慢。
  2. 内置操作比较少。经过iter包装,所以很多的操作不能直接调用。
  3. 访问方式死板,方向不可逆,不能反复,只能向下取值。

同迭代器。生成器中yield关键字的特征是暂停函数执行,所以可以让多个任务程序(函数)交替执行。这也是网络编程与并发编程里面的协程的实现基本原理,和异步编程里面协程基础

关系

可迭代对象包含了迭代器和生成器
迭代器包含了生成器

注:

1 for循环的本质

x = 可迭代对象
# for循环的形式:
for item in x:
    print(item)

python的可迭代对象 和 不可迭代对象 可迭代对象 迭代器_迭代

 

for 循环首先会调用可迭代对象data内的__iter__方法返回一个迭代器,然后再调用这个迭代器的__next__方法将取到的值赋给item,即for后面定义的变量item(item经过每次for循环调用next()时都会赋值了一遍)。循环一次,调用一次next方法,直至遇到StopIteration异常,for循环内部捕获并处理该异常后结束迭代过程。

区别于while循环模拟遍历过程(while比for循环要单调一点,仅仅是重复)

data = [10,20,30,40]
# 1. 先执行iter方法,把可迭代对象转换成迭代器
iter_data = iter(data)
while True:
    try:     #异常处理语句,可以让我们识别代码异常而不会导致代码因为异常出错而终止
        # 2. 每次循环,通过next取值,付给item
        item = next(iter_data)
        print(item)
    except StopIteration:
        break # 此处,退出循环

2 查看一个对象是否是可迭代对象或迭代器

from collections.abc import Iterable, Iterator

data = [1, 2, 3, 4]
print(isinstance(data, Iterable))  # True       # 查看是不是可迭代对象
print(isinstance(data, Iterator))  # False      # 查看是不是迭代器
# 在 python中容器数据类型都是可迭代对象,但是并非迭代器,那么我们就可以进行装换。[高阶函数(filter, map..)的返回值是一个迭代器]
# 方式1: print(isinstance(data.__iter__(), Iterator)) # True # 方式2: print(isinstance(iter(data), Iterator)) # True #isinstance 是面向对象里面的内容,表示判断当前变量是否属于某个类型。区别于type,用于基本函数类型

3 生成器实例补充

"""
next 使用生成器过程中,仅仅只能接受yield暂停时返回的数据
send 使用生成器过程中,不仅可以接受yield暂停时返回的数据,还可以在send()的小括号把数据从外界传递到生成器函数内部(传参)给上一个yield(也就是暂停出)接收到
"""
1 def gen2():
    key = 0
    print(">>>>> 嘟嘟,开车了")
    while True:
        food = yield "第%s次" % key
        print(f"内部接收到了{food}")
        key += 1
# 判断当前文件是否作为主程序入口来运行,
# 如果__name__魔术变量的值为"__main__"则表示当前文件是主执行文件
# 如果__name__魔术变量的值为文件名,则表示当前文件不是主执行文件
if __name__ == "__main__":
g2 = gen2()
# 在循环之前,先执行一遍send的作用是为了预激活生成器,
# 让生成器内部执行到第一个yield位置,否则代码没有暂停的话,就无法通过send传递数据给内部的yield
ret = g2.send(None)
print(ret)
for item in ["苹果","芒果"]:
    res = g2.send(item)
    print(f"res={res}")
输出:
嘟嘟,开车了
第0次
内部接收到了苹果
res=第1次
内部接收到了芒果
res=第2次
2 yield from可以将一个可迭代对象变成一个迭代器返回,也可以用于多个生成器之间进行嵌套调用
def gen1():
   a = 0
   while True:
      # print("+++++++")
      a = yield a**2

def gen2(gen):
   # a = 0
   # while True:
   #  # print("+++++++")
   #  a = yield a ** 2
   yield from gen # 相当于一次性声明了多个yield

if __name__ == '__main__':
   g1 = gen1()
   g2 = gen2(g1)
   g2.send(None)#在一个生成器函数未启动之前,是不能传递数值进去。必须先传递一个None进去或者调用一次next()方法,才能进行传值操作
   for i in range(5):
      # print(">>>> %s" % i)
      print(g2.send(i))
输出:0 1 4 6 9 16
3 用生成器实现斐波那契
def fib(max):
    n, a, b = 0, 0, 1  # n表示当前第几项,a表示第一项的值,b表示第二项的值

    while n <= max:
        yield a
        a, b = b, a + b
        n = n + 1

g1 = fib(10)
for i in g1:#生成器为特殊的迭代器,可遍历可调用next方法
    print(i)#输出a的值
# 0 1 1 2 3 5 8 13

二 推导式

推导式,实际上就是一种代码简写方式。是通过一行代码完成一个或多个循环和判断,并遍历出一系列数据的编写代码方式

语法:

成员 for 循环 ... if 判断 ...

推导式种类

其中,列表和字典最常用
# 1. 列表推导式,结果是一个列表
[item for item in Iterable]
# 2. 字典推导式,结果是一个字典
{a:b for a,b in iterable.items()}
# 3. 集合推导式,结果是一个集合
{item for item in Iterable}
# 4. 生成器表达式,结果是一个生成器
(item for item in Iterable)

实例1 列"""

推导式里面可以存在1-多个for循环,也可以有0-多个判断,
但是判断和循环需要根据代码嵌套顺序来缩写
"""
"""1. 找出1~10之间的偶数"""
# ret = []
# for num in range(1, 11):
#     if num % 2 == 0:
#         ret.append(num)
# print(ret)

# 上面的这种结构代码就可以使用推导式进行简写
# ret = [num for num in range(1, 11) if num % 2 == 0]
# print(ret)
2 字典推导式

"""1. 统计列表中各个字符出现的次数"""
# data = ["A", "B", "C", "A", "C", "A"]
# # ret = {}
# # for item in data:
# #     ret[item] = data.count(item)
#
# 上面的这种结构代码就可以使用推导式进行简写
# ret = {item: data.count(item) for item in data}
# print(ret)
3 集合推导式

"""1. 计算列表中每个值的平方,自带去重功能"""
# data = [1, -1, 2, -3, 3]
# # ret = set()
# # for num in data:
# #     ret.add(num**2)
# 上面的这种结构代码就可以使用推导式进行简写
# ret = {num**2 for num in data}
# print(ret)
4 生成器推导式 

获取1-10整数的平方

"""上面的代码实际上是以下代码的简写"""
def gen():
    for i in range(1, 11):
        yield i**2

n = gen()
for i in n:
    print(i)# 上面的这种结构代码就可以使用推导式进行简写
# ret = (i**2 for i in range(1, 11))
# print(ret)