在Python中,下划线用于属性名时具有特殊含义。一种特殊形式是使用两对双下划线,一个在属性名之前,另一个在属性名之后,这被称为特殊方法或魔术方法。
例如,我们大多数人知道的第一个特殊方法可能是初始化方法__init__
,它用于创建Python对象。下面的代码显示了一个示例:
class Student:
def __init__(self, name):
self.name = name
当你创建这个类的实例对象时,你可以通过在交互式控制台中输入对象变量来检查这个对象:
>>> student0 = Student("John Smith")
>>> student0
<__main__.Student at 0x11883aa60>
然而,这个对象的信息并不是很友好,它只是显示了它的类和内存地址。为了使它更有趣,我们应该考虑定制类的字符串格式化方法。具体来说,我们将在本文中讨论三种特殊的方法。
__repr__
方法与对象的表示相关,将对象转化为供解释器读取的形式。如上所示,Python对象在交互式控制台中输出其表示字符串。下面的代码显示了这个特性:
>>> class Student:
... def __init__(self, name):
... self.name = name
...
... def __repr__(self):
... return "__repr__ is called"
...
>>> student0 = Student("John Smith")
>>> student0
__repr__ is called
需要注意的一点是,我们应该为__repr__
特殊方法返回一个字符串。如果返回一个非字符串,就会遇到TypeError,如下图所示。
但是尚未解决的问题是,我们应该为对象返回什么字符串?根据经验来说,我们应该返回一个字符串,用户可以使用它重新构造对象(例如,创建一个与被检查对象具有相等值的对象)。
class Student:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"{self.__class__.__name__}({self.name!r})"
使用更新后的代码,我们可以在交互式控制台中检查对象,如下所示。如你所见,表示字符串是一个有效的Python表达式,我们可以使用它创建一个Student对象:
>>> student0 = Student("John Smith")
>>> student0
Student('John Smith')
同样,我们可以使用内置的repr()方法来检索表示字符串。因为我们可能希望通过将字符串表示发送给内置的eval()函数来重新构造另一个对象:
>>> student1 = eval(repr(student0))
>>> student1
Student('John Smith')
顺便提一下,我们经常会使用f-string来插入对象的属性,我们使用!r来指定我们想要该字符串的原始表示,因为默认情况下,f-string会调用__str__
方法来检索被插入变量的值。在这种情况下,省略!r将使字符串本身被使用,而不是引号内的字符串。下面是一个简单的例子——显然,后一个字符串不能用于创建Student对象:
>>> name = "John Smith"
>>> print(f"Student({name!r})")
Student('John Smith')
>>> print(f"Student({name})")
Student(John Smith)
另一件需要注意的事情是,有时提供一个允许重建对象的有意义的字符串是不实际的。在这种情况下,我们通常使用<>来封装实例的类和一些摘要信息。下面的代码展示了内置类的示例。
>>> from io import BytesIO
>>> BytesIO(b'Medium')
<_io.BytesIO object at 0x11269c9a0>
>>> with open("test.text", 'w') as file:
... print(repr(file))
...
<_io.TextIOWrapper name='test.text' mode='w' encoding='UTF-8'>
另一个与对象格式化相关的有趣的特殊方法是__str__
方法。下面的代码向您展示了如何在自定义类中重写它。
class Student:
def __init__(self, name):
self.name = name
def __str__(self):
return "__str__ is called"
使用修改后的类,让我们看看什么时候调用它。
>>> print(student0)
__str__ is called
>>> str(student0)
'__str__ is called'
>>> f"{student0}"
'__str__ is called'
如上所示,至少有三种情况会导致调用__str__
特殊方法。具体来说,print()函数、str()函数(这是预期的,因为它只是一个语法糖)和f-string插值变量都将为对象调用底层的__str__
方法。
虽然我们知道使用__str__
方法的情况,但问题是实现这个方法的最佳实践是什么。
>>> class Student:
... def __init__(self, name):
... self.name = name
...
... def __str__(self):
... return f"{self.__class__.__name__}, {self.name}"
...
...
... student0 = Student("John Smith")
... print(student0)
...
Student, John Smith
虽然没有通用的方法来定义__str__
方法返回的字符串。但是原则是我们应该返回一些关于实例对象的描述性信息。在大多数情况下,我们只返回实例对象的属性来表示对象就可以了。
除了这些点,应该注意的是,如果你的类没有定义__str__
方法,Python将查找是否实现了__repr__
。如果是,任何调用__str__
的函数都会回退到调用__repr__
。
另一个与对象字符串格式化相关的重要特殊方法是__format__
方法。下面的代码显示了这个方法在自定义类中的使用方式:
class Student:
def __init__(self, name):
self.name = name
def __format__(self, format_spec):
return "__format__ is called"
正如我们之前所做的,让我们先看看这个方法被调用的常见情况:
>>> student0 = Student("John Smith")
>>> f"{student0}"
'__format__ is called'
>>> format(student0)
'__format__ is called'
>>> print(student0)
<__main__.Student object at 0x114a92c10>
>>> str(student0)
'<__main__.Student object at 0x114a92c10>'
f-string和内置的format()方法都可以调用__format__
方法。让我们以f-string为例,向你展示__format__
是如何工作的。
你可能注意到,与__repr__
和__str__
方法不同,__format__
方法有一个额外的参数名为format_spec。此参数定义如何将对象格式化为字符串。让我们看看下面代码片段中的一些自定义规范:
>>> class Student:
... def __init__(self, name):
... self.name = name
...
... def __str__(self):
... return f"{self.__class__.__name__}, {self.name}"
...
... def __format__(self, format_spec):
... if format_spec == "i":
... return "".join(x[0] for x in self.name.split())
... elif format_spec == "C":
... return self.name.upper()
... print("___delegate to the built-in format method for generic formatting___")
... return format(str(self), format_spec)
...
...
... student0 = Student("John Smith")
... print("format_spec: i", f"{student0:i}")
... print("format_spec: C", f"{student0:C}")
... print("format_spec: generic", f"{student0:-^30}")
...
format_spec: i JS
format_spec: C JOHN SMITH
___delegate to the built-in format method for generic formatting___
format_spec: generic -----Student, John Smith------
我们定义了两个自定义规范。当说明为i时,它代表首字母,我们返回学生的首字母。另一个规范是C,它代表大写,我们返回学生名的大写形式。除了这两个规范之外,我们还将默认的内置格式委托给适用的字符串格式化。回想一下,当我们使用f-string时,我们在冒号后面指定格式要求。在我们的示例中,当我们将规范指定为i和C时,我们确实得到了所需的格式。
在本文中,我们回顾了Python中有关格式化的三个基本的特殊方法。这里是一个简短的回顾。
__repr__
方法是显示一个字符串表示形式,要求返回的是一个有效的Python表达式,可以用来创建类似的对象。当它不适用时,考虑使用“<>”来提供类信息和其他有意义的特性。
__str__
方法提供对象的描述性信息。
__format__
方法提供了除基本格式之外的自定义格式规范。如果你希望你的对象对于不同的用例有不同的字符串表示,这是很有帮助的。
作者:Yong Cui
原文地址:https://medium.com/better-programming/3-special-methods-for-object-string-formatting-in-python-c020150d624c
deephub翻译组
本文分享自 DeepHub IMBA 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!