返回主页
来自东方地灵殿的小提琴手
愿时间能带走痛苦
博客园
首页
新随笔
联系
订阅
管理
随笔 - 345 文章 - 0 评论 - 354 阅读 - 109万
《深度剖析CPython解释器》30. 源码解密内置函数 iter、next
作者:@古明地盆
喜欢这篇文章的话,就点个关注吧,或者关注一下我的公众号也可以,会持续分享高质量Python文章,以及其它相关内容。:点击查看公众号
楔子
这次我们来看看 iter 和 next 这两个内置函数的用法,我们知道 iter 是将一个可迭代对象变成一个迭代器,next 是将迭代器里的值一步一步迭代出来。
lst = [1, 2, 3]
it = iter(lst)
print(it) # <list_iterator object at 0x000001DC6E898640>
调用next, 可以对迭代器进行迭代
print(next(it)) # 1
注意:iter 还有一个鲜为人知的用法,我们来看一下:
val = 0
def foo():
global val
val += 1
return val
iter可以接收一个参数: iter(可迭代对象)
iter可以接收两个参数: iter(可调用对象, value)
for i in iter(foo, 5):
print(i)
"""
1
2
3
4
"""
进行迭代的时候, 会不停的调用内部接收的 可调用对象
直到返回值等于传递第二个参数 value(在底层被称为哨兵), 然后终止迭代
当然 next 函数也有一个特殊用法,就是它在接收一个迭代器的时候,还可以指定一个默认值;如果元素迭代完毕之后再次迭代的话,不会抛出StopIteration,而是会返回默认值。
it = iter([1, 2, 3])
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
print(next(it, "xxx")) # xxx
print(next(it, "yyy")) # yyy
注意:iter 内部接收可迭代对象的类型不同,那么得到的迭代器种类也不同。
print(iter("xyz")) # <str_iterator object at 0x00000234493B8640>
print(iter((1, 2, 3))) # <tuple_iterator object at 0x00000234493B8640>
print(iter([1, 2, 3])) # <list_iterator object at 0x00000234493B8640>
iter 函数底层实现
我们看一下 iter 函数底层是如何实现的,其实之前已经见识过了,还想的起来吗?
static PyObject *
builtin_iter(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *v;
// iter函数要么接收一个参数, 要么接收两个参数
if (!_PyArg_CheckPositional("iter", nargs, 1, 2))
return NULL;
v = args[0];
// 如果接收一个参数, 那么直接使用 PyObject_GetIter 获取对应的迭代器即可
// 在这个函数中, 可迭代对象的类型不同, 那么得到的迭代器也不同
if (nargs == 1)
return PyObject_GetIter(v);
// 如果接收的不是一个参数, 那么一定是两个参数
// 如果是两个参数, 那么第一个参数一定是可调用对象
if (!PyCallable_Check(v)) {
PyErr_SetString(PyExc_TypeError,
"iter(v, w): v must be callable");
return NULL;
}
// 获取value(哨兵)
PyObject *sentinel = args[1];
// 调用PyCallIter_New, 得到一个可调用的迭代器, calliterobject 对象
/*
位于 Objects/iterobject.c 中
typedef struct {
PyObject_HEAD
PyObject *it_callable;
PyObject *it_sentinel;
} calliterobject;
*/
return PyCallIter_New(v, sentinel);
}
所以核心就在于 PyObject_GetIter 中,它是根据可迭代对象生成迭代器的关键,那么它都做了哪些事情呢?不用想肯定是执行:obj.iter(),当然更准确的说应该是:type(obj).iter(obj),我们来看一下。该函数定义在 Objects/abstract.c 中:
PyObject *
PyObject_GetIter(PyObject *o)
{
// 获取该可迭代对象的类型对象
PyTypeObject *t = Py_TYPE(o);
// 我们说类型对象的方法和属性 决定了 实例对象的行为, 实例对象调用的那些方法都是属于类型对象的
// 还是那句话 obj.func() 等价于 type(obj).func(obj)
getiterfunc f;
// 所以这里是获取类型对象的 tp_iter成员, 也就是Python中的 iter
f = t->tp_iter;
// 如果 f 为 NULL, 说明该类型对象内部的 tp_iter 成员被初始化为NULL, 即内部没有定义 iter 方法
// 像str、tuple、list等类型对象, 它们的 tp_iter 成员都是不为NULL的
if (f == NULL) {
// 如果 tp_iter 为 NULL, 那么解释器会退而求其次, 检测该类型对象中是否定义了 getitem, 我们后面会介绍
// 如果定义了, 那么直接调用PySeqIter_New, 得到一个 seqiterobject 对象, 我们在上一篇博客介绍过的
// PySequence_Check 里面的逻辑就是检测类型对象是否初始化了 tp_as_sequence->sq_item 成员, 它对应 getitem
if (PySequence_Check(o))
return PySeqIter_New(o);
// 走到这里说明该类型对象的实例对象不具备可迭代的性质, 抛出异常
return type_error("'%.200s' object is not iterable", o);
}
else {
// 否则的话, 直接进行调用, Py_TYPE(o)->tp_iter(o)
PyObject res = (f)(o);
// 如果返回值 res 不为NULL, 并且它还不是一个迭代器, 抛出异常
if (res != NULL && !PyIter_Check(res)) {
PyErr_Format(PyExc_TypeError,
"iter() returned non-iterator "
"of type '%.100s'",
Py_TYPE(res)->tp_name);
Py_DECREF(res);
res = NULL;
}
// 返回 res
return res;
}
}
所以我们看到这便是 iter 函数的底层实现,里面我们提到了 getitem。我们说如果类型对象内部没有定义 iter,那么解释器会退而求其次检测内部是否定义了 getitem。
class A:
def __getitem__(self, item):
return f"参数item: {item}"
a = A()
内部定义了 getitem, 首先可以让实例对象像字典一样
print(a["name"]) # 参数item: name
print(a["夏色祭"]) # 参数item: 夏色祭
此外还可以像迭代器一个被for循环
for idx, val in enumerate(a):
print(val)
if idx == 5:
break
"""
参数item: 0
参数item: 1
参数item: 2
参数item: 3
参数item: 4
参数item: 5
"""
我们看到循环的时候会自动给item传值, 这个值是0 1 2 3...., 因此这个循环是无限的
class Girl:
def init(self):
self.names = ["夏色祭", "神乐七奈", "夜空梅露", "雫_るる"]
def __getitem__(self, item):
try:
val = self.names[item]
return val
except IndexError:
raise StopIteration # 让for循环结束
g = Girl()
for _ in g:
print(_)
"""
夏色祭
神乐七奈
夜空梅露
雫_るる
"""
当出现 StopIteration 异常的时候, 再次循环时传递的item会被重置为0
print(list(g)) # ['夏色祭', '神乐七奈', '夜空梅露', '雫_るる']
lst = []
lst.extend(g)
print(lst) # ['夏色祭', '神乐七奈', '夜空梅露', '雫_るる']
以上被称为解释器的退化功能,就是在找不到某个实现的时候,会进行退化、尝试寻找其它实现。类似的做法还有其它,比如:
class A:
def __len__(self):
return 0
当进行布尔判断的时候, 会尝试获取内部 bool 方法的返回值
如果没有定义 bool, 那么解释器会退化尝试寻找 len 方法
print(bool(A())) # False
如果内部定义了 iter,则直接调用即可。
如果type(obj)内部定义了__iter__, 那么iter(obj) <==> type(obj).iter(obj)
print(str.iter("123")) # <str_iterator object at 0x00000213CC2A8640>
print(list.iter([1, 2, 3])) # <list_iterator object at 0x00000213CC2A8640>
print(tuple.iter((1, 2, 3))) # <tuple_iterator object at 0x00000213CC2A8640>
print(set.iter({1, 2, 3})) # <set_iterator object at 0x00000213CC478E80>
next函数底层实现
了解 iter 之后,我们再来看看 next 函数;如果内部定义了 next 函数,那么不用想,结果肯定是type(obj).next(obj),我们看一下底层实现。
static PyObject *
builtin_next(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *it, *res;
// 同样接收一个参数或者两个参数
if (!_PyArg_CheckPositional("next", nargs, 1, 2))
return NULL;
it = args[0];
// 第一个参数必须是一个迭代器, 也就是类型对象的tp_iternext成员不能为NULL
if (!PyIter_Check(it)) {
// 否则的话, 抛出TypeError, 表示第一个参数传递的不是一个迭代器
PyErr_Format(PyExc_TypeError,
"'%.200s' object is not an iterator",
it->ob_type->tp_name);
return NULL;
}
/*
列表对应的迭代器是: listiterobject, 其类型为: PyListIter_Type
元组对应的迭代器是: tupleiterobject, 其类型为: PyTupleIter_Type
字符串对应的迭代器是: unicodeiterobject, 其类型为: PyUnicodeIter_Type
*/
// 通过 ob_type 获取对应的类型对象, 然后获取成员 tp_iternext 的值, 相当于__next__
// 再传入迭代器进行迭代
res = (*it->ob_type->tp_iternext)(it);
// 如果 res 不为 NULL, 那么证明迭代到值了, 直接返回
if (res != NULL) {
return res;
} else if (nargs > 1) {
// 否则的话, 看nargs是否大于1, 如果大于1, 说明设置了默认值
PyObject *def = args[1];
// 出现异常的话, 将异常清空
if (PyErr_Occurred()) {
if(!PyErr_ExceptionMatches(PyExc_StopIteration))
return NULL;
PyErr_Clear();
}
// 增加引用计数, 并返回
Py_INCREF(def);
return def;
} else if (PyErr_Occurred()) {
return NULL;
} else {
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
}
还是比较简单的,我们以 列表 对应的迭代器为例,举个栗子:
lst = [1, 2, 3]
it = iter(lst)
对应的类型是list_iterator, 但在底层我们知道类型是PyListIter_Type
print(type(it)) # <class 'list_iterator'>
然后迭代
print(type(it).next(it)) # 1
print(type(it).next(it)) # 2
print(type(it).next(it)) # 3
以上就等价于 next(it), 但是我们知道内置函数 next 要更强大一些, 因为它还可以做一些其它处理
当然默认情况下, 和 type(it).next(it) 最终是殊途同归的
怎么样,是不是很简单呢?
我们看到一个变量 obj 不管指向什么可迭代对象,都可以交给 iter,得到对应的迭代器;不管什么迭代器,都可以交给 next 进行迭代。原因就在于它们接收的不是对象本身,而是对象对应的 PyObject * 泛型指针。不管你是谁的指针,只要你指向的对象是一个可迭代对象,那么都可以交给 iter。至于 next 也是同理,不管你指向的是哪一种迭代器,只要是迭代器,就可以交给 next,然后会自动调用迭代器内部的 next(底层是 tp_iternext)将值给迭代出来。所以这是不是相当于实现了多态呢?所以这就是 Python 的设计哲学,变量只是一个指针,传递变量的时候相当于传递指针(将指针拷贝一份),但是操作一个变量的时候会自动操作变量(指针)指向的内存。比如:a = 12345; b = a,相当于把 a 拷贝了一份给 b,但 a 是一个指针,所以此时 a 和 b 保存的地址是相同的,也就是指向了同一个对象。但 a + b 的时候则不是两个指针相加、而是将 a、b 指向的对象进行相加,也就是操作变量会操作变量指向的内存。因此在 Python 中,说传递方式是值传递或者引用传递都是不准确的,应该是变量之间的赋值传递,对象之间的引用传递。
如果觉得文章对您有所帮助,可以请囊中羞涩的作者喝杯柠檬水,万分感谢,愿每一个来到这里的人都生活愉快,幸福美满。
微信赞赏
支付宝赞赏
标签: 深度剖析CPython解释器
好文要顶 关注我 收藏该文
古明地盆
粉丝 - 590 关注 - 0
+加关注
0
0
« 上一篇: 《深度剖析CPython解释器》29. 源码解密 map、filter、zip 底层实现,对比列表解析式
» 下一篇: 《深度剖析CPython解释器》31. Python 和 C / C++ 联合编程
posted @ 2020-11-26 00:39 古明地盆 阅读(963) 评论(2) 编辑 收藏 举报
评论列表
按时间 | 按支持数
回复 引用
1楼 2020-12-12 13:55 psi-cmd
啊这,觉了?
支持(0) 反对(0)
回复 引用
2楼 2020-12-12 13:58 psi-cmd
吓得我专门注册一个号来评论(
支持(0) 反对(0)
刷新评论刷新页面返回顶部
发表评论
编辑 预览
自动补全
退出 订阅评论 我的博客
[Ctrl+Enter快捷键提交]
【推荐】园子的商业化努力-AI人才服务:招募AI导师,一起探索AI领域的机会
【推荐】中国云计算领导者:阿里云轻量应用服务器2核2G低至108元/年
【推荐】第五届金蝶云苍穹低代码开发大赛正式启动,百万奖金等你拿!
编辑推荐:
· SQL 优化实战分享
· 分页列表缓存,你真的会吗
· 红黑树是怎么来的
· 一次 redis 主从切换导致的数据丢失与陷入只读状态故障
· [MAUI] 在 .NET MAUI 中复刻苹果 Cover Flow
即构专区:
· 即构电商直播方案,支持万商之家撬动千亿商业直播市场
· AI 时代的视频云转码移动端化——更快、更好、更低、更广
· 影响音视频延迟的关键因素(二): 采集、前处理、编解码
· 零基础实现Java直播(二):实现流程
· 【活动回顾】WebRTC服务端工程实践和优化探索
公告
对啦,创建了一个微信公众号,也会不断在上面分享自己的一些经验。
如果你想成为 Python 领域的 master,就点个关注吧,我愿助你一臂之力。
我的微信:18510286802,可有偿提供指导(一份土豆牛肉盖饭 + 一瓶柠檬水即可)。
只要是我熟悉的,都可以毫无保留的分享给你
昵称: 古明地盆
园龄: 5年8个月
粉丝: 590
关注: 0
+加关注
< 2023年5月 >
日 一 二 三 四 五 六
30 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 1 2 3
4 5 6 7 8 9 10
搜索
常用链接
我的随笔
我的评论
我的参与
最新评论
我的标签
我的标签
深度剖析CPython解释器(34)
Python中你不可不知的模块(27)
python中你未曾在意的点点滴滴(26)
没有人比你更懂Redis(24)
python数据处理(22)
深度解密 HTTP 协议(22)
ClickHouse:一款速度快到让人发指的列式存储数据库(19)
python的一些比较冷门、但是有趣的库(18)
关系型数据库(17)
go语言从入门到入坟(14)
更多
随笔档案
2023年5月(12)
2023年4月(7)
2023年3月(5)
2023年2月(2)
2022年2月(1)
2022年1月(1)
2021年12月(5)
2021年10月(24)
2021年9月(18)
2021年8月(2)
2021年7月(2)
2021年5月(4)
2021年4月(1)
2021年2月(1)
2021年1月(1)
更多
相册
satori(27)
阅读排行榜
1. 详解pandas的read_csv方法(210636)
2. pandas行转列、列转行、以及一行生成多行(56364)
3. numpy增加维度、删除维度(46582)
4. 详解pandas中的rolling(37378)
5. FastAPI:Python 世界里最受欢迎的异步框架(30627)
评论排行榜
1. pandas行转列、列转行、以及一行生成多行(23)
2. 详解pandas的read_csv方法(18)
3. FastAPI:Python 世界里最受欢迎的异步框架(14)
4. ClickHouse 的基本介绍,什么是 ClickHouse?(13)
5. 《深度剖析CPython解释器》7. 解密Python中字符串的底层实现,以及相关操作(13)
推荐排行榜
1. 详解pandas的read_csv方法(23)
2. pandas行转列、列转行、以及一行生成多行(17)
3. FastAPI:Python 世界里最受欢迎的异步框架(16)
4. 详解pandas中的rolling(14)
5. 1. Redis是属于多线程还是单线程?不同版本之间有什么区别?(11)
最新评论
1. Re:ClickHouse 的基本介绍,什么是 ClickHouse?
我还以为点错了,点进萌娘百科了。我寻思萌娘百科也做这玩意的百科?
--键盘吞噬兽
2. Re:详解 C 语言的宏定义
MARK 点赞 支持。慢慢消化。好难的说。。。
--tianqi911
3. Re:《深度剖析CPython解释器》5. 解密Python中的整数在底层是如何实现的,以及为什么Python中大整数的运算不会溢出
python3.12对整形的定义变成了 typedef struct _PyLongValue { uintptr_t lv_tag; /* Number of digits, sign and fl...
--点墨留白
4. Re:《深度剖析CPython解释器》6. 解密Python中bytes对象的底层实现,以及相关操作
缓存技术是一种以空间换时间的优化手段,文章中有笔误
在 字节序列缓冲池 这一小节的末尾总结中
--路璐
5. Re:详解大数据中必不可少的消息中间件 kafka(3.x 新版本)
太棒了,博主就是我的神
--doge_gange
Copyright © 2023 古明地盆
Powered by .NET 7.0 on Kubernetes
目录导航