在学习类相关知识时,注意到了__str__()和__repr__()这两种方法,但对两者的异同不是很了解,因此写这篇博客记录一下学习过程,也供其他小伙伴参考。文中如有错误或者表述不当之处欢迎在评论区指出。
首先,__repr__()和__str__()都属于特殊方法(也称魔术方法,magic method),且作用都是根据对象的状态返回字符串。那两者的具体应用场景以及区别是什么呢?接下来一一进行介绍。
一、__str__()方法
1. 应用场景,即在什么时候会被调用
对象的__str__()方法在以下情况下会被调用:
- 使用print()打印一个对象;
- 将对象传入format();
- 将对象传入str.format();
- 将对象传入str();
注意:如果对象中定义了__str__()方法,则在上述情况下__str__()会被调用,否则将会调用__repr__()方法。
2. 默认实现
python中所有类默认会继承object类,object类中默认实现了__str__()和__repr__()方法,但由于python的源码是C语言,所以对于普通python用户来说阅读难度较大,在这里我们用python语言等价表示一下__str__()方法默认实现的原理:
# __repr__()默认实现同理
def __str__(self):
return '<{0}.{1} object at {2}>'.format(
type(self).__module__, type(self).__qualname__, hex(id(self)))
其中__module__()表示对象来自哪个模块,__qualname__()我们可以暂时理解为类的名字(__qualname__和__name__的异同感兴趣的伙伴可自行查阅)。
也就是说,object类中默认实现的__str__()方法会输出对象所在的模块、对象所属的类的名字以及对象在内存中的十六进制地址表示。
3. 实例分析
了解了__str__()的具体应用场景以及默认实现,接下来结合几个例子进一步理解一下具体的调用过程。
例子1:自定义类,但并未在类中实现__str__()方法。可以先回忆一下如果没有实现__str__()方法,情况会如何?
>>> class CustomClass: pass
...
>>> print(CustomClass()) # 调用print()方法
<__main__.CustomClass object at 0x000001BFB5E13DC0>
>>> str(CustomClass()) # 调用str()方法
'<__main__.CustomClass object at 0x000001BFB5E13DC0>'
结合上述例子,我们分析一下具体的调用过程。首先我们定义了一个名为CustomClass的类,然后我们分别将它的一个实例对象传入print()和和str()方法中,这两种方法都是调用对象的__str__()方法;但是由于CustomClass中没有实现__str__()方法,那么根据注意事项中提到的,会调用__repr__()方法,但显然CustomClass中也没有实现,最终就会调用基类object中的__repr__()方法。
注意:print()和str()两者最终调用的都是object中的__repr__()方法,__repr__()返回的是字符串,但print()将字符串打印在了屏幕上,而str()保留了字符串的格式。
例子2:自定义类,但实现了__str__()方法。
>>> class CustomClass:
... def __str__(self): return 'foo'
...
>>> print(CustomClass())
foo
>>> str(CustomClass())
'foo'
这个例子比较容易理解,就是调用了CustomClass类中实现的__str__()方法。
4. 目的
根据__str__()的具体使用场景我们可以看出,主要是print()、str()等函数会调用对象的__str__()方法,而print()、str()等函数的输出通常主要是给程序的使用者看的,且并非所有程序使用者都具有编程知识,所以__str__()的返回值一定是便于阅读的。这一点结合后面__repr__()的目的进行理解。
二、__repr__()方法
1. 应用场景
__repr__()方法调用场景主要有以下两种:
- 被内置函数repr()调用;
- 我们输入能返回一个对象的表达式时,回车之后在python shell中显示内容的过程会调用__repr__()方法。
- debugger时的变量表示
__repr__()作为__str__()的备用方法,如果你只能实现其中一个的话,建议实现__repr__()方法。
2. 默认实现
同__str__()。
3. 案例分析
例子1:自定义类,但没有实现__repr__()方法。
>>> class C: pass
...
>>> repr(C())
'<__main__.C object at 0x000001B277733DC0>'
>>> C()
<__main__.C object at 0x000001B277733DC0>
首先,我们自定义了一个名为C的类,在将C的实例对象传给repr()时,去调用C的__repr__()方法,但由于C中没有实现,最终调用的是基类object中的__repr__()方法。在解释器中输入C()回车后调用过程同理。
注意:当在控制台直接输入C()并按回车键时,调用__repr__()并得到一个字符串返回值,但为了提高可读性,解释器只显示了这个字符串的内容,没有加引号。
例子2:自定义类且实现__repr__()方法。
>>> class C:
... def __repr__(self): return 'call __repr__() method'
...
>>> repr(C())
'call __repr__() method'
>>> C()
call __repr__() method
4. 目的
根据__repr__()的应用场景——控制台输出、debugger等,可以看出,其输出的内容主要面向程序开发者,需要为开发者提供必要且有用的信息,包括但不限于对象对应的类名、对象属性值等。
我们结合一个例子直观感受一下为什么说__str__()主要面向程序使用者,而__repr__()主要面向程序开发者:
>>> import datetime
>>> datetime.datetime.now() # 调用__repr__()
datetime.datetime(2024, 11, 17, 22, 23, 18, 647770)
>>> print(datetime.datetime.now()) # 调用__str__()
2024-11-17 22:23:42.227722
因此对于__repr__()的返回值,我们希望其满足一定要求,具体来说就是:
eval(repr(object)) == object
其中eval()函数用来执行一个字符串表达式,并返回表达式的值。也就是说,一个对象__repr__()的返回值,如果在控制台输入这个返回值,可以得到一个对象实例。
结合例子具体理解一下:
# 定义一个表示二维向量的类
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
s = 'Vector(%r, %r)' % (self.x, self.y)
return s
在上述例子中,对于使用默认参数的对象实例Vector(),_repr__()返回值为‘Vector(0, 0)’,而eval()则会根据这个返回值再创建一个对象实例。
但__repr__()的默认实现并不满足上述要求,因此在编写大型项目时通常需要自行实现。
三、总结
最后总结一下__str__()和__repr__()两者的异同。
相同点:
- 都属于特殊方法;
- 返回值都是字符串;
- 在默认实现中,两者的返回值相同。
不同点:
- 应用场景不同,__str__()是当对象传入print()、str()、format()和str.format()时被调用,而__repr__()是对象传入repr()、控制台输出和debugger时被调用;
- 输出内容面向人群不同,__str__()返回值主要是给程序使用者看的,而__repr__()返回值是给程序开发者看的。