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.s
c = 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?
Python has a built-in string class named "str" with many handy features (there is an older module named "string" which you should not use). String literals can be enclosed by either double or single quotes, although single quotes are more commonly used.
__class__ is an attribute on the object that refers to the class from which the object was created. a. __class__ # Output: <class 'int'> b. __class__ # Output: <class 'float'> After simple data types, let's now understand the type function and __class__ attribute with the help of a user-defined class, Human .
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With