next() 迭代优化
在手动驱动生成器或迭代器时,不同的结束检测方式对性能影响显著。本文比较三种常见写法的性能差异,并结合 CPython 实现给出结论与实践建议。
- for 循环:由解释器在 C 层处理,性能最好且最安全。
- next(...) + 默认值:避免进入异常路径,适用于需显式推进迭代器的场景。
- next(...) + try/except StopIteration:依赖异常作为常态结束判断,开销最大,不推荐在热路径使用。
代码测试
通过以下基准脚本,可以测试三种迭代方式的性能差异:
python
# -*- coding: utf-8 -*-
import sys
from time import time
if sys.version_info < (3, 0):
RANGE = xrange
else:
RANGE = range
print("Python version: %s" % sys.version)
COUNT = 10000000
def GEN_TEST():
yield 1
t = time()
for _ in RANGE(COUNT):
# 1. for 循环CPython内部迭代(最快)
for __ in GEN_TEST():
pass
print("Time taken (for loop): %fs" % (time() - t))
t = time()
for _ in RANGE(COUNT):
gen = GEN_TEST()
# 2. while 循环 + 捕获 StopIteration 异常(最慢)
while 1:
try:
next(gen)
except StopIteration:
break
print("Time taken (while loop): %fs" % (time() - t))
t = time()
for _ in RANGE(COUNT):
gen = GEN_TEST()
# 3. while 循环 + next 函数提供默认值(较快)
while not next(gen, None) is None:
pass
print("Time taken (while with next default): %fs" % (time() - t))输出示例
以下为不同 Python 版本上的示例输出(仅供参考,具体结果随环境而异):
text
Python version: 3.12.10 (tags/v3.12.10:0cc8128, Apr 8 2025, 12:21:36) [MSC v.1943 64 bit (AMD64)]
Time taken (for loop): 0.743824s
Time taken (while loop): 2.234993s
Time taken (while with next default): 1.391951s
Python version: 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:53:40) [MSC v.1500 64 bit (AMD64)]
Time taken (for loop): 0.989000s
Time taken (while loop): 4.398000s
Time taken (while with next default): 1.876000s结论:带默认值的 next() 在性能上优于捕获 StopIteration 的写法;而 for 循环由于完全在 C 层实现,通常最快。
底层解析
通过查看CPython的源代码,可以观察到next()函数的实现细节:
c
// CPython 3.12
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];
if (!PyIter_Check(it)) {
PyErr_Format(PyExc_TypeError,
"'%.200s' object is not an iterator",
Py_TYPE(it)->tp_name);
return NULL;
}
res = (*Py_TYPE(it)->tp_iternext)(it);
if (res != NULL) {
return res;
} else if (nargs > 1) {
// 当设置默认值时,next函数会在内部消化掉StopIteration异常,不再统计完整的调用栈信息
// 因此使用带默认值的next函数会比不带默认值的next函数更快
PyObject *def = args[1];
if (PyErr_Occurred()) {
if(!PyErr_ExceptionMatches(PyExc_StopIteration))
return NULL;
PyErr_Clear();
}
return Py_NewRef(def);
} else if (PyErr_Occurred()) {
return NULL;
} else {
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
}手动调用 next() 的常见场景
- 驱动协程/微线程:外部调度器按帧或按事件推进生成器(
next()/send()),为避免频繁抛出 StopIteration,建议使用默认值或先检查结束条件。 - 可中断/可暂停的迭代器:当外部条件决定何时继续消费下一项时,手动推进更灵活,使用哨兵值(如
None)判断结束更高效。
简言之:能用 for 就用 for;必须手动推进时,优先用带默认值的 next()。