13

I know, that the purpose of str() method is to return the string representation of an object, so I wanted to test what happens if I force it to make something else.

I've created a class and an object:

class MyClass(object):

    def __str__(self, a=2, b=3):
        return a + b

mc = MyClass()

When I call:

print(str(mc))

The interpreter complains:

TypeError: __str__ returned non-string (type int)

And this is fully understandable because the str() method is trying to return int.

But if I try:

print(mc.__str__())

I get the output: 5.

So why the interpreter allows me to return int when I call __str__ directly, but not when I'm using str(mc) which - as I understand - is also evaluated to mc.__str__().

jpp
  • 159,742
  • 34
  • 281
  • 339
Narta
  • 131
  • 1
  • 5
  • I'm interested to understand why also – RustyShackleford Sep 11 '18 at 16:11
  • You might find this link here useful ! https://stackoverflow.com/questions/12448175/confused-about-str-on-list-in-python – Julian Silvestri Sep 11 '18 at 16:12
  • This explains a bit how overriding of `__str__` works in Python: https://stackoverflow.com/questions/8144026/how-to-define-a-str-method-for-a-class – regina_fallangi Sep 11 '18 at 16:12
  • 5
    The `str` function *does* call `__str__`, but it enforces the `str` return type also. It's not simply a direct call to `__str__` – juanpa.arrivillaga Sep 11 '18 at 16:18
  • 2
    Does this therefore count as an omission of the documentation? According to the docs, `str` (with a single argument) just calls `__str__` -- it makes no reference to checking the type. – Denziloe Sep 11 '18 at 16:32
  • 1
    @Denziloe well, in the [data model docs](https://docs.python.org/3/reference/datamodel.html#object.__str__) it does state that `__str__` must return a `str`. – juanpa.arrivillaga Sep 11 '18 at 17:40

4 Answers4

5

str() calls PyObject_Str(). Here is the source code where PyObject_Str() is defined. If you search this document for "__str__", you will see where the function calls __str__ and makes sure the return type is actually a string.

4

The built-in str function (and also repr) do more than just calling .__str__ (or .__repr__) – they also have defaults to cope with objects that don't have a __str__ or __repr__ method, and some cleverness to deal with objects whose string representation is recursive.

You can see the source (in C) for str and repr here and here. As you can see, they enforce the return type of __str__ and __repr__:

if (!PyUnicode_Check(res)) {
    PyErr_Format(PyExc_TypeError,
                 "__str__ returned non-string (type %.200s)",
                 Py_TYPE(res)->tp_name);
    Py_DECREF(res);
    return NULL;
}

If you just call the __str__ method on an object, Python itself doesn't enforce that any method called __str__ can only return a string – it's the str function that enforces that restriction.

ash
  • 5,139
  • 2
  • 27
  • 39
2

str isn't just

def str(obj):
    return obj.__str__()

I think very few of the standard functions or operators map directly to a magic method like that, although I'm not sure of the exact counts.

str tries __str__, but it also tries __repr__ if there's no __str__, and it enforces the str return type. (It also calls the return value's __init__ for technical reasons, which can get weird for str subclasses.) + tries __add__, but it also tries __radd__. iter tries __iter__, but it also tries __getitem__. The list goes on and on.

user2357112
  • 260,549
  • 28
  • 431
  • 505
-1

__str__ provides a contract: you return a string, and the program won't break when it tries to use a non-string value when the program expects a string. Determining whether __str__ actually obeys that contract is uncomputable in general, so it is up to the programmer to enforce the contract.

As @Juanpa.arrivillaga points out, str is simply stricter about the __str__ method returning what it should be. Your explicit call to __str__ doesn't actually invoke the protocol; it returns an int value, but that value itself has a __str__ method which print calls when it wants a str value.

chepner
  • 497,756
  • 71
  • 530
  • 681