Python 在没有转换说明符的情况下不会在 % 上引发异常

Python not raising an exception on % without conversion specifier

描述了字符串格式的 % 运算符 here

通常,当出现一个没有转换说明符的字符串时,它会引发一个TypeError: not all arguments converted during string formatting。例如,"" % 1 将失败。到目前为止,还不错。

有时,它不会失败,但是,如果 % 运算符右边的参数是空的:"" % [],或 "" % {}"" % () 将默默地 return 空字符串,看起来很公平。

"%s" 相同而不是空字符串会将空对象转换为字符串,除了最后一个会失败,但我认为这是 % 运算符问题的一个实例,它们是用format方法解决。

还有一个非空字典的情况,如 "" % {"a": 1},它之所以有效,是因为它确实应该与命名类型说明符一起使用,如 "%(a)d" % {"a": 1}.

但是,有一种情况我不明白:"" % b"x" 将 return 空字符串,没有引发异常。为什么?

我不是 100% 确定,但在 sources 快速浏览后,我猜原因如下:

%右边只有一个参数时,Python查看它是否有getitem方法,如果有,则假定它是一个映射并期待我们使用像 %(name)s 这样的命名格式。否则,Python 从参数创建一个单元素元组并执行位置格式化。不使用映射检查参数计数,因此,由于 byteslists 确实有 getitem,它们不会失败:

>>> "xxx" % b'a'
'xxx'
>>> "xxx" % ['a']
'xxx'

另请考虑:

>>> class X: pass
... 
>>> "xxx" % X()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: not all arguments converted during string formatting

>>> class X:
...    def __getitem__(self,x): pass
... 
>>> "xxx" % X()
'xxx'

字符串是此规则的例外 - 它们有 getitem,但对于位置格式仍然是 "tuplified":

>>> "xxx" % 'a'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: not all arguments converted during string formatting

当然,这个"sequences as mappings"逻辑没有多大意义,因为格式化键总是字符串:

>>> "xxx %(0)s" % ['a']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not str

但我怀疑有人会解决这个问题,因为 % 无论如何都被放弃了。

违规行位于 unicodeobject.c。它认为所有 "mappings" 的对象,并且明确地不是元组或字符串或其子类,如 "dictionaries",并且如果不是所有参数都被转换,那么它不是错误。

PyMapping_Check定义为:

int
PyMapping_Check(PyObject *o)
{
    return o && o->ob_type->tp_as_mapping &&
        o->ob_type->tp_as_mapping->mp_subscript;
}

也就是说,定义了 tp_as_mapping 且具有 mp_subscript 的任何类型都是一个映射。

并且 bytesdefine that 一样,任何其他带有 __getitem__ 的对象也是如此。因此,至少在 Python 3.4 中,具有 __getitem__ 的对象不会作为 % 格式操作的右侧参数失败。

现在这是对 Python 2.7 的更改。此外,这样做的原因是没有办法检测所有可能用于 %(name)s 格式化的类型,除非接受 all 实现 __getitem__,虽然最明显的错误已经被剔除。当 Python 3 发布时,没有人在那里添加 bytes,尽管它显然不应该支持字符串作为 __getitem__ 的参数;但那里也没有list

另一个疏忽是列表不能用于格式化位置参数。