如果 t.__str__() 返回一个非字符串而不是 print(t.__str__()),为啥 print(t) 会出错?

Posted

技术标签:

【中文标题】如果 t.__str__() 返回一个非字符串而不是 print(t.__str__()),为啥 print(t) 会出错?【英文标题】:Why does print(t) error if t.__str__() returns a non-string, but not print(t.__str__())?如果 t.__str__() 返回一个非字符串而不是 print(t.__str__()),为什么 print(t) 会出错? 【发布时间】:2021-10-12 08:18:47 【问题描述】:

我正在尝试理解 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__())不想要字符串类型转换?

【问题讨论】:

print(t.__str__()) 避免了错误,因为它在显式调用方法后变为print(5) print(t) 返回错误的原因与str(t) 相同。由于__str__() 的错误实现,您无法从t 中创建字符串(因为print() 必须输出其值)。 在某种程度上,这并不重要:它是依赖于实现的未定义行为。 __str__ 方法记录了“[其]返回值必须是字符串对象”。 今天,t.__str__() 可以愉快地返回一个非str 的值;明天,它可能会引发异常,这不会是一个重大变化,因为语言从未定义如果 __str__ 不返回字符串会发生什么。 (以__init__:它是一个TypeError 来返回除None 之外的任何值,尽管我隐约记得它曾经是合法的,即使对象期间隐式调用的任何返回值创建将被忽略。) 【参考方案1】:

理论上,你在这部分是正确的:

据我了解 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)

【讨论】:

【参考方案2】:

"print" 隐式执行字符串转换,因此无论哪种方式都可以得到相同的输出。

特殊方法

在 Python 中,某些特殊名称由 Python 解释器在特殊情况下调用。例如,每当构造一个对象时,就会自动调用一个类的 init 方法。 str 方法在打印时自动调用,repr 在交互式会话中调用以显示值。

Python 中的许多其他行为都有特殊的名称。下面介绍了一些最常用的方法。

真假值。我们之前看到 Python 中的数字具有真值。更具体地说,0 是假值,所有其他数字都是真值。事实上,Python 中的所有对象都有一个真值。默认情况下,用户定义类的对象被认为是真实的,但是可以使用特殊的 bool 方法来覆盖这种行为。如果一个对象定义了 bool 方法,那么 Python 会调用该方法来确定其真值。

让我们尝试不同内置类型的字面量,看看结果如何:

print(42)                            # <class 'int'>
42
print(3.14)                          # <class 'float'>
3.14
print(1 + 2j)                        # <class 'complex'>
(1+2j)
print(True)                          # <class 'bool'>
True
print([1, 2, 3])                     # <class 'list'>
[1, 2, 3]
print((1, 2, 3))                     # <class 'tuple'>
(1, 2, 3)
print('red', 'green', 'blue')      # <class 'set'>
'red', 'green', 'blue'
print('name': 'Alice', 'age': 42)  # <class 'dict'>
'name': 'Alice', 'age': 42
print('hello')                       # <class 'str'>
hello

【讨论】:

【参考方案3】:

我将把我在查看print 函数的行为时发现的一些发现放在这里。所以,这里什么都没有。

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

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

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

发生这种情况是因为检查以确保对象“表示”为字符串正确。可以在此source code 上检查此行为

【讨论】:

【参考方案4】:

这都是关于 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 itself calls __str__() 方法时,它确实检查 __str__() 是否返回预期的字符串。 (str(t) 也会发生同样的事情)

但是,当您说print(t.__str__()) 时,首先,您自己在实例上显式调用它的__str__() 方法,这里没有检查...什么会返回?编号5,然后python会运行print(5)

【讨论】:

【参考方案5】:

当您直接调用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; 

manual 声明:

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

所以你应该做类似的事情

def __str__(self):
        return str(5)

或者更好,更有意义的东西,比如

def __str__(self) -> str:
        return "TestObject with id: ".format(self.id)

(返回类型可以添加到函数声明中 所以如果它没有正确的类型,你的编辑会通知你。)

【讨论】:

方法本身不会引发错误;它确实返回一个int。如果__str__ 方法返回非字符串,则str 函数会引发错误。【参考方案6】:

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

【讨论】:

【参考方案7】:

你所做的相当于print(5),这是因为print5 上调用__str__ 以获取字符串。但是传递对象时,print 在对象上调用 __str__没有得到一个实际的字符串作为响应。

【讨论】:

以上是关于如果 t.__str__() 返回一个非字符串而不是 print(t.__str__()),为啥 print(t) 会出错?的主要内容,如果未能解决你的问题,请参考以下文章

/writeReview/1 __str__ 处的 Django 模型 TypeError 返回非字符串(int 类型)

python3.x __str__与__repr__

6.4定制类

__str__魔术方法

Django-Factoryboy:带有字符串格式的__str__调用改为返回对象

如果没有实现,动态实现 __str__