Python 类的__repr__方法转换字符串,在Python中定义一个自定义类之后,如果尝试在控制台中输出其实例或在解释器会话中查看,并不能得到十分令人满意的结果。默认的“转换成字符串”功能非常原始,缺少细节:
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
>>> my_car = Car('red', 37281)
>>> print(my_car)
<__console__.Car object at 0x109b73da0>
>>> my_car
<__console__.Car object at 0x109b73da0>
默认情况下得到的是一个包含类名和对象实例id
的字符串(这是CPython中对象的内存地址)。虽然比什么都没有要好,但也没什么用。
你可能会直接打印类的属性,或者向类中添加自定义的to_string()
方法来绕过这个问题:
>>> print(my_car.color, my_car.mileage)
red 37281
这个总体思路是正确的,但是忽略了Python将对象转成字符串的约定和内置机制。
因此不要构建自己的字符串转换机制,而是向类中添加双下划线方法__str__
和__repr__
。这两个方法以具有Python特色的方式在不同情况下将对象转换为字符串。
来看看这些方法在实践中是如何工作的。首先为前面的Car
类增加一个__str__
方法:
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __str__(self):
return f'a {self.color} car'
在尝试打印或查看Car
实例时,得到的结果稍许有些改进:
>>> my_car = Car('red', 37281)
>>> print(my_car)
'a red car'
>>> my_car
<__console__.Car object at 0x109ca24e0>
在控制台中查看Car
对象得到的依然是之前包含对象id
的结果,但是打印对象就会得到由新添加的__str__
方法返回的字符串。
__str__
是Python中的一种双下划线方法,尝试将对象转换为字符串时会调用这个方法:
>>> print(my_car)
a red car
>>> str(my_car)
'a red car'
>>> '{}'.format(my_car)
'a red car'
有了合适的__str__
实现,打印对象时就不用担心会直接打印对象属性,也不用编写单独的to_string()
函数了。这就是具有Python特色的字符串转换方法。
顺便说一下,有些人把Python的双下划线方法称为“魔法方法”。不过这些方法并没有什么神奇之处,以双下划线起始只是一个命名约定,表示这些方法是Python的核心特性。除此之外,加上双下划线还有助于避免与自己的方法和属性同名。对象构造函数__init__
就遵循这样的约定,其中并没有什么神奇或神秘的地方。
因此不要害怕使用Python双下划线方法,这些方法大有用途。
Python 类的repr方法转换字符串 __str__
与__repr__
我们继续来看字符串转换的问题。前一节中,如果在解释器会话中查看my_car
,仍然会得到奇怪的<Car object in 0x109ca24e0>
。
发生这种情况是因为在Python 3中实际上有两个双下划线方法能够将对象转换为字符串。第一个是刚刚介绍的__str__
;第二个是__repr__
,其工作方式类似于__str__
,但用于其他情形。
这里做一个简单的实验,你可以从中了解__str__
和__repr__
的使用场景。下面重新定义Car
类,添加这两个用来转换成字符串的双下划线方法,用于产生不同的输出:
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __repr__(self):
return '__repr__ for Car'
def __str__(self):
return '__str__ for Car'
在尝试上面的示例时,可以看到每个方法生成了对应的字符串转换结果:
>>> my_car = Car('red', 37281)
>>> print(my_car)
__str__ for Car
>>> '{}'.format(my_car)
'__str__ for Car'
>>> my_car
__repr__ for Car
从实验可以得知,在Python解释器会话中查看对象得到的是对象的__repr__
结果。
有趣的是,像列表和字典这样的容器总是使用__repr__
的结果来表示所包含的对象,哪怕对容器本身调用str()
也是如此:
str([my_car])
'[__repr__ for Car]'
为了手动选择其中一种字符串转换方法,比如要更清楚地表示代码的意图,最好使用内置的str()
和repr()
函数。使用这两个函数比直接调用对象的__str__
或__repr__
要好,因为结果相同但更美观:
>>> str(my_car)
'__str__ for Car'
>>> repr(my_car)
'__repr__ for Car'
介绍完__str__
和__repr__
之后,你可能想知道它们各自在实际使用中的差异。这两个函数的目的看上去相同,因此你可能对其各自的使用场景感到费解。
对于这样的问题,可以看看Python标准库是怎么做的。现在再设计一个实验,创建一个datetime.date
对象,看这个对象如何使用__repr__
和__str__
来控制字符串转换:
>>> import datetime
>>> today = datetime.date.today()
在date
对象上,__str__
函数的结果侧重于可读性,旨在为人们返回一个简洁的文本表示,以便放心地向用户展示。因此在date
对象上调用str()
时,得到的是一些看起来像ISO日期格式的东西:
>>> str(today)
'2017-02-02'
__repr__
侧重的则是得到无歧义的结果,生成的字符串更多的是帮助开发人员调试程序。为此需要尽可能明确地说明这个对象是什么,因此在对象上调用repr()
会得到相对更复杂的结果,其中甚至包括完整的模块和类名称:
>>> repr(today)
'datetime.date(2017, 2, 2)'
复制并粘贴由这个__repr__
返回的字符串,可以作为有效的Python语句重新创建原date
对象。这种方式很不错,在编写自己的repr
时值得借鉴。
然而我发现这个模式实践起来相当困难,通常不值得这么做,因为这只会带来额外的工作。我的经验法则是只要让__repr__
生成的字符串对开发人员清晰且有帮助就可以了,并不需要能从中恢复对象的完整状态。
Python 类的repr方法转换字符串 __repr__
如果不提供__str__
方法,Python在查找__str__
时会回退到__repr__
的结果。因此建议总是为自定义类添加__repr__
方法,这只需花费很少的时间,但能保证在几乎所有情况下都能得到可用字符串转换结果。
下面介绍如何快速高效地为自定义类添加基本的字符串转换功能。对于前面的Car
类,首先添加一个__repr__
:
def __repr__(self):
return f'Car({self.color!r}, {self.mileage!r})'
这里使用
!r
转换标志来确保输出字符串使用的是repr(self.color)
和repr(self.mileage)
,而不是str(self.color)
和str(self.mileage)
。
虽然这样能正常工作,但缺点是在格式字符串中硬编码了类名称。有一种技巧能避免这种硬编码,即使用对象的__class__.__name__
属性来获得以字符串表示的类名称。
这样做的好处是在改变类名称时,不必修改__repr__
的实现,因此能更好地遵循“不要重复自己”(DRY)原则:
def __repr__(self):
return (f'{self.__class__.__name__}('
f'{self.color!r}, {self.mileage!r})')
其缺点是格式字符串非常长且笨拙。但如果仔细设置格式,依然能保持良好的代码形式,并符合PEP 8规范。
有了上面的__repr__
实现,在直接查看对象或调用repr()
时能够得到有用的结果:
>>> repr(my_car)
'Car(red, 37281)'
打印对象或调用str()
会返回相同的字符串,因为默认的__str__
实现只是简单地调用__repr__
:
>>> print(my_car)
'Car(red, 37281)'
>>> str(my_car)
'Car(red, 37281)'
我认为这是一种标准方法,能做到事半功倍,并且在大部分情况下可以直接使用。因此,我总是会为自定义类添加一个基本的__repr__
实现。
下面是一个针对Python 3的完整示例,其中还包括可选的__str__
实现:
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'
Python 类的repr方法转换字符串 与__unicode__
的差异
在Python 3中使用str
数据类型表示文本,其中用到了unicode字符,可以表示世界上大部分书写系统。
使用不同的数据模型来表示字符串。有两种类型可以表示文本:str
(仅限于ASCII字符集)和unicode(等同于Python 3的str
)
由于这种差异,Python 2中还有另一种双下划线方法能够控制字符串转换:__unicode__
。在Python 2中,__str__
返回字节,而__unicode__
返回字符。
大多数情况下应优先使用新的__unicode__
方法转换字符串。同时还有一个内置的unicode()
函数,该函数会调用相应的双下划线方法,与str()
和repr()
的工作方式相似。
到目前为止还不错,但Python 2中调用__str__
和__unicode__
的规则非常古怪:print
语句和str()
调用__str__
;内置的unicode()
先调用__unicode__
,若没有__unicode__
则回退到__str__
,此时使用系统文本编码对结果进行解码。
与Python 3相比,这些特殊情况让文本转换规则变得更加复杂。不过能针对实际情况进一步简化。unicode是Python程序中处理文本的首选,同时也是趋势。
所以一般情况下,建议将所有字符串格式化代码放入__unicode__
方法中,然后创建一个__str__
存根实现,返回以UTF-8编码的unicode表示形式:
def __str__(self):
return unicode(self).encode('utf-8')
大多数自定义类中的__str__
存根函数都是相同的,所以可以根据需要复制和粘贴(或者放到一个基类中)。所有生成针对非开发人员的字符串转换代码都应位于__unicode__
中。
下面是一个针对python 2.x的完整示例:
class Car(object):
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __repr__(self):
return '{}({!r},
{!r})'.format( self.__class__.__name__,
self.color, self.mileage)
def __unicode__(self):
return u'a {self.color}
car'.format( self=self)
def __str__(self):
return unicode(self).encode('utf-8')
关键要点
-
使用
__str__
和__repr__
双下划线方法能够自行控制类中的字符串转换。 -
__str__
的结果应该是可读的。__repr__
的结果应该是无歧义的。 -
总是为类添加
__repr__
。__str__
默认情况下会调用__repr__
。 -
在Python 2中使用
__unicode__
而不是__str__
。