前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python 魔术方法(三)对象的打印 -- __repr__ 与 __str__

python 魔术方法(三)对象的打印 -- __repr__ 与 __str__

作者头像
用户3147702
发布2022-06-27 13:24:26
4160
发布2022-06-27 13:24:26
举报
文章被收录于专栏:小脑斧科技博客

1. 引言

上一篇文章中,我们介绍了 Python 的对象创建和初始化的两个方法。 python 魔术方法(二) 对象的创建与单例模式的实现

但有另外两个常用的魔术方法也一样困扰着很多 Python 程序员,那就是本文将介绍的用于对象字符串化的两个方法 — __repr__ 和 __str__ 你一定会疑惑,为什么 Python 与其他很多编程语言有如此不同 — 对象的字符串输出方法为什么会有两个?别急,本文就将为你答疑解惑。

2. 对象的打印

我们来看一个示例:

代码语言:javascript
复制
import logging

logging.basicConfig(level=logging.INFO,
        format='%(asctime)s - [line:%(lineno)d] - %(levelname)s: %(message)s')

class TechlogTest:
    def __str__(self):
        return "TechlogTest.__str__"

    def __repr__(self):
        return "TechlogTest.__repr__"

if __name__ == '__main__':
    testobj = TechlogTest()
    print('%%r: %r; %%s: %s' % (testobj, testobj))
    logging.info(testobj)
    print(testobj)
    print([testobj])

打印出了:

%r: TechlogTest.__repr__; %s: TechlogTest.__str__ 2019-04-07 20:56:59,083 - [line:19] - INFO: TechlogTest.__str__ TechlogTest.__str__ [TechlogTest.__repr__]

事实上,上面的例子已经展现了两个方法设计原则上的不同:

  • %r 设计用来展示对象的细节,此时调用的是 __repr__ 方法
  • %s 用来为用户展示友好的可读信息,这与 str 方法以及 logging 打印日志的目的一致,此时调用的是 __str__ 方法
  • 容器的 __str__ 方法调用的是每一个成员的 __repr__ 方法

3. 默认的行为

3.1. 默认实现

如果我们没有实现两个方法的任何一个,会打印出什么呢?

代码语言:javascript
复制
import logging

logging.basicConfig(level=logging.INFO,
                            format='%(asctime)s - [line:%(lineno)d] - %(levelname)s: %(message)s')

class TechlogTest:
    # def __str__(self):
    #     return "TechlogTest.__str__"
    #
    # def __repr__(self):
    #     return "TechlogTest.__repr__"
    pass

if __name__ == '__main__':
    testobj = TechlogTest()
    print('%%r: %r; %%s: %s' % (testobj, testobj))
    logging.info(testobj)
    print(testobj)
    print([testobj])

打印出了:

%r: <__main__.TechlogTest object at 0x0000025423AD1AC8>; %s: <__main__.TechlogTest object at 0x0000025423AD1AC8> <__main__.TechlogTest object at 0x0000025423AD1AC8> [<__main__.TechlogTest object at 0x0000025423AD1AC8>]

我们看到,默认的实现方式通常不是我们想要的,他仅仅展示了对象在内存中的逻辑地址。

3.2. 只实现 __str__

代码语言:javascript
复制
import logging

logging.basicConfig(level=logging.INFO,
                            format='%(asctime)s - [line:%(lineno)d] - %(levelname)s: %(message)s')

class TechlogTest:
    def __str__(self):
        return "TechlogTest.__str__"

    # def __repr__(self):
    #     return "TechlogTest.__repr__"
    pass

if __name__ == '__main__':
    testobj = TechlogTest()
    print('%%r: %r; %%s: %s' % (testobj, testobj))
    logging.info(testobj)
    print(testobj)
    print([testobj])

输出了:

%r: <__main__.TechlogTest object at 0x000002B3CFC99B70>; %s: TechlogTest.__str__ 2019-04-07 21:01:30,321 - [line:20] - INFO: TechlogTest.__str__ TechlogTest.__str__ [<__main__.TechlogTest object at 0x000002B3CFC99B70>]

我们看到,原本需要调用 __repr__ 变成了系统的默认实现。

3.3. 只实现 __repr__

代码语言:javascript
复制
import logging

logging.basicConfig(level=logging.INFO,
                            format='%(asctime)s - [line:%(lineno)d] - %(levelname)s: %(message)s')

class TechlogTest:
    # def __str__(self):
    #     return "TechlogTest.__str__"

    def __repr__(self):
        return "TechlogTest.__repr__"
    pass

if __name__ == '__main__':
    testobj = TechlogTest()
    print('%%r: %r; %%s: %s' % (testobj, testobj))
    logging.info(testobj)
    print(testobj)
    print([testobj])

打印出了:

%r: TechlogTest.__repr__; %s: TechlogTest.__repr__ 2019-04-07 21:02:37,489 - [line:20] - INFO: TechlogTest.__repr__ TechlogTest.__repr__ [TechlogTest.__repr__]

所有需要调用 __str__ 的地方都改为调用了我们实现的 __repr__

4. 结论

我们看到,默认的方式通常并不是我们想要的,而如果我们只实现了 __repr__,那么所有需要使用 __str__ 的场景都会去调用 __repr__ 因此,实践中的建议是,为每个类都实现 __repr__ 方法,只为那些用于为用户展示友好信息的类实现 __str__ 方法。 例如下面定义的 IP 类,__repr__ 方法用于在 log 等场景中打印类内成员的详情,而 __str__ 则用于将 IP 值转化为点分十进制方式用于友好的输出。

代码语言:javascript
复制
class TechlogIP:
    def __init__(self, ip):
        self._ip = ip

    def __repr__(self):
        return 'TechlogIP(ip: %r)' % self._ip

    def __str__(self):
        """
        数字 IP 转换为点分十进制形式
        :return: 点分十进制 IP 字符串
        """
        ip = ''
        t = 2 ** 8
        dec_value = self._ip
        for _ in range(4):
            v = dec_value % t
            ip = '.' + str(v) + ip
            dec_value = dec_value // t
        ip = ip[1:]
        return ip

5. 无限递归

与此前我们介绍的几个魔术方法一样,由于其回调的特性 __repr__ 与 __str__ 两个方法也存在着循环递归的可能。

代码语言:javascript
复制
class TechlogTestA:
    def __init__(self, obj):
        self.obj = obj

    def __repr__(self):
        return 'TechlogTestA(obj: %r)' % self.obj

class TechlogTestB:
    def __init__(self, obj):
        self.obj = obj

    def __repr__(self):
        return 'TechlogTestB(ip: %r)' % self.obj

if __name__ == '__main__':
    testA = TechlogTestA(None)
    testB = TechlogTestB(testA)
    testA.obj = testB
    print("\%r: %r" % testA)

抛出了异常:

RecursionError: maximum recursion depth exceeded

上面的例子中,两个类的成员相互引用,解释器循环调用他们的 __repr__ 方法,这种问题是需要格外注意和避免的。

6. 参考资料

《流畅的 python》。 https://stackoverflow.com/questions/1436703/difference-between-str-and-repr。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 对象的打印
  • 3. 默认的行为
    • 3.1. 默认实现
      • 3.2. 只实现 __str__
        • 3.3. 只实现 __repr__
        • 4. 结论
        • 5. 无限递归
        • 6. 参考资料
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档