我认为可以在 CPython 源代码中找到负责的行,我得到了 git v3.8.2
:
在函数中
PyObject *
PyUnicode_Format(PyObject *format, PyObject *args)
在Objects/unicodeobject.c
第 14944 行,有以下几行
Objects/unicodeobject.c
, 第 15008 行
if (ctx.argidx < ctx.arglen && !ctx.dict) {
PyErr_SetString(PyExc_TypeError,
"not all arguments converted during string formatting");
goto onError;
}
arglen
如果不匹配,这将给出错误,但如果ctx.dict
为“true”,则不会给出错误。什么时候是“真的”?
Objects/unicodeobject.c
, 第 14976 行
if (PyMapping_Check(args) && !PyTuple_Check(args) && !PyUnicode_Check(args))
ctx.dict = args;
else
ctx.dict = NULL;
好的,PyMapping_Check
检查传递的args
,如果那是“真”,并且我们没有元组或 unicode 字符串,我们设置ctx.dict = args
.
做什么PyMapping_Check
?
Objects/abstract.c
,第 2110 行
int
PyMapping_Check(PyObject *o)
{
return o && o->ob_type->tp_as_mapping &&
o->ob_type->tp_as_mapping->mp_subscript;
}
据我了解,如果该对象可以用作“映射”,并且可以被索引/下标,这将返回1
. 在这种情况下, 的值ctx.dict
将设置为args
,即!0
,因此不会进入错误情况。
两者dict
和list
都可以用作此类映射,因此在用作参数时不会引发错误。tuple
在第 14976 行的检查中明确排除,可能是因为它用于将可变参数传递给格式化程序。
我不清楚这种行为是否或为什么是故意的,但源代码中的部分未注释。
基于此,我们可以尝试:
assert 'foo' % [1, 2] == 'foo'
assert 'foo' % {3: 4} == 'foo'
class A:
pass
assert 'foo' % A() == 'foo'
# TypeError: not all arguments converted during string formatting
class B:
def __getitem__(self):
pass
assert 'foo' % B() == 'foo'
__getitem__
因此,对象定义一个不触发错误的方法就足够了。
编辑:在v3.3.2
OP 中引用的 中,违规行是同一文件中的第 13922、13459 和 1918 行,逻辑看起来相同。
EDIT2:在v3.0
中,检查在 8841 和 9226 行中Objects/unicodeobject.c
,在 Unicode 格式代码中尚未使用PyMapping_Check
from 。Objects/abstract.c
EDIT3:根据一些二分法和 git blame,核心逻辑(在 ASCII 字符串上,而不是 unicode 字符串上)可以追溯到 Python 1.2,并由 GvR 自己在 25 年前实现:
commit caeaafccf7343497cc654943db09c163e320316d
Author: Guido van Rossum <guido@python.org>
Date: Mon Feb 27 10:13:23 1995 +0000
don't complain about too many args if arg is a dict
diff --git a/Objects/stringobject.c b/Objects/stringobject.c
index 7df894e12c..cb76d77f68 100644
--- a/Objects/stringobject.c
+++ b/Objects/stringobject.c
@@ -921,7 +921,7 @@ formatstring(format, args)
XDECREF(temp);
} /* '%' */
} /* until end */
- if (argidx < arglen) {
+ if (argidx < arglen && !dict) {
err_setstr(TypeError, "not all arguments converted");
goto error;
}
可能 GvR 可以告诉我们为什么这是预期的行为。