Iterable:一个容易被忽视的Python编码细节
Type hints
近年来,越来越多的 Python 开发者愿意为变量声明类型了,变化非常明显。
def add(left, right):
return left + right
from typing import TypeVar, Union
T = TypeVar('T', int, float)
def add_typed(left: T, right: T) -> T:
return left + right
虽然 type hints 并不会在运行时进行类型检查,但它们足以让 IDE 在运行前就报出不少类型风险,也让阅读代码的人有了更多思考空间,不至于迷失在“这到底是什么类型”的疑惑中。
Iterator 和 Iterable 的问题
Python 对 Iterator 的要求只有一条:
An iterator object implements
__next__, which is expected to return the next element of the iterable object that returned it, and to raise aStopIterationexception when no more elements are available.一个迭代器对象需要实现
__next__方法,该方法应当返回其所属可迭代对象的下一个元素;当没有更多元素可返回时,应抛出StopIteration异常。-- Python Wiki
Python 对 Iterable 的要求也很简单:
An iterable object is an object that implements
__iter__, which is expected to return an iterator object.一个可迭代对象是实现了
__iter__方法的对象,该方法应当返回一个迭代器对象。-- Python Wiki
这两年我还写了不少 Rust。在 Rust 里,Python 的 Iterator 对应 std::iter::Iterator,Iterable 则对应 std::iter::IntoIterator。正是有了写 Rust 的经验,我才能一眼看出下面代码的问题:
def stream(
self,
*,
# ...
tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN,
# ...
) -> AsyncChatCompletionStreamManager[ResponseFormatT]:
# ...
_validate_input_tools(tools)
# ...
api_request = self.create(
...
tools=tools,
...
)
return AsyncChatCompletionStreamManager(
...
input_tools=tools,
)
这段代码节选自 OpenAI 的 Python 接口 completions.py。问题出现在 #1129 这个 PR。
作为 Rust 选手的直觉告诉我,一个 Iterable 在这里被用到了三次,这就要求这个 Iterable 在多次迭代时本身状态不可变,否则无法保证三次迭代的结果一致。
通俗点说:这个 Iterable 必须保证多次调用 __iter__ 返回的迭代器,其迭代结果是一致的。
验证一下。对于普通容器当然没问题,但对于生成器就大有问题。生成器作为迭代器,其状态是会变的,所以三次调用的结果无法保证一致。
from typing import Iterable, List
def generate_from_list(lst: List[int]):
for item in lst:
yield item
# 容器可以多次迭代,产生相同结果
iterable0 = [1 ,2 ,3]
assert list(iterable0) == list(iterable0) == [1, 2, 3]
# 生成器迭代器只能迭代一次
iterable1 = generate_from_list([1, 2, 3])
assert list(iterable1) == [1, 2, 3]
assert list(iterable1) == []
# 容器外套一层map并不能保持容器可以多次迭代的特征,只能迭代一次
iterable2 = map(lambda x: x + 1, [1, 2, 3])
assert list(iterable2) == [2, 3, 4]
assert list(iterable2) == []
# 生成器额迭代器外套一层map自不必多说,只能迭代一次
iterable3 = map(lambda x: x + 1, generate_from_list([1, 2, 3]))
assert list(iterable3) == [2, 3, 4]
assert list(iterable3) == []
运行结果:
反思
我不反思,我一眼就看出这个问题了,我为啥要反思。但需要反思的人还真不少。
写代码时一定要清楚,自己进行的每一个操作对前置操作有什么依赖,是否修改了某个状态,这个状态是否应该由这个操作修改。如果不确定,就必须按照最保守的策略来。
还是以 Iterable 为例,拿到一个可迭代对象后,如果不确定它是不是容器、能否多次迭代且结果一致,那就只允许自己对它做一次迭代。如果因为种种原因不得不多次迭代,那就把它转成 list,再在这个 list 上反复迭代。
#1129 的错误略有不同,它不是在写新代码时出的问题,而是在放宽已有代码的限制时,只考虑了容器的情况,没考虑到其他典型可迭代对象(生成器、map 等)。这个 PR 合并一年多了,似乎还没人踩坑,这恰恰说明大多数场景下传入的就是容器(List),原 PR 放宽约束意义不大,反而引入了潜在风险。
#1606 的错误则更直接。一个 validate_input_tools 函数本不该对输入数据做任何更改,但根据刚才的讨论,这里有潜在的对 Iterable 状态的修改,所以这个函数的类型标注就应该只接受容器,或者其他能表示不可变可迭代对象的类型。
总结
总之,Iterable 虽然是 Python 类型标注中常见的一个概念,但它的“可多次迭代且结果一致”这一点很容易被忽视。写代码时,尤其是在类型标注、接口设计和数据处理时,一定要明确区分“容器型可迭代对象”和“一次性可迭代对象”,避免踩坑。遇到不确定的情况,最保险的做法就是把它转成 list,这样既保证了多次迭代的一致性,也让代码的行为更加可控和易于理解。
(感觉这个还是不要做面试题了)
Iterable:一个容易被忽视的Python编码细节的更多相关文章
- 探索 Python、机器学习和 NLTK 库 开发一个应用程序,使用 Python、NLTK 和机器学习对 RSS 提要进行分类
挑战:使用机器学习对 RSS 提要进行分类 最近,我接到一项任务,要求为客户创建一个 RSS 提要分类子系统.目标是读取几十个甚至几百个 RSS 提要,将它们的许多文章自动分类到几十个预定义的主题领域 ...
- (转)PEP 8——Python编码风格指南
PEP 8——Python编码风格指南标签(空格分隔): Python PEP8 编码规范原文:https://lizhe2004.gitbooks.io/code-style-guideline-c ...
- 小学生都能学会的python(编码 and 字符串)
小学生都能学会的python(编码 and 字符串) 一,编码 最早的计算机编码是ASCII. 有英文+数字+特殊字符 8bit => 1byte 没有中文, 后面的编码必须兼容ASCII ...
- 一篇夯实一个知识点系列--python生成
写在前面 本系列目的:一篇文章,不求鞭辟入里,但使得心应手. 迭代是数据处理的基石,在扫描内存无法装载的数据集时,我们需要一种惰性获取数据的能力(即一次获取一部分数据到内存).在Python中,具有这 ...
- (转载) 浅谈python编码处理
最近业务中需要用 Python 写一些脚本.尽管脚本的交互只是命令行 + 日志输出,但是为了让界面友好些,我还是决定用中文输出日志信息. 很快,我就遇到了异常: UnicodeEncodeError: ...
- Python 编码简单说
先说说什么是编码. 编码(encoding)就是把一个字符映射到计算机底层使用的二进制码.编码方案(encoding scheme)规定了字符串是如何编码的. python编码,其实就是对python ...
- Python之路3【知识点】白话Python编码和文件操作
Python文件头部模板 先说个小知识点:如何在创建文件的时候自动添加文件的头部信息! 通过:file--settings 每次都通过file--setings打开设置页面太麻烦了!可以通过:View ...
- python编码规范
python编码规范 文件及目录规范 文件保存为 utf-8 格式. 程序首行必须为编码声明:# -*- coding:utf-8 -*- 文件名全部小写. 代码风格 空格 设置用空格符替换TAB符. ...
- Python编码问题整理
认识常见编码 GB2312是中国规定的汉字编码,也可以说是简体中文的字符集编码 GBK 是 GB2312的扩展 ,除了兼容GB2312外,它还能显示繁体中文,还有日文的假名 cp936:中文本地系统是 ...
- 【转】python编码规范
http://blog.csdn.net/willhuo/article/details/49300441 决定开始Python之路了,利用业余时间,争取更深入学习Python.编程语言不是艺术,而是 ...
随机推荐
- SpringBoot的自动装配原理
Spring Boot 的自动装配(Auto-Configuration)是其核心特性之一,它极大地简化了Spring应用的配置过程. import org.springframework.boot. ...
- Cpu 资源占用高排查
查看java进程 ps aux | grep java 或者 ps -ef | grep java 查看java进程 线程信息 使用top -p [PID] -H 观察该进程中所有线程的资源占用 to ...
- 36条技巧优化PHP代码(总结)
原文:38条技巧优化PHP代码 1.如果一个方法能被静态,那就声明他为静态的,速度可提高1/4; 2.echo的效率高于print,因为echo没有返回值,print返回一个整型; 3.在循环之前设置 ...
- Python3处理文档_word文档实现自动化办公(一)
最近打算写一个自动化出报告的脚本 先从处理word文档开始 Python 操作 Word 最常见的依赖库是:python-docx 所以,在开始操作之前,我们需要在虚拟环境下安装这个依赖库 pip3 ...
- C#/.NET/.NET Core技术前沿周刊 | 第 36 期(2025年4.21-4.27)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...
- 【HUST】网络攻防实践|TCP会话劫持+序列号攻击netcat对话
文章目录 一.前言 1. 实验环境 2. 攻击对象 3. 攻击目的 4. 最终效果 docker的使用 新建docker docker常用指令 二.正式开始 过程记录 1. ARP欺骗 2. 篡改数据 ...
- Linux系列:如何用perf跟踪.NET程序的mmap泄露
一:背景 1. 讲故事 如何跟踪.NET程序的mmap泄露,这个问题困扰了我差不多一年的时间,即使在官方的github库中也找不到切实可行的方案,更多海外大佬只是推荐valgrind这款工具,但这款工 ...
- Excel工具类之“参数汇总”
一.SXSSFWorkbook技术 1.冻结行数 代码 SXSSFWorkbook wb = new SXSSFWorkbook(); SXSSFSheet sheet = wb.createShee ...
- 二:简单的C/S阻塞模型
C/S阻塞模型是指客户端/服务器阻塞模型,它描述了一种基于阻塞的网络通信方式.在阻塞模型中,客户端发送请求给服务器,并等待服务器的响应.在等待服务器响应的过程中,客户端的操作会被阻塞,直到服务器响应返 ...
- 【工程应用十】 基于Hessian矩阵的Frangi滤波算法 == 血管图像增强 == Matlab中fibermetric函数的自我实现、加速和优化。
前几天在翻一翻matlab中的帮助文档,无意中发现一个叫fibermetric的图像处理函数,感觉有点意思,可以增强或者说突出一些类似于管状的对象,后面看了下算法的帮助文档,在百度上找了找,原来这也是 ...