python迭代器生成器

正文:

  • 容器
  • 迭代器
  • 可迭代对象
  • 生成器

容器:

容器就是存储某些元素的统称,特性:可以用in 或 not in 来判断一个元素存在/不存在于一个容器内。

strlisttuplesetdict 都可以通过 in 或 not in 来判断一个元素是否在存在/不存在这个实例中,所以这些类型都可以称作「容器」

print('x' in 'xyz')  # 字符串
print(1 in [1, 2, 3])       # 列表
print(2 not in (1, 2, 3))  # 元组
print('x' not in {'a', 'b', 'c'}) # 集合
print('a' in {'a': 1, 'b': 2}) # 字典

之所以可以使用 in 或 not in 来判断「容器」是因为实现了 __contains__ 方法。一个类只要实现了 __contains__ 方法,那么它就是一个「容器」。

复制代码
class A:

    def __init__(self):
        self.items = [1, 2]

    def __contains__(self, item):
        return item in self.items  

a = A()
print(1 in a)   # True
print(3 in a)   # False
复制代码

容器另一个常用的功能是:输出容器内的所有元素。例如执行 for x in [1, 2, 3],(in关键字后面的必须是可迭代对象iterable),就可以迭代出容器内的所有元素。

 

迭代器:

对于for loop而言,有两个核心概念必不可少:可迭代对象(iterable)、迭代器(iterator)

可迭代对象iterable更像是一个数据保存者一个容器,它可以没有状态 可以不知道迭代器iterator数到哪了,但是它必须要产生一个迭代器iterator

迭代器(iterator)是一个表示数据流的对象,可以使用next函数不断的从这个对象里面获取新的数据,iterator必须要有__next__这个method,这个method保证它被next()作用的时候可以返回下一个iterable里面的值,iterator最核心的部分是要有这个__next__函数

几个常见可迭代对象:

复制代码
lst = [1,3,5]
for i in lst:
    print(i)
d = {'a':1,'b':2}
for i in d:
    print(i)
with open('my.txt') as f:
    for i in f:
        print(i)
复制代码

一个对象要想使用 for 的方式迭代出容器内的所有数据,需要实现以下 2 个方法:

  • __iter__:这个方法返回对象本身,即 self,让每个迭代器也是可迭代的
  • __next__:这个方法每次返回迭代的值,在没有可迭代元素时,抛出 StopIteration 异常
复制代码
#!/usr/bin/python
# -*- coding: UTF-8 -*-
class NodeIter:  # 迭代器iterator
    def __init__(self, node):
        self.curr_node = node

    # iterator class 核心部分要有__next__函数,每次调用这个函数的时候,两种情况,如果这个iterator到头了需要raise一个
    # StopIteration,如果还有数据的话把这个数据return回去,
    def __next__(self):          if self.curr_node is None:
            raise StopIteration
        node, self.curr_node = self.curr_node, self.curr_node.next
        return node

    def __iter__(self):
        return self


class Node:  # 可迭代对象iterable:凡是可以返回一个迭代器的对象,都称为可迭代对象
    def __init__(self, name):
        self.name = name
        self.next = None

    def __iter__(self):  # __iter__方法返回一个迭代器
        return NodeIter(self)

node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")
node1.next = node2
node2.next = node3

for node in node1:
    print(node.name)
复制代码

其实在执行 for loop循环时,实际执行流程是这样的:

  • for i in iterableA  会对iterableA进行一次iter()这么一个操作,相当于执行 iter(terableA),实际而言,for loop首先做了一个从iterable里面拿到iterator的操作,在进行forloop之前这个iterable会先被取一个iter的值,然后这个for loop是根据这个iterator来进行操作的
  • iterator每次迭代时会执行一次 __next__ 方法,返回一个值,这就是iterator为啥要有__next__这个方法的原因,这个方法保证它被next()作用的时候可以返回下一个iterable里面的值
  • 如果没有可迭代的数据,抛出 StopIteration 异常,for 会停止迭代
# for loop过程可以想象成下面这个样子
lst = [1,3,5] it = iter(lst) for i in it:  # 实际并不是这样子,因为执行for loop操作的时候还会对这个it进行一次iter()这么一个操作 print(i)

从实现上来看,一个iterable,要么就有这个__iter__这个method,要不然它就需要是一个序列sequence,然后有__getitem__这个method,这两者都是为了保证它可以在iter()这个函数的作用下,返回一个iterator

 可迭代对象

凡是可以返回一个「迭代器」的对象,都可以称之为「可迭代对象」。即__iter__ 方法返回一个迭代器,那么这个对象就是「可迭代对象」。

上文中的class Node不是一个「迭代器」,因为它只了实现 __iter__,没有实现 __next__,由于class Node的 __iter__ 返回了NodeIter的实例,而NodeIter是一个迭代器,所以Node的实例是一个「可迭代对象」,换句话说,Node把迭代细节交给了 NodeIter

生成器

「生成器」generator是一个特殊的「迭代器」,并且它也是一个「可迭代对象」

 2 种方式可以创建一个生成器:

  • 生成器表达式
  • 生成器函数

 生成器表达式创建生成器

g = (i for i in range(5))  # 创建生成器,类型是generator
print(type(g))  # <class 'generator'>
print(g)  # 生成器就是一个迭代器
print(iter(g))
for i in g:    # 生成器也是一个可迭代对象
    print(i)

生成器函数创建生成器

在这里我们要区分什么是生成器函数、生成器对象,当函数定义里面有yield的这个关键词的时候,它就不会把这个函数当成一个普通函数来处理,python会给这个函数打上一个标签,然后说这是一个生成器函数,调用生成器函数,会生成一个生成器对象

复制代码
def gen(num):   # 生成器函数
    while num > 0:
        yield num
        num -= 1
    return  # 在生成器函数里面,return等价于raise Stoplteration

# g叫做生成器对象
g = gen(5)  # 调用生成器函数不会返回值,而会返回一个生成器对象,保存到g里
print(g)   # <generator object gen at 0x10bb46f50>  
print(type(g)) # <type 'generator'>
# 生成器也可以使用next函数,用next将第一个拿出来
first = next(g)

# 迭代这个生成器
for i in g:
    print(i)
复制代码

生成器高级用法send

send就是在这个生成器yield之后,把这个yield的什么东西变成一个值,然后这个值还可以继续赋给这个生成器函数里面的其他变量,send函数只是给一个机制让你可以去和你的generator做沟通和交流,可以改变它的一些状态。如果yield num没有赋值给一个变量的时候,send任何东西包括send None跟用next就是一样的等价的

复制代码
def gen(num):
    while num > 0:
        tmp = yield num  # 在这个生成器yield之后,send可以理解为yield num进行了一次赋值,赋值成了send进来的这个值
        if tmp is not None:
            num = tmp
        num -= 1

g = gen(5)

first = next(g)  # 等价于first = g.send(None),tmp=None直接跳到num-=1
print(f"first:{first}")

# num先被赋值成了10,然后又减了1,所以下一次yield出来是9 print(f
"send:{g.send(10)}") for i in g: print(i)
复制代码

 

 

 

 

 

 参考学习:https://zhuanlan.zhihu.com/p/319402935

posted @   天才九少  阅读(23)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示