0


可以格式化Python自定义对象的3个魔术方法

在Python中,下划线用于属性名时具有特殊含义。一种特殊形式是使用两对双下划线,一个在属性名之前,另一个在属性名之后,这被称为特殊方法或魔术方法。

例如,我们大多数人知道的第一个特殊方法可能是初始化方法

__init__

,它用于创建Python对象。下面的代码显示了一个示例:

class Student:
    def __init__(self, name):
        self.name = name

当你创建这个类的实例对象时,你可以通过在交互式控制台中输入对象变量来检查这个对象:

>>> student0 = Student("John Smith")
>>> student0
<__main__.Student at 0x11883aa60>

然而,这个对象的信息并不是很友好,它只是显示了它的类和内存地址。为了使它更有趣,我们应该考虑定制类的字符串格式化方法。具体来说,我们将在本文中讨论三种特殊的方法。

repr方法

__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方法

另一个与对象格式化相关的有趣的特殊方法是

__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方法

另一个与对象字符串格式化相关的重要特殊方法是

__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翻译组

标签:

“可以格式化Python自定义对象的3个魔术方法”的评论:

还没有评论