Por favor, consulte el siguiente fragmento de código:
In [1]: class A(Exception): ...: def __init__(self, b): ...: self.message = b.message ...: In [2]: class B: ...: message = "hello" ...: In [3]: A(B()) Out[3]: __main__.A(<__main__.B at 0x14af96790>) In [4]: class A: ...: def __init__(self, b): ...: self.message = b.message ...: In [5]: A(B()) Out[5]: <__main__.A at 0x10445b0a0> si A subclase de Exception , su repr devuelve una referencia a B() aunque solo pasemos el atributo de mensaje de B() .
¿Por qué es este el comportamiento intencional en Exception.repr y cómo funciona en python psuedocode si es posible dado que el código cpython no es demasiado legible?
Muy bien, creo que he encontrado el truco. Aquí está el código fuente de C, pero volveré a implementar algo similar en Python para demostrarlo.
Además del __init__ habitual (que está anulando), Python también tiene un método mágico llamado __new__ . Cuando construyes una A como A(B()) , está haciendo algo más o menos como
b = B() a = A.__new__(A, b) a.__init__(b) Ahora, ha anulado A.__init__ , por lo que Exception.__init__ nunca se llama. Pero A.__new__ es simplemente Exception.__new__ (más precisamente, es BaseException.__new__ , que es esencialmente el código fuente C que vinculé). Y, según el código vinculado, eso es aproximadamente
class BaseException: def __new__(cls, *args): obj = object.__new__(cls) obj.args = args return obj Entonces almacenamos explícitamente la tupla de argumentos en un campo llamado args en el objeto de excepción. Esta es la tupla de argumentos reales pasada al constructor, incluso si __init__ . Entonces, repr solo hace referencia a self.args para recuperar los argumentos originales.
Tenga en cuenta que estoy siendo un poco impreciso aquí. Si BaseException.__new__ en REPL, verá que todavía es object.__new__ . Las devoluciones de llamada de C funcionan de manera diferente y usan algo de magia de compilación a la que no tenemos acceso, pero la idea básica es la misma.
Cuando se crea un objeto de Python, se llama al método __new__ de la clase, y luego se llama a __init__ en la nueva instancia que devuelve el método __new__ (suponiendo que devuelva una nueva instancia, que a veces no lo hace).
Su método __init__ anulado no mantiene una referencia a b , pero no __new__ , por lo que hereda el método __new__ definido aquí ( enlace fuente de CPython ):
static PyObject * BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { // ... if (args) { self->args = args; Py_INCREF(args); return (PyObject *)self; } // ... } Omití las partes que no son relevantes. Como puede ver, el método __new__ de la clase BaseException almacena una referencia a la tupla de argumentos utilizados al crear la excepción y, por lo tanto, esta tupla está disponible para que el método __repr__ imprima una referencia a los objetos utilizados para instanciar la excepción. Entonces es esta tupla la que retiene una referencia al argumento original b . Esto es consistente con la expectativa general de que repr debería devolver el código de Python que crearía un objeto en el mismo estado, si es posible.
Tenga en cuenta que solo args , no kwds , tiene este comportamiento; el método __new__ no almacena una referencia a kwds y __repr__ no la imprime, por lo que deberíamos esperar no ver el mismo comportamiento si se llama al constructor con un argumento de palabra clave en lugar de un argumento posicional. Efectivamente, eso es lo que observamos:
>>> A(B()) A(<__main__.B object at 0x7fa8e7a23860>,) >>> A(b=B()) A() Un poco extraño ya que se supone que los dos objetos A tienen el mismo estado, pero así es como se escribe el código, de todos modos.
Podemos explorar la diferencia con la pestaña de Ipython completa.
In [230]: a=A(B()) In [231]: a Out[231]: __main__.A(<__main__.B at 0x7f29cd1e71c0>)Con la pestaña de ipython completa:
In [234]: a. args a.bin message a.npy with_traceback() a.t1 In [234]: a.args Out[234]: (<__main__.B at 0x7f29cd1e71c0>,) La clase sin excepción, solo tiene un atributo, message :
In [235]: a1=A1(B()) In [236]: a1 Out[236]: <__main__.A1 at 0x7f29ccc21a60> In [237]: a1.message Out[237]: 'hello'y una simple excepción:
In [238]: e=Exception(B()) In [239]: e Out[239]: Exception(<__main__.B at 0x7f29ccd0e790>) In [240]: e. args with_traceback() Y usando a como excepción:
In [240]: raise(a) Traceback (most recent call last): File "<ipython-input-240-cf5e5bb7d43e>", line 1, in <module> raise(a) A: <__main__.B object at 0x7f29cd1e71c0>