30

I am trying to understand the __str__ method in Python.

class Test:
    def __str__(self):
        return 5

t = Test()

print(t.__str__())

In this method it returns an integer value but the print method is able to print it.

But, when I tried print(t) it threw the error TypeError: __str__ returned non-string (type int).

As I understand print(t) is also calling the __str__(self) method.

Why didn't print(t.__str__()) want the the string type conversion?

Community
  • 1
  • 1
  • 5
    `print(t.__str__())` avoids the error because it becomes `print(5)` after explicitly calling the method. – martineau Aug 08 '21 at 07:48
  • Whereas `print(t)` returns an error for the same reason `str(t)` does. You can't make a string out of `t` (as `print()` must, to output its value), due to the bad implementation of `__str__()`. – FeRD Aug 09 '21 at 08:18
  • At some level, it doesn't really matter: it's implementation-dependent undefined behavior. The `__str__` method documents that "[its] return value must be a string object". – chepner Aug 26 '21 at 19:40
  • Today, `t.__str__()` could happily return a non-`str` value; tomorrow, it could raise an exception, and that would not be a breaking change, because the language never defined what should happen if `__str__` doesn't return a string. – chepner Aug 26 '21 at 19:41
  • (Take `__init__`: it's a `TypeError` to return any value other than `None`, though I vaguely recall that it was once legal, even if any return value from the implicit call during object creation would be ignored.) – chepner Aug 26 '21 at 19:47

6 Answers6

32

What you’re doing is equivalent to print(5), which works because print calls __str__ on 5 to get a string. But passing the object, print calls __str__ on the object and doesn’t get an actual string in response.

deceze
  • 510,633
  • 85
  • 743
  • 889
29

It's all about extra checks that python does with some built-in functions.

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

There is a difference between when "you" call a method explicitly or when that method gets called "by Python"! The point is when Python calls your method it will do some checks for you. (That's one of the reasons that we should use those built-in functions instead of calling relevant dunder method.)

We can see that behavior with len() and __len__() as well:

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

So python checked for returning integer in second print statement! that's what expected from __len__().

Same thing happens here. When you call print(t) Python itself calls __str__() method, so it does check to see if __str__() returns a string that is expected or not. (same thing happens with str(t))

But, when you say print(t.__str__()), first, you're calling it's __str__() method on the instance explicitly by yourself, there is no checking here... what does get back ? number 5, and then python will run print(5).

S.B
  • 13,077
  • 10
  • 22
  • 49
8

When you call t.__str__() directly it is just like any other method. The method __str__ method was overwritten, so there is nothing special about it when calling it directly.

When doing print(t) invocation happens internally, where some typechecking takes place

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; 

The manual states:

The return value must be a string object.

so you should do something like

def __str__(self):
        return str(5)

or better, something more meaningful like

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

(The return type can be added to the function decalaration so your editor will let you know if it doesn't have the right type.)

Alex
  • 5,759
  • 1
  • 32
  • 47
  • 2
    The method itself doesn't raise an error; it does return an int. It's the `str` function that raises an error if the `__str__` method returns a non-string. – kaya3 Aug 08 '21 at 08:58
3

When you call print(t), print function tries to get the str(t) value which returns integer. The value has to be str, so it raises an exception. But when you call print(t.__str__()), it doesn't raise an exception because the method acts like an ordinary method and the return value type doesn't have to be str.

PHP Master
  • 116
  • 5
0

I will put here some finding that I found while looking at the behaviour of the print function. So, here goes nothing.

Why didn't print(t.str()) want the the string type conversion?

It actually does (but, I think not the way you expect it). As most people have noted here, what happened with the code, is that it will evaluate the __str__ function first (so, you got the number 5 here). And, what happened is that, it does the conversion (if you look at the source code here) using the __str__ of int class (so, your number 5 will be printed as "5")

But, when I tried print(t) it threw the error TypeError: str returned non-string (type int).

This happen because there is a checking to ensure the object "representated" as string properly. This behaviour can be checked on this source code

kucing_terbang
  • 4,991
  • 2
  • 22
  • 28
0

In theory, you are correct in this part:

As I understand print(t) is also calling the str(self) method.

Inside Python internal, when calling the __str__ method, Python does call the method __str__(self), but only one time, and it does get the result, a number 5:

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

But then, Python will check the result in C level, reports an error if the result is not a string:

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

It will not try to call __str__ method on the result again. So instead see the result, you will get the error TypeError: __str__ returned non-string (type int).

sunny2016
  • 1,345
  • 3
  • 13
  • 22