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

2019-04-07 21:32:40   最后更新: 2019-04-07 21:32:40   访问数量:242




上一篇文章中,我们介绍了 Python 的对象创建和初始化的两个方法

python 魔术方法(二) 对象的创建与单例模式的实现

 

但有另外两个常用的魔术方法也一样困扰着很多 Python 程序员,那就是本文将介绍的用于对象字符串化的两个方法 -- __repr__ 和 __str__

你一定会疑惑,为什么 Python 与其他很多编程语言有如此不同 -- 对象的字符串输出方法为什么会有两个?别急,本文就将为你答疑解惑

 

 

 

我们来看一个示例:

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__ 方法

 

默认实现

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

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>]

 

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

 

只实现 __str__

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__ 变成了系统的默认实现

 

只实现 __repr__

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__

 

我们看到,默认的方式通常并不是我们想要的,而如果我们只实现了 __repr__,那么所有需要使用 __str__ 的场景都会去调用 __repr__

因此,实践中的建议是,为每个类都实现 __repr__ 方法,只为那些用于为用户展示友好信息的类实现 __str__ 方法

例如下面定义的 IP 类,__repr__ 方法用于在 log  等场景中打印类内成员的详情,而 __str__ 则用于将 IP 值转化为点分十进制方式用于友好的输出

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

 

 

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

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__ 方法,这种问题是需要格外注意和避免的

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,全部原创,只有干货没有鸡汤

 

 

《流畅的 python》

https://stackoverflow.com/questions/1436703/difference-between-str-and-repr

 






技术帖      python      string      面向对象      对象      魔术方法     


京ICP备15018585号