如果t.__str__()返回非字符串,为什么print(t)会出错,但print(t.__str__())不会?

30

我正在尝试理解Python中的__str__方法。

class Test:
    def __str__(self):
        return 5

t = Test()

print(t.__str__())

在这个方法中,它返回一个整数值,但是print方法可以打印它。
但是,当我尝试使用print(t)时,它抛出了错误TypeError: __str__ returned non-string (type int)
据我理解,print(t)也调用了__str__(self)方法。
为什么print(t.__str__())不需要进行字符串类型转换呢?

5
print(t.__str__()) 避免了错误,因为在显式调用该方法后,它变成了 print(5) - martineau
当使用str(t)时,由于__str__()的实现不良,你无法将t转换为字符串(就像print()输出值一样),因此同样的原因,print(t)会返回错误。 - FeRD
在某种程度上,这并不重要:它是实现相关的未定义行为。__str__方法记录了“[其]返回值必须是一个字符串对象”。 - chepner
今天,t.__str__() 可以愉快地返回一个非 str 值;明天,它可能会引发异常,但这不会是一个破坏性的变化,因为语言从未定义如果 __str__ 不返回字符串应该发生什么。 - chepner
在编程中,"init"是一个特殊的函数。返回除了 "None" 之外的任何值都会引起 "TypeError" 的异常。尽管我模糊地记得,在对象创建期间隐式调用时,即使有任何返回值也会被忽略,但这种行为曾经是合法的。 - chepner
6个回答

32
你所做的相当于print(5),因为它有效,原因是print调用__str__来获取数字5的字符串表示。但是如果传递对象,则print会在对象上调用__str__,并且不会得到实际的字符串响应。

29

这与Python对一些内置函数进行额外检查有关。

len() --> __len__() + some checks
str() --> __str__() + some checks

当你显式调用一个方法和当该方法被Python调用时是有区别的!关键在于,当Python调用你的方法时,它会为你做一些检查。(这也是我们应该使用那些内置函数而不是调用相关dunder方法的原因之一。)

我们也可以通过len()__len__()来看到这种行为:

class Test:
    def __len__(self):
        return 'foo'

t = Test()

print(t.__len__())  # fine
print(len(t))       # TypeError: 'str' object cannot be interpreted as an integer

所以,在第二个打印语句中,Python 检查是否返回整数!这是从 __len__() 期望得到的。

同样的事情也发生在这里。当你调用 print(t) 时,Python 自身会调用 __str__() 方法,因此它确实会检查 __str__() 是否返回了一个符合预期的字符串(str(t) 也是同样的情况)。

但是,当你说 print(t.__str__()) 时,首先,你自己显式地在实例上调用了它的 __str__() 方法,这里没有检查... 那么会返回什么?数字 5,然后 Python 将运行 print(5)


8
当你直接调用 t.__str__() 时,它就像任何其他方法一样。 __str__ 方法被覆盖,因此在直接调用它时没有什么特殊之处。
当执行 print(t) 时,内部会发生调用,其中进行了一些类型检查。
if (!PyUnicode_Check(res)) {
    _PyErr_Format(tstate, PyExc_TypeError,
                  "__str__ returned non-string (type %.200s)",
                  Py_TYPE(res)->tp_name);
    Py_DECREF(res);
    return NULL; 

手册中写道:

返回值必须是字符串对象。

因此,您应该这样做:

def __str__(self):
        return str(5)

或者更好地说,像更有意义的东西一样。
def __str__(self) -> str:
        return "TestObject with id: {}".format(self.id)

可以在函数声明中添加返回类型,这样您的编辑器就会提醒您是否具有正确的类型。


2
该方法本身不会引发错误,它会返回一个整数。如果__str__方法返回非字符串类型,则是str函数引发错误。 - kaya3

3
当您调用print(t)时,打印函数会尝试获取str(t)值,该值返回整数。该值必须是str类型,因此会引发异常。但是,当您调用print(t.__str__())时,它不会引发异常,因为该方法的行为类似于普通方法,返回值类型不必为str

0

从理论上讲,您在这部分是正确的:

据我所知,print(t) 也调用了 str(self) 方法。

在 Python 内部,当调用 __str__ 方法时,Python 确实会调用方法 __str__(self)但仅一次,并且它确实获得了结果,即数字 5

https://github.com/python/cpython/blob/3.10/Objects/object.c#L499

然后,Python将在C级别检查结果,如果结果不是字符串,则报告错误:

https://github.com/python/cpython/blob/3.10/Objects/object.c#L505

它不会再次尝试调用结果的 __str__ 方法。因此,而不是查看结果,您将收到错误 TypeError: __str__ returned non-string (type int)


0

我在研究print函数的行为时发现了一些结果,我会在这里记录下来。所以,开始吧。

为什么print(t.str())不想进行字符串类型转换?

实际上它确实进行了转换(但是,我认为不是你期望的方式)。正如大多数人在这里指出的那样,代码发生了什么,它将首先评估__str__函数(因此,在这里您得到数字5)。然后,发生的是,它使用int类的__str__(因此,您的数字5将被打印为"5")进行转换(如果您查看源代码here

但是,当我尝试print(t)时,它抛出了错误TypeError:str returned non-string (type int)。

这是因为有一个检查确保对象“表示”为字符串的正确性。可以在此源代码上检查此行为。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接