I am running into an issue with subtyping the str class because of the str.__call__
behavior I apparently do not understand.
This is best illustrated by the simplified code below.
class S(str):def __init__(self, s: str):assert isinstance(s, str)print(s)class C:def __init__(self, s: str):self.s = S(s)def __str__(self):return self.sc = C("a") # -> prints "a"
c.__str__() # -> does not print "a"
str(c) # -> asserts fails in debug mode, else prints "a" as well!?
I always thought the str(obj)
function simply calls the obj.__str__
method, and that's it. But for some reason it also calls the __init__
function of S
again.
Can someone explain the behavior and how I can avoid that S.__init__
is called on the result of C.__str__
when using the str()
function?
Strictly speaking, str
isn't a function. It's a type. When you call str(c)
, Python goes through the normal procedure for generating an instance of a type, calling str.__new__(str, c)
to create the object (or reuse an existing object), and then calling the __init__
method of the result to initialize it.
str.__new__(str, c)
calls the C-level function PyObject_Str
, which calls _PyObject_Str
, which calls your __str__
method. The result is an instance of S
, so it counts as a string, and _PyObject_Str
decides this is good enough rather than trying to coerce an object with type(obj) is str
out of the result. Thus, str.__new__(str, c)
returns c.s
.
Now we get to __init__
. Since the argument to str
was c
, this also gets passed to __init__
, so Python calls c.s.__init__(c)
. __init__
calls print(c)
, which you might think would call str(c)
and lead to infinite recursion. However, the PRINT_ITEM
opcode calls the C-level PyFile_WriteObject to write the object, and that calls PyObject_Str
instead of str
, so it skips the __init__
and doesn't recurse infinitely. Instead, it calls c.__str__()
and prints the resulting S
instance, as the S
instance is a string.