如果 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)
,这是因为print
在5
上调用__str__
以获取字符串。但是传递对象时,print
在对象上调用 __str__
,没有得到一个实际的字符串作为响应。
【讨论】:
以上是关于如果 t.__str__() 返回一个非字符串而不是 print(t.__str__()),为啥 print(t) 会出错?的主要内容,如果未能解决你的问题,请参考以下文章
/writeReview/1 __str__ 处的 Django 模型 TypeError 返回非字符串(int 类型)