上一篇文章中,我们介绍了 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__]
事实上,上面的例子已经展现了两个方法设计原则上的不同:
如果我们没有实现两个方法的任何一个,会打印出什么呢?
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>]
我们看到,默认的实现方式通常不是我们想要的,他仅仅展示了对象在内存中的逻辑地址。
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__ 变成了系统的默认实现。
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。