0


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

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

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

  1. __init__

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

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

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

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

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

repr方法

  1. __repr__

方法与对象的表示相关,将对象转化为供解释器读取的形式。如上所示,Python对象在交互式控制台中输出其表示字符串。下面的代码显示了这个特性:

  1. >>> class Student:
  2. ... def __init__(self, name):
  3. ... self.name = name
  4. ...
  5. ... def __repr__(self):
  6. ... return "__repr__ is called"
  7. ...
  8. >>> student0 = Student("John Smith")
  9. >>> student0
  10. __repr__ is called

需要注意的一点是,我们应该为

  1. __repr__

特殊方法返回一个字符串。如果返回一个非字符串,就会遇到TypeError,如下图所示。

但是尚未解决的问题是,我们应该为对象返回什么字符串?根据经验来说,我们应该返回一个字符串,用户可以使用它重新构造对象(例如,创建一个与被检查对象具有相等值的对象)。

  1. class Student:
  2. def __init__(self, name):
  3. self.name = name
  4. def __repr__(self):
  5. return f"{self.__class__.__name__}({self.name!r})"

使用更新后的代码,我们可以在交互式控制台中检查对象,如下所示。如你所见,表示字符串是一个有效的Python表达式,我们可以使用它创建一个Student对象:

  1. >>> student0 = Student("John Smith")
  2. >>> student0
  3. Student('John Smith')

同样,我们可以使用内置的repr()方法来检索表示字符串。因为我们可能希望通过将字符串表示发送给内置的eval()函数来重新构造另一个对象:

  1. >>> student1 = eval(repr(student0))
  2. >>> student1
  3. Student('John Smith')

顺便提一下,我们经常会使用f-string来插入对象的属性,我们使用!r来指定我们想要该字符串的原始表示,因为默认情况下,f-string会调用

  1. __str__

方法来检索被插入变量的值。在这种情况下,省略!r将使字符串本身被使用,而不是引号内的字符串。下面是一个简单的例子——显然,后一个字符串不能用于创建Student对象:

  1. >>> name = "John Smith"
  2. >>> print(f"Student({name!r})")
  3. Student('John Smith')
  4. >>> print(f"Student({name})")
  5. Student(John Smith)

另一件需要注意的事情是,有时提供一个允许重建对象的有意义的字符串是不实际的。在这种情况下,我们通常使用<>来封装实例的类和一些摘要信息。下面的代码展示了内置类的示例。

  1. >>> from io import BytesIO
  2. >>> BytesIO(b'Medium')
  3. <_io.BytesIO object at 0x11269c9a0>
  4. >>> with open("test.text", 'w') as file:
  5. ... print(repr(file))
  6. ...
  7. <_io.TextIOWrapper name='test.text' mode='w' encoding='UTF-8'>

str方法

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

  1. __str__

方法。下面的代码向您展示了如何在自定义类中重写它。

  1. class Student:
  2. def __init__(self, name):
  3. self.name = name
  4. def __str__(self):
  5. return "__str__ is called"

使用修改后的类,让我们看看什么时候调用它。

  1. >>> print(student0)
  2. __str__ is called
  3. >>> str(student0)
  4. '__str__ is called'
  5. >>> f"{student0}"
  6. '__str__ is called'

如上所示,至少有三种情况会导致调用

  1. __str__

特殊方法。具体来说,print()函数、str()函数(这是预期的,因为它只是一个语法糖)和f-string插值变量都将为对象调用底层的

  1. __str__

方法。

虽然我们知道使用

  1. __str__

方法的情况,但问题是实现这个方法的最佳实践是什么。

  1. >>> class Student:
  2. ... def __init__(self, name):
  3. ... self.name = name
  4. ...
  5. ... def __str__(self):
  6. ... return f"{self.__class__.__name__}, {self.name}"
  7. ...
  8. ...
  9. ... student0 = Student("John Smith")
  10. ... print(student0)
  11. ...
  12. Student, John Smith

虽然没有通用的方法来定义

  1. __str__

方法返回的字符串。但是原则是我们应该返回一些关于实例对象的描述性信息。在大多数情况下,我们只返回实例对象的属性来表示对象就可以了。

除了这些点,应该注意的是,如果你的类没有定义

  1. __str__

方法,Python将查找是否实现了

  1. __repr__

。如果是,任何调用

  1. __str__

的函数都会回退到调用

  1. __repr__

format方法

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

  1. __format__

方法。下面的代码显示了这个方法在自定义类中的使用方式:

  1. class Student:
  2. def __init__(self, name):
  3. self.name = name
  4. def __format__(self, format_spec):
  5. return "__format__ is called"

正如我们之前所做的,让我们先看看这个方法被调用的常见情况:

  1. >>> student0 = Student("John Smith")
  2. >>> f"{student0}"
  3. '__format__ is called'
  4. >>> format(student0)
  5. '__format__ is called'
  6. >>> print(student0)
  7. <__main__.Student object at 0x114a92c10>
  8. >>> str(student0)
  9. '<__main__.Student object at 0x114a92c10>'

f-string和内置的format()方法都可以调用

  1. __format__

方法。让我们以f-string为例,向你展示

  1. __format__

是如何工作的。

你可能注意到,与

  1. __repr__

  1. __str__

方法不同,

  1. __format__

方法有一个额外的参数名为format_spec。此参数定义如何将对象格式化为字符串。让我们看看下面代码片段中的一些自定义规范:

  1. >>> class Student:
  2. ... def __init__(self, name):
  3. ... self.name = name
  4. ...
  5. ... def __str__(self):
  6. ... return f"{self.__class__.__name__}, {self.name}"
  7. ...
  8. ... def __format__(self, format_spec):
  9. ... if format_spec == "i":
  10. ... return "".join(x[0] for x in self.name.split())
  11. ... elif format_spec == "C":
  12. ... return self.name.upper()
  13. ... print("___delegate to the built-in format method for generic formatting___")
  14. ... return format(str(self), format_spec)
  15. ...
  16. ...
  17. ... student0 = Student("John Smith")
  18. ... print("format_spec: i", f"{student0:i}")
  19. ... print("format_spec: C", f"{student0:C}")
  20. ... print("format_spec: generic", f"{student0:-^30}")
  21. ...
  22. format_spec: i JS
  23. format_spec: C JOHN SMITH
  24. ___delegate to the built-in format method for generic formatting___
  25. format_spec: generic -----Student, John Smith------

我们定义了两个自定义规范。当说明为i时,它代表首字母,我们返回学生的首字母。另一个规范是C,它代表大写,我们返回学生名的大写形式。除了这两个规范之外,我们还将默认的内置格式委托给适用的字符串格式化。回想一下,当我们使用f-string时,我们在冒号后面指定格式要求。在我们的示例中,当我们将规范指定为i和C时,我们确实得到了所需的格式。

总结

在本文中,我们回顾了Python中有关格式化的三个基本的特殊方法。这里是一个简短的回顾。

  1. __repr__

方法是显示一个字符串表示形式,要求返回的是一个有效的Python表达式,可以用来创建类似的对象。当它不适用时,考虑使用“<>”来提供类信息和其他有意义的特性。

  1. __str__

方法提供对象的描述性信息。

  1. __format__

方法提供了除基本格式之外的自定义格式规范。如果你希望你的对象对于不同的用例有不同的字符串表示,这是很有帮助的。

作者:Yong Cui

原文地址:https://medium.com/better-programming/3-special-methods-for-object-string-formatting-in-python-c020150d624c

deephub翻译组

标签:

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

还没有评论