请考虑以下代码:
from itertools import chain
list(chain(42))
我将一个不可迭代的参数作为参数传递给chain
,而且令人惊讶的是,我得到了这个错误:
TypeError: 'int' object is not iterable
(仅传递list
是必要的,因为chain
在实际迭代之前不会评估其参数。)
如果我正确使用chain
,我可以将结果解压缩为函数参数:
from itertools import chain
foo = lambda x: x
foo(*chain([42]))
这样运行没有错误。
现在,考虑上述两种情况的组合,即一个带有非可迭代参数的链解压缩为函数参数:
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的类似错误消息。此错误消息的解释是什么?
答案 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
。