cpython解释器源代码_《深度剖析CPython解释器》31. 源码解密内置函数 iter、next

楔子

这次我们来看看 iter 和 next 这两个内置函数的用法,我们知道 iter 是将一个可迭代对象变成一个迭代器,next 是将迭代器里的值一步一步迭代出来。

lst = [1, 2, 3]

it = iter(lst)

print(it) #

# 调用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")) #

print(iter((1, 2, 3))) #

print(iter([1, 2, 3])) #

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")) #

print(list.__iter__([1, 2, 3])) #

print(tuple.__iter__((1, 2, 3))) #

print(set.__iter__({1, 2, 3})) #

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)) #

# 然后迭代

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) 最终是殊途同归的

怎么样,是不是很简单呢?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值