Skip to content

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


Released under the BSD3 License