Estoy tratando de entender el método __str__
en Python.
class Test: def __str__(self): return 5 t = Test() print(t.__str__())
En este método, devuelve un valor entero, pero el método de impresión puede imprimirlo.
Pero, cuando probé print(t)
arrojó el error TypeError: __str__ returned non-string (type int)
.
Según tengo entendido, print(t)
también está llamando al __str__(self)
.
¿Por qué print(t.__str__())
no quería la conversión de tipo de cadena?
"imprimir" realiza implícitamente la conversión de cadenas para que obtenga el mismo resultado de cualquier manera.
Métodos especiales
En Python, el intérprete de Python invoca ciertos nombres especiales en circunstancias especiales. Por ejemplo, el método init de una clase se invoca automáticamente cada vez que se construye un objeto. El método str se invoca automáticamente al imprimir y repr se invoca en una sesión interactiva para mostrar valores.
Hay nombres especiales para muchos otros comportamientos en Python. A continuación se describen algunos de los más utilizados.
Valores verdaderos y falsos. Vimos anteriormente que los números en Python tienen un valor de verdad; más específicamente, 0 es un valor falso y todos los demás números son valores verdaderos. De hecho, todos los objetos en Python tienen un valor de verdad. De forma predeterminada, los objetos de las clases definidas por el usuario se consideran verdaderos, pero se puede usar el método bool especial para anular este comportamiento. Si un objeto define el método bool , Python llama a ese método para determinar su valor de verdad.
Probemos literales de diferentes tipos incorporados y veamos qué sale:
print(42) # <class 'int'> 42 print(3.14) # <class 'float'> 3.14 print(1 + 2j) # <class 'complex'> (1+2j) print(True) # <class 'bool'> True print([1, 2, 3]) # <class 'list'> [1, 2, 3] print((1, 2, 3)) # <class 'tuple'> (1, 2, 3) print({'red', 'green', 'blue'}) # <class 'set'> {'red', 'green', 'blue'} print({'name': 'Alice', 'age': 42}) # <class 'dict'> {'name': 'Alice', 'age': 42} print('hello') # <class 'str'> hello
En teoría, tienes razón en esta parte:
Según tengo entendido, print(t) también está llamando al método str (self).
Dentro de Python interno, al llamar al método __str__
, Python llama al método __str__(self)
, pero solo una vez, y obtiene el resultado, un número 5
:
https://github.com/python/cpython/blob/3.10/Objects/object.c#L499
Pero luego, Python verificará el resultado en el nivel C, informa un error si el resultado no es una cadena:
https://github.com/python/cpython/blob/3.10/Objects/object.c#L505
No intentará volver a llamar al método __str__
en el resultado. Entonces, en lugar de ver el resultado, obtendrá el error TypeError: __str__ returned non-string (type int)
.
Lo que estás haciendo es equivalente a print(5)
, que funciona porque print
llama a __str__
en 5
para obtener una cadena. Pero al pasar el objeto, print
llama a __str__
en el objeto y no obtiene una cadena real en respuesta.
Cuando llama a print(t)
, la función de impresión intenta obtener el valor de str(t)
que devuelve un número entero. El valor tiene que ser str , por lo que genera una excepción. Pero cuando llamas a print(t.__str__())
, no genera una excepción porque el método actúa como un método ordinario y el tipo de valor de retorno no tiene que ser str .
Cuando llama a t.__str__()
directamente, es como cualquier otro método. El método __str__
se sobrescribió, por lo que no tiene nada de especial al llamarlo directamente.
Cuando se realiza la invocación de print(t)
internamente, donde se realiza una verificación de tipo
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;
El manual dice:
El valor de retorno debe ser un objeto de cadena.
así que deberías hacer algo como
def __str__(self): return str(5)
o mejor, algo más significativo como
def __str__(self) -> str: return "TestObject with id: {}".format(self.id)
(El tipo de devolución se puede agregar a la declaración de la función para que su editor le informe si no tiene el tipo correcto).
Se trata de verificaciones adicionales que hace Python con algunas funciones integradas .
len() --> __len__() + some checks str() --> __str__() + some checks
¡Hay una diferencia entre cuando "usted" llama a un método explícitamente o cuando ese método es llamado "por Python"! El punto es que cuando Python llame a su método, hará algunas comprobaciones por usted. (Esa es una de las razones por las que deberíamos usar esas funciones integradas en lugar de llamar al método dunder relevante).
Podemos ver ese comportamiento con len()
y __len__()
también:
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
¡Entonces python verificó el retorno de un entero en la segunda declaración de impresión! eso es lo que se esperaba de __len__()
.
Lo mismo sucede aquí. Cuando llamas a print(t)
, Python mismo llama al método __str__()
, por lo que verifica si __str__()
devuelve una cadena que se esperaba o no. (Lo mismo sucede con str(t)
)
Pero, cuando dices print(t.__str__())
, primero, estás llamando a su método __str__()
en la instancia explícitamente por ti mismo, no hay verificación aquí... ¿qué se devuelve? número 5
, y luego python ejecutará print(5)
.
Pondré aquí algunos hallazgos que encontré mientras observaba el comportamiento de la función de print
. Entonces, aquí va nada.
¿Por qué print(t. str ()) no quería la conversión de tipo de cadena?
En realidad lo hace (pero creo que no de la manera que esperas). Como la mayoría de la gente ha notado aquí, lo que sucedió con el código es que primero evaluará la función __str__
(entonces, obtuviste el número 5
aquí). Y lo que sucedió es que hace la conversión (si miras el código fuente aquí ) usando el __str__
de la clase int
(entonces, tu número 5
se imprimirá como "5"
)
Pero, cuando intenté imprimir (t), arrojó el error TypeError: str devolvió una cadena (tipo int).
Esto sucede porque hay una verificación para garantizar que el objeto "representado" como cadena correctamente. Este comportamiento se puede comprobar en este código fuente