Python集合类中的__repr__最佳实践是什么?

11

我有一个自定义的Python类,本质上封装了一种对象的list,我想知道该如何实现它的__repr__函数。我倾向于采用以下方式:

class MyCollection:
   def __init__(self, objects = []):
      self._objects = []
      self._objects.extend(objects)

   def __repr__(self):
      return f"MyCollection({self._objects})"

这样做的优点是可以生成一个有效的Python输出,完整描述类实例。然而,在我的实际情况中,对象列表可能会相当大,并且每个对象本身可能都有一个很大的repr(它们本身就是数组)。

在这种情况下,最佳实践是什么?接受repr经常是一个非常长的字符串吗?这方面是否存在潜在问题(调试器UI等)?我应该使用分号实现某种缩短方案吗?如果是这样,有没有一个良好/标准的方法来实现这一点?或者我应该完全跳过列出集合的内容?


2
你是否曾经看到过一个内置的容器类型,可以做你正在考虑将你的容器改变成为的任何一种功能? - user2357112
@user2357112supportsMonica 嗯,list 确实会打印出所有内容。我还没有测试其他内置容器,但是 numpy 的 ndarray 会缩短它的 repr(我知道,不是标准库,但仍然...)。 - abey
repr 的目的并不是制造出看起来好看的东西,而是为了帮助调试。将缩短算法保存到 __str__ 或自定义方法中。 - chepner
1个回答

31

官方文档概述了如何处理 __repr__:

由 repr() 内置函数调用,计算对象的“官方”字符串表示形式。如果可能的话, 这应该看起来像一个有效的 Python 表达式,可以用于创建一个具有相同值的对象 (给定适当的环境)。如果不可能,应返回形如 <...some useful description...> 的字符串。返回值必须是字符串对象。如果一个类定义了 __repr__() 而没有定义 __str__(),那么在需要这个类实例的“非正式”字符串表示时也会使用 __repr__()。

通常用于调试,因此表示形式应具有信息丰富和明确不误的特点。

Python 3 __repr__ 文档

列表、字符串、集合、元组和字典在其 __repr__ 方法中打印出其整个集合。

您当前的代码看起来完全遵循文档所建议的示例。但我建议将您的 __init__ 方法更改为以下方式:

class MyCollection:
   def __init__(self, objects=None):
       if objects is None:
           objects = []
      self._objects = objects

   def __repr__(self):
      return f"MyCollection({self._objects})"

通常情况下,您应该避免将可变对象用作默认参数。从技术上讲,由于使用了extend方法(它会复制列表),所以它仍然可以正常工作,但是Python的文档仍建议您避免这样做。

好的编程实践是不要使用可变对象作为默认值。相反,使用None作为默认值,在函数内部检查参数是否为None,如果是,则创建一个新的列表/字典/任何其他对象。

https://docs.python.org/zh-cn/3/faq/programming.html#why-are-default-values-shared-between-objects

如果您对另一个库如何进行不同处理感兴趣,Numpy数组的repr仅在数组长度大于1,000时显示前三个和最后三个项。它还使所有项目都使用相同数量的空间进行格式化(在下面的示例中,1000占用四个空格,因此必须用三个更多的空格填充0以匹配)。

>>> repr(np.array([i for i in range(1001)]))
'array([   0,    1,    2, ...,  998,  999, 1000])'

要模仿这个numpy数组样式,您可以在您的类中实现一个像下面这样的__repr__方法:

要模仿这个 numpy 数组样式,您可以在您的类中实现一个像这样的 __repr__ 方法:
class MyCollection:
   def __init__(self, objects=None):
      if objects is None:
          objects = []
      self._objects = objects

   def __repr__(self):
       # If length is less than 1,000 return the full list.
      if len(self._objects) < 1000:
          return f"MyCollection({self._objects})"
      else:
          # Get the first and last three items
          items_to_display = self._objects[:3] + self._objects[-3:]
          # Find the which item has the longest repr
          max_length_repr = max(items_to_display, key=lambda x: len(repr(x)))
          # Get the length of the item with the longest repr
          padding = len(repr(max_length_repr))
          # Create a list of the reprs of each item and apply the padding
          values = [repr(item).rjust(padding) for item in items_to_display]
          # Insert the '...' inbetween the 3rd and 4th item
          values.insert(3, '...')
          # Convert the list to a string joined by commas
          array_as_string = ', '.join(values)
          return f"MyCollection([{array_as_string}])"

>>> repr(MyCollection([1,2,3,4]))
'MyCollection([1, 2, 3, 4])'

>>> repr(MyCollection([i for i in range(1001)]))
'MyCollection([   0,    1,    2, ...,  998,  999, 1000])'
          

1
我希望楼主接受这个答案,里面有很多好的信息。 - Michael Ruth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接