Comportamiento inesperado de la función str incorporada en Python

Estoy teniendo un problema con los subtipos de la clase str debido a la conducta str.__call__ que aparentemente no entiendo.

Esto se ilustra mejor con el código simplificado a continuación.

 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!? 

Siempre pensé que la función str(obj) simplemente llama al método obj.__str__ , y eso es todo. Pero por alguna razón, también llama a la función __init__ de S nuevamente. ¿Alguien puede explicar el comportamiento y cómo puedo evitar que se S.__init__ en el resultado de C.__str__ al usar la función str() ?

Estrictamente hablando, str no es una función. Es un tipo Cuando llama a str(c) , Python realiza el procedimiento normal para generar una instancia de un tipo, llama a str.__new__(str, c) para crear el objeto (o reutiliza un objeto existente) y luego llama al método __init__ de El resultado para inicializarlo .

str.__new__(str, c) llama a la función de nivel C PyObject_Str , que llama a _PyObject_Str , que llama a su método __str__ . El resultado es una instancia de S , por lo que cuenta como una cadena, y _PyObject_Str decide que esto es lo suficientemente bueno en lugar de tratar de forzar a un objeto con type(obj) is str fuera del resultado. Por lo tanto, str.__new__(str, c) devuelve cs .

Ahora llegamos a __init__ . Como el argumento para str fue c , esto también se pasa a __init__ , por lo que Python llama a cs__init__(c) . __init__ llama a print(c) , lo que podría pensar que llamaría str(c) y conduciría a una recursión infinita. Sin embargo, el PRINT_ITEM operación PRINT_ITEM llama al PyFile_WriteObject de nivel C para escribir el objeto, y eso llama a PyObject_Str lugar de str , por lo que se salta el __init__ y no se repite infinitamente. En su lugar, llama a c.__str__() e imprime la instancia S resultante, ya que la instancia S es una cadena.