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

abe*_*bey 0 python repr

我有一个自定义的 Python 类,它本质上封装了list某种对象,我想知道我应该如何实现它的__repr__功能。我很想采用以下方法:

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

   def __repr__(self):
      return f"MyCollection({self._objects})"
Run Code Online (Sandbox Code Playgroud)

这具有生成完整描述类实例的有效 Python 输出的优点。然而,在我的真实案例中,对象列表可能相当大,每个对象本身可能有一个很大的代表(它们本身就是数组)。

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

小智 6

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

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

这通常用于调试,因此表示信息丰富且明确是很重要的。

https://docs.python.org/3/reference/datamodel.html#object.\_\_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})"
Run Code Online (Sandbox Code Playgroud)

您通常希望避免使用可变对象作为默认参数。从技术上讲,由于您的方法是使用扩展(它制作列表的副本)实现的方式,它仍然可以正常工作,但 Python 的文档仍然建议您避免这种情况。

不使用可变对象作为默认值是一种很好的编程习惯。相反,使用 None 作为默认值,在函数内部,检查参数是否为 None 并创建一个新的列表/字典/不管它是什么。

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

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

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

要模仿这种 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])'
          
Run Code Online (Sandbox Code Playgroud)