【问题标题】:Python 'string' % [1, 2, 3] doesn't raise TypeErrorPython 'string' % [1, 2, 3] 不会引发 TypeError
【发布时间】:2013-09-26 15:51:48
【问题描述】:

str.__mod__ 的确切行为是否记录在案?

这两行代码按预期工作:

>>> 'My number is: %s.' % 123
'My number is: 123.'
>>> 'My list is: %s.' % [1, 2, 3]
'My list is: [1, 2, 3].'

这条线的行为也符合预期:

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

但是这条线是做什么的,为什么它没有引发任何错误?

>>> 'Not a format string' % [1, 2, 3]
'Not a format string'

P。

>>> print(sys.version)
3.3.2 (default, Aug 15 2013, 23:43:52) 
[GCC 4.7.3]

【问题讨论】:

  • 请注意,这种行为在 Python 3.8 中仍然存在,并且似乎只有 list 或 dict 参数无法产生错误。
  • range() 也无法产生错误(至少在 Python 3.7.3 中),但生成器表达式会产生错误。看起来很神秘
  • 奇怪的是,没有字典、列表和范围都有的神奇方法,但元组(抛出错误)缺乏。另一方面,字典、列表和范围都实现了'__iter__', '__len__', '__contains__', '__getitem__',但简单的数字却没有——所以我怀疑其中一种方法与观察到的行为有关。
  • @JohnColeman 查看我的回答。通常所有可以 sed 为“映射”并且可以被索引的 args 都不会引发错误,但 tuples 被明确排除,可能是因为它们用于 varargs 解包。
  • @JanChristophTerasa 这是一个不错的答案 (+1)。作为一个快速测试,我刚刚编写了一个包含def __getitem__(self): pass 的单行定义的类,并且该类的一个对象按照您的回答表明它应该起作用。元组在这里自动解包因此被排除在外是有道理的。

标签: python list modulo string-interpolation python-3.2


【解决方案1】:

我觉得可以在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,因此不会进入错误情况。

dictlist 都可以用作此类映射,因此在用作参数时不会引发错误。 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__ 方法就足够了,不会触发错误。


编辑:在 OP 中引用的 v3.3.2 中,违规行是同一文件中的第 13922、13459 和 1918 行,逻辑看起来相同。


EDIT2:在v3.0 中,检查位于Objects/unicodeobject.c 的第8841 和9226 行,来自Objects/abstract.cPyMapping_Check 尚未用于Unicode 格式代码。


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 可以告诉我们为什么这是预期的行为。

【讨论】:

    【解决方案2】:

    添加最新的 printf 样式格式后,% 格式中似乎出现了很多小怪癖。今天(3.8 版),这是documented here,但在3.3 版here 中已经提到过。

    这里描述的格式化操作表现出各种怪癖 这会导致许多常见错误(例如无法显示 元组和字典正确)。使用较新的格式化字符串 文字、str.format() 接口或模板字符串可能会有所帮助 避免这些错误。这些替代方案中的每一个都提供了自己的 简单性、灵活性和/或 可扩展性。

    在这种特定情况下,Python 在% 的右侧看到一个带有__getitem__ 方法的非元组值,并假定必须执行format_map。这通常使用dict 完成,但确实可以使用__getitem__ 方法对任何对象完成。

    特别是,format_map 允许忽略未使用的键,因为您通常不会遍历映射项来访问它们。

    >>> "Include those items: %(foo)s %(bar)s" % {"foo": 1, "bar": 2, "ignored": 3}
    'Include those items: 1 2'
    

    您的示例是使用该功能,其中容器的所有键都被忽略。

    >>> "Include no items:" % {"foo": 1, "bar": 2}
    'Include no items:'
    

    如果您想进一步证明这一点,请检查当您使用 list 作为右侧时会发生什么。

    >>> lst = ["foo", "bar", "baz"]
    >>> "Include those items: %(0)s, %(2)s" % lst
    TypeError: list indices must be integers or slices, not str
    

    Python确实尝试获取lst["0"],不幸的是没有办法指定"0"应该转换为int,所以这注定了%语法会失败。

    旧版本

    据记录,这似乎是 Python 3.0 之前出现的一个怪癖,因为我尽我所能得到相同的行为,尽管文档开始仅针对 3.3 版本提及它。

    Python 3.0.1+ (unknown, May  5 2020, 09:41:19) 
    [GCC 9.2.0] on linux4
    Type "help", "copyright", "credits" or "license" for more information.
    >>> 'Not a format string' % [1, 2, 3]
    'Not a format string'
    

    【讨论】:

    • 这个问题最初是在 2013 年提出的,比 f-strings 还要早几年。 F 字符串可能已添加到解决问题,但它们不是原因。
    • 虽然format_map 已经在 Python 3.2 中被引入,但是挖掘一个 Python 3.0 或 Python 3.1 解释器进行比较会很有趣。
    • @chepner 我在答案中添加了一条注释。事实证明,这实际上是指 printf 样式的格式,而不仅仅是 f 字符串,并且存在于 3.3 版的文档中:docs.python.org/3.3/library/…
    • @chepner 作为记录,我对 Python 3.0 有相同的行为
    • 我在 python 2.7 中看到了同样的行为。
    猜你喜欢
    • 2016-06-20
    • 2011-05-08
    • 2018-12-08
    • 1970-01-01
    • 2010-10-04
    • 2021-05-07
    • 1970-01-01
    • 2021-12-22
    • 2018-09-23
    相关资源
    最近更新 更多