为什么使用不可迭代的参数解包链会引发此错误?

时间:2017-04-10 14:14:29

标签: python python-2.7 iterable-unpacking

  1. 请考虑以下代码:

    from itertools import chain
    list(chain(42))
    

    我将一个不可迭代的参数作为参数传递给chain,而且令人惊讶的是,我得到了这个错误:

    TypeError: 'int' object is not iterable
    

    (仅传递list是必要的,因为chain在实际迭代之前不会评估其参数。)

  2. 如果我正确使用chain,我可以将结果解压缩为函数参数:

    from itertools import chain
    foo = lambda x: x
    foo(*chain([42]))
    

    这样运行没有错误。

  3. 现在,考虑上述两种情况的组合,即一个带有非可迭代参数的链解压缩为函数参数:

    from itertools import chain
    foo = lambda x: x
    foo(*chain(42))
    

    正如所料,这失败了。在Python 3中,这会引发与第一种情况相同的错误。但是,在Python 2.7.12中,抛出的错误是:

    TypeError: <lambda>() argument after * must be an iterable, not itertools.chain
    

    这对我没有任何意义。 itertools.chain显然是一种可迭代的类型:isinstance(chain(42),collections.Iterable)会产生True。此外,它在第二个例子中没有引起任何问题。我希望出现类似于案例2或Python 3的类似错误消息。此错误消息的解释是什么?

1 个答案:

答案 0 :(得分:0)

您看到的行为是尝试提供有关函数调用出错的更清晰的错误消息。

Python 2.7确定对象是否可迭代的方法只是尝试迭代它,然后在必要时捕获TypeError异常。它实际上并没有在Python代码中实现,但在处理函数调用语法时仍然会发生这种情况。 注意:这与lambda无关,而普通的def也会说明这个例子。

函数调用由this C code在CPython 2.7中处理:

static PyObject *
ext_do_call(PyObject *func, PyObject ***pp_stack, int flags, int na, int nk)
{
    ... snip ...

        t = PySequence_Tuple(stararg);
        if (t == NULL) {
            if (PyErr_ExceptionMatches(PyExc_TypeError) &&
                    /* Don't mask TypeError raised from a generator */
                    !PyGen_Check(stararg)) {
                PyErr_Format(PyExc_TypeError,
                             "%.200s%.200s argument after * "
                             "must be an iterable, not %200s",
                             PyEval_GetFuncName(func),
                             PyEval_GetFuncDesc(func),
                             stararg->ob_type->tp_name);
            }
            goto ext_call_fail;

    ... snip ...
}

我为了简洁而截断了代码来显示相关的块:starargs被迭代成一个元组,如果失败了PyExc_TypeError,那么会出现一个新的错误,类型和消息与你的匹配见过。

在Python 3中,函数调用C代码被清理并简化了。实际上ext_do_call函数甚至不再存在,它可能在PEP 3113的实现过程中被删除。现在,迭代破坏的链条的例外气泡未处理。如果你想在当前的通话代码中找到答案,你可以开始挖掘Python/ceval.c::do_call_core