【问题标题】:How can I make my class pretty printable in Python?如何使我的课程在 Python 中非常可打印?
【发布时间】:2017-04-11 05:21:30
【问题描述】:

Python 有一个漂亮的打印机 (pprint(...))。我想让我的课程非常可打印。如果我提供某个接口,漂亮打印会以更好的方式打印我的实例吗?

8.11 部分中的 Python 文档显示了不同的示例,但没有示例如何使用户定义的类可打印。

那么我的类需要提供什么接口呢?
还有其他(可能更好)的格式化程序吗?


用例:

我想漂亮地打印ConfigParser 的内容,为此我创建了一个名为ExtendenConfigParser 的扩展版本。所以我有可能添加更多功能或添加匹配的漂亮打印界面。

【问题讨论】:

  • “漂亮印刷”类是什么意思?喜欢听听吗?
  • pprint(config) 只给出<lib.ExtendedConfigParser.ExtendedConfigParser object at 0x0000000003569940>。内部数据结构是两个嵌套的有序字典。我想将它们打印为 2 个嵌套的字典。我可以为这项工作编写一个函数,但我想要一个方法和/或 pprint 兼容的类。
  • 我不认为pprint 提供该功能。但是,您可以为您的类提供一个__format__ 方法(除了__repr____str__ 方法),以便在传递给format 内置函数或str.format 方法。

标签: python python-3.x formatting pretty-print


【解决方案1】:

pprint 不寻找任何钩子。 pprint.PrettyPrinter 使用 dispatch 模式 代替;以class.__repr__ 引用为键的类上的一系列方法。

您可以继承pprint.PrettyPrinter 来教授您的课程:

class YourPrettyPrinter(pprint.PrettyPrinter):
    _dispatch = pprint.PrettyPrinter._dispatch.copy()

    def _pprint_yourtype(self, object, stream, indent, allowance, context, level):
        stream.write('YourType(')
        self._format(object.foo, stream, indent, allowance + 1,
                     context, level)
        self._format(object.bar, stream, indent, allowance + 1,
                     context, level)
        stream.write(')')

    _dispatch[YourType.__repr__] = _pprint_yourtype

然后直接使用该类来漂亮地打印包含YourType 实例的数据。请注意,这取决于具有自己自定义 __repr__ 方法的类型!

您也可以将函数直接插入PrettyPrinter._dispatch 字典; self 是显式传入的。这可能是第 3 方库的更好选择:

from pprint import PrettyPrinter

if isinstance(getattr(PrettyPrinter, '_dispatch'), dict):
     # assume the dispatch table method still works
     def pprint_ExtendedConfigParser(printer, object, stream, indent, allowance, context, level):
         # pretty print it!
     PrettyPrinter._dispactch[ExtendedConfigParser.__repr__] = pprint_ExtendedConfigParser

请参阅pprint module source code 了解其他调度方法的编写方式。

与往常一样,像_dispatch 这样的单下划线名称是可以在未来版本中更改的内部实现细节。但是,这是您在这里的最佳选择。调度表已添加in Python 3.5,并且至少存在于 Python 3.5 - 3.9 alpha 中。

【讨论】:

  • 如果我的类已经使用(多)继承,您提出的解决方案似乎没有害处,对吧?
  • @Paebbels:请确保有一个唯一的 __repr__ 方法可以键入,否则我认为没有潜在的危害。
  • ConfigParser 类抽象 INI 配置文件。如果我还想打印 INI 表示格式,那么我应该使用__format__,对吗?因为pprint(...) 以 Python 可读表示为目标。
  • @Paebbels 您必须尝试如何将其与 pprint 输出集成(例如,给定缩进和余量值)。
  • @RickGraves:它仍然存在,即使在后备箱中:github.com/python/cpython/blob/…,但我在代码中有一个小错误,现在已更正。 Python 2 从来没有。
【解决方案2】:

这并不是一个真正的解决方案,但我通常只是让对象可序列化并像这样漂亮地打印它们:

pprint(obj.dict())

【讨论】:

    【解决方案3】:

    Martijn Pieters 的子类解决方案对我有用,我通过不对 foo 和 bar 进行硬编码使其更通用。

    取出:

        self._format(object.foo, stream, indent, allowance + 1,
                     context, level)
        self._format(object.bar, stream, indent, allowance + 1,
                     context, level)
    

    代入(放入):

        for s in vars(object):
            stream.write( '\n%s: ' % s )
            self._format( object.__dict__[s],
                          stream, indent, allowance + 1, context, level )
    

    【讨论】:

      【解决方案4】:

      如果您要全力以赴,不妨将pprint 超类化来接受一个钩子,那么您只需编写所有代码一次。

      在你用pp = pprint.PrettyPrinter(indent=4).pprint(我的一个坏习惯)实例化一个 pprint 助手之后定义你的类的情况下,它也会更好地工作。

      然后,您可以使用其中一种方法选择加入任何课程 [双关语不是有意的]

      [edit]:经过一些自用,我意识到了一个更简单的替代解决方案, __pprint_repr__。而不是尝试创建自己的 pprint 函数, 只需定义__pprint_repr__ 方法并返回一个标准的python 对象。 如果您有一个复杂的类,您可以将多个对象分组到 dict 中。

      [edit #2]:我还意识到将所有 _format 变量传递给 __pprint_repr__ 函数会很有用,因为这可以让你做一些非常酷的事情——比如显示一个压缩的如果您的项目在列表中(缩进 > 0),则输出;如果它是唯一的对象,则输出完整(缩进 == 0)

      这也意味着这个解决方案兼容 Python 2.7,而不仅仅是 Python ~> 3.3

      class my_object(object):
      
          # produce pprint compatible object, easy as pie!
          def __pprint_repr__(self, **kwargs):
              return self.__dict__
          
          # make a multi-level object, easy as cheese-cake!
          def __pprint_repr__(self, **kwargs):
              _indent = kwargs['indent']
              if _indent:
                  return self._toText()
              return { self._toText(): self.__dict__ }
      
          # to take total control (python 3) (requires __repr__ be defined)
          def __pprint__(self, object, stream, indent, allowance, context, level):
              stream.write('my_object(\n')
              self._format(object._data, stream, indent, allowance + 1, context, level)
              stream.write(')')
              pass
      

      子类本身很简单——在 Python 3.7 和 2.7 中测试:

              if _pprint_repr:
                  return PrettyPrinter._format(self, _pprint_repr(object, stream=stream, 
                      indent=indent, allowance=allowance, context=context, level=level), 
                          stream, indent, allowance, context, level)
      
              # else check for alternate _pprint method (if supported ~ python 3.3)
              if getattr(PrettyPrinter, '_dispatch', None):
                  _repr = type(object).__repr__
                  _pprint = getattr(type(object), '__pprint__', None)
                  _exists = self._dispatch.get(_repr, None)
                  if not _exists and _pprint:
                      self._dispatch[_repr] = _pprint
      
              return PrettyPrinter._format(self, object, stream, indent, allowance, context, level)
      

      【讨论】:

      • 你不应该用双下划线命名你自己的方法(“dunder 方法”)——这个约定表示它的内置方法可以做一些透明的“魔法”。此外,两个前导下划线会无意中触发名称修改。
      • 换句话说,__this__ 表示某种外力会以某种方式作用于该属性,魔法就会发生。在这种情况下,外力是 PrettyPrinter,神奇的是格式化输出。尽管您的规则是合理的,但这是一个“特殊情况”。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-10-10
      • 2010-12-01
      • 1970-01-01
      • 2022-11-30
      • 2017-05-26
      • 2022-01-08
      相关资源
      最近更新 更多