PEP 3140 – str(容器) 应该调用 str(元素),而不是 repr(元素)
- 作者:
- Oleg Broytman <phd at phdru.name>, Jim J. Jewett <jimjjewett at gmail.com>
- 讨论列表:
- Python-3000 列表
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建:
- 2008年5月27日
- 更新历史:
- 2008年5月28日
拒绝理由
Guido 表示,这会在接近 Beta 版本时造成太多干扰。参见 [1]。
摘要
本文档讨论了 str(container)
当前实现的优缺点。它还讨论了另一种方法的优缺点——调用 str(item)
而不是 repr(item)
。
动机
目前 str(container)
对元素调用 repr
。支持它的理由:
- 容器拒绝猜测用户希望在
str(container)
中看到什么——环境、分隔符等; repr(item)
通常显示类型信息——字符串周围的单引号、类名等。
反对的理由:
- 这很不合理;
str()
预期如果存在则调用__str__
,而不是__repr__
; - 没有标准的方法来打印容器的内容并调用元素的
__str__
,这在__str__
和__repr__
返回不同结果的情况下很不方便; repr(item)
有时会做错事(例如,对非 ASCII 字符串进行十六进制转义)。
本 PEP 提出更改 str(container)
的工作方式。建议模仿 repr(container)
的工作方式,但有一个细节不同——对元素调用 str
而不是 repr
。这允许用户选择她想要获得的结果——来自 item.__repr__
或 item.__str__
。
现状
大多数容器类型(元组、列表、字典、集合等)都没有实现 __str__
方法,因此 str(container)
调用 container.__repr__
,而 container.__repr__
一旦被调用,就会忘记它是从 str
调用的,并且始终对容器的元素调用 repr
。
这种行为有优点和缺点。一个优点是大多数元素都用类型信息表示——字符串用单引号括起来,实例可能同时具有类名和实例数据。
>>> print([42, '42'])
[42, '42']
>>> print([Decimal('42'), datetime.now()])
[Decimal("42"), datetime.datetime(2008, 5, 27, 19, 57, 43, 485028)]
缺点是 __repr__
通常返回技术数据(如“<object at address>
”)或不可读的字符串(如果输入是非 ASCII 字符串,则为十六进制编码的字符串)。
>>> print(['тест'])
['\xd4\xc5\xd3\xd4']
对 PEP 3138 的动机之一是,repr
和 str
都无法对键为非 ASCII 文本字符串的字典进行合理的打印。现在允许使用 Unicode 标识符,它包括 Python 自身的属性字典。这也包括 JSON 序列化(并导致 json 库的一些麻烦)。
PEP 3138 提出通过打破“repr 是安全的 ASCII”不变性来解决此问题,并更改 repr
(用于持久性)输出某些对象的方式,并伴随系统相关的错误。
更改 str(container)
的工作方式将允许在正常情况下轻松调试,并保留仅 ASCII 字符的机器可读情况的安全。唯一的缺点是 str(x)
和 repr(x)
将更频繁地不同——但仅在当前几乎相同版本不足的情况下。
此外,str(container)
对元素调用 repr
而不是 str
似乎也不合逻辑。期望以下代码
class Test:
def __str__(self):
return "STR"
def __repr__(self):
return "REPR"
test = Test()
print(test)
print(repr(test))
print([test])
print(str([test]))
打印
STR
REPR
[STR]
[STR]
但实际上它打印
STR
REPR
[REPR]
[REPR]
尤其是在 Python 2 中,当对看起来像元组的对象调用 print 时,它使用 str
,这一点很不合理。
>>> print Decimal('42'), datetime.now()
42 2008-05-27 20:16:22.534285
而在真正的元组上,它打印
>>> print((Decimal('42'), datetime.now()))
(Decimal("42"), datetime.datetime(2008, 5, 27, 20, 16, 27, 937911))
另一种方法 - 调用 str(item)
例如,对于数字,人们通常只关心其值。
>>> print Decimal('3')
3
但是将值放入列表中会迫使用户读取类型信息,就像为了机器的利益而调用了 repr
一样。
>>> print [Decimal('3')]
[Decimal("3")]
在此更改之后,类型信息不会干扰 str
输出。
>>> print "%s".format([Decimal('3')])
[3]
>>> str([Decimal('3')]) # ==
[3]
但如果需要,它仍然可用。
>>> print "%r".format([Decimal('3')])
[Decimal('3')]
>>> repr([Decimal('3')]) # ==
[Decimal('3')]
有多种策略可以解决这个问题。最激进的方法是更改 __repr__
,使其接受一个新的参数(标志)“从 str
调用,因此对元素调用 str
,而不是 repr
”。该提议的缺点是必须更改每个 __repr__
实现。内省可以稍微有所帮助(在调用之前检查 __repr__
是否接受 2 个或 3 个参数),但内省不适用于用 C 编写的类,例如所有内置容器。
不太激进的提议是为内置容器类型实现 __str__
方法。明显的缺点是工作量重复——所有这些 __str__
和 __repr__
实现只有一个细微的差别——它们是否对元素调用 str
或 repr
。
最保守的提议是不更改 str,而是允许开发人员实现他们自己的特定于应用程序或库的漂亮打印器。缺点再次是工作量增加和许多小的特定容器遍历算法的扩散。
向后兼容性
在类型信息比平时更重要的那些情况下,仍然可以通过显式调用 repr
来获得当前的结果。
参考文献
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3140.rst