文章目录
使用场景
很多时候我们自己编写一个类,在将它的实例在终端上打印或查看的时候,我们往往会看到一个不太满意的结果。
>>> class Car:
... def __init__(self, color, mileage):
... self.color = color
... self.mileage = mileage
...
>>> my_car = Car('red', 37281)
>>> print (my_car)
<__main__.Car object at 0x000001BEDBDDEAC8>
>>> my_car
<__main__.Car object at 0x000001BEDBDDEAC8>
类默认转化的字符串基本没有我们想要的一些东西,仅仅包含了类的名称以及实例的 ID (理解为 Python 对象的内存地址即可)。虽说这总比没有好,但确实是没什么用处啊。
所以,我们可能会手动打印对象的一些属性或者是在类里自己实现一个方法来返回我们需要的信息。
这没有什么不对的地方,但是我们可以使用更 Pythonic 的方式来解决这个问题。
使用 str 实现类到字符串的转化
不用自己另外定义一个方法,和 JAVA 的 toString() 方法类似,你可以在类里实现__str__ 和 __repr__ 方法从而自定义类的字符串描述,这两种都是比较 Pythonic 的方式去控制对象转化为字符串的方式。
下面我们通过做实验慢慢的来看这两种方式是怎么工作的。首先,我们先加一个 str 方法到前面的类中看看情况。
>>> class Car:
... def __init__(self, color, mileage):
... self.color = color
... self.mileage = mileage
... def __str__(self):
... return f'a {self.color} car'
...
当你重新打印和查看这个类的实例的时候,你会看到一个稍微不同的结果
>>> my_car = Car('red', 37281)
>>> print(my_car)
a red car
>>> my_car
<__main__.Car object at 0x000001BEDBDDEC18>
查看 my_car 的时候的输出仍然和之前一样,不过打印 my_car 的时候返回的内容和新加的 __str__ 方法的返回一致。类的 __str__ 方法会在某些需要将对象转为字符串的时候被调用。比如下面这些情况
>>> print(my_car)
a red car
>>> str(my_car)
'a red car'
>>> '{}'.format(my_car)
'a red car'
有了 __str__ 这个方法,你就不用手动去打印对象的一些信息或者添加额外的方法去达到目的。类到字符串的转化使用 str 这种 Pythonic 的方式实现即可。
使用 repr 也有类似的效果
有的朋友可能发现,上面我们查看 my_car 对象的时候,输出的仍是类似 <main.Car object at 0x10b142128> 这样比较奇怪的结果。这是因为 Python 3 中一共有 2 中方式控制类到字符串的转化,第一种就是我们前面提到的 str 方法,另一个就是 repr 方法。后者的工作方式与前者类似,但是它被调用的时机不同。
这里有个简单的例子,同样是在之前的类上作改动
>>> class Car:
... def __init__(self, color, mileage):
... self.color = color
... self.mileage = mileage
... def __str__(self):
... return '__str__ for car'
... def __repr__(self):
... return '__repr__ for car'
...
我们通过下面的操作来感觉下什么时候调用 __str__ ,什么时候调用的__repr__ 。
>>> my_car = Car('red', 37281)
>>> print(my_car)
__str__ for car
>>> '{}'.format(my_car)
'__str__ for car'
>>> my_car
__repr__ for car
从上面可以看出,当我们查看对象的时候(上面的最后一个操作)调用的是 __repr__ 方法。
另外,列表以及字典等容器总是会使用 __repr__ 方法。即使你显式的调用 str 方法,也是如此。
>>> str([my_car])
'[__repr__ for car]'
如果我们需要显示的指定以何种方式进行类到字符串的转化,我们可以使用内置的 str() 或 repr() 方法,它们会调用类中对应的双下划线方法。(当然,上面的情况除外)
>>> str(my_car)
'__str__ for car'
>>> repr(my_car)
'__repr__ for car'
当然,如果你直接调用 str 或 repr 方法,也能达到同样的方法,但是不推荐这么做。
str 和 repr 的差别
现在你可能在想,__str__ 和 __repr__ 的差别究竟在哪里,它们的功能都是实现类到字符串的转化,它们的特定并没有体现出用途上的差异。
带着这个这个问题,我们试着去 Python 的标准库中找找答案。我们就来看看 datetime.date 这个类是怎么在使用这两个方法的。
>>> today = datetime.date.today()
>>> today
datetime.date(2019, 4, 30)
>>> str(today)
'2019-04-30'
>>> repr(today)
'datetime.date(2019, 4, 30)'
因此,我们有个初步的答案。
__str__ 的返回结果可读性强。也就是说,__str__ 的意义是得到便于人们阅读的信息,就像上面的 ‘2018-04-03’ 一样。
__repr__ 的返回结果应更准确。怎么说,__repr__ 存在的目的在于调试,便于开发者使用。细心的读者会发现将 __repr__ 返回的方式直接复制到命令行上,是可以直接执行的。
上面应该就是这两个方法的意义所在吧(便于描述,后面我称这为通常的原则吧)。
但是于个人来说,如果按照通常的原则去编写代码会做很多额外的工作,两个方法的返回结果只需要对开发者友好就可以了,并不一定需要存储某个对象的完整状态。后面我会根据这一点,写部分有实践意义的代码实例,并不完全按照通常的原则。
为什么每个类都最好有一个 repr 方法
如果你没有添加 str 方法,Python 在需要该方法但找不到的时候,它会去调用 repr 方法。因此,我推荐在写自己的类的时候至少添加一个 repr 方法,这能保证类到字符串始终有一个有效的自定义转换方式。
我们为 Car 类添加一个 repr 方法
>>> class Car:
... def __init__(self, color, mileage):
... self.color = color
... self.mileage = mileage
... def __repr__(self):
... return (f'{self.__class__.__name__}('
... f'{self.color!r}, {self.mileage!r})')
... def __str__(self):
... return f'a {self.color} car'
注意,我们这里用了 !r 标记,是为了保证 self.color 与 self.mileage 在转化为字符串的时候使用 repr(self.color) 和 repr(self.mileage) ,而不是 str(self.color) 和 str(self.mileage) 。
这个能正常工作,不过有个缺点,就是我们把类的名称写死了。这有一个小技巧可以改进这种方式,就是使用对象的 __class__.__name__ 属性,该属性总代表着类的名称。
这样做的话,当类名被修改的时候,我们不需要修改 __repr__ 方法,这也符合软件开发的 DRY 原则( Don’t Repeat Yourself )。
这种写法也有一个不好的地方,就是格式化字符串太长了。当然,我们好好调整一个格式也能符合 PEP 8 的代码规范。
实现了 __repr__ 方法后,当我们查看类的实例或者直接调用 repr() 方法,就能得到一个比较满意的结果了。
打印或直接调用 str() 方法也能得到相同的结果,因为 __str__ 的默认实现就是调用 __repr__ 方法。
>>> my_car = Car('red', 37281)
>>> print(my_car)
a red car
>>> str(my_car)
'a red car'
>>> my_car
Car('red', 37281)
>>> repr(my_car)
"Car('red', 37281)"
小结
- 我们可以使用 __str__ 和 __repr__ 方法定义类到字符串的转化方式,而不需要手动打印某些属性或是添加额外的方法。
- 一般来说,__str__ 的返回结果在于强可读性,而 __repr__ 的返回结果在于准确性。
- 我们至少需要添加一个 __repr__ 方法来保证类到字符串的自定义转化的有效性,__str__ 是可选的。因为默认情况下,在需要却找不到 __str__ 方法的时候,会自动调用 __repr__ 方法。