Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

143
Visualizações
Why does python's Exception's repr keep track of passed object's to __init__?

Please see the below code snippet:

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>

if A subclasses from Exception, its repr returns a reference to B() even though we only pass B()'s message attribute.

Why is this the intentional behavior in Exception.repr and how does it work in python psuedocode if possible given the cpython code isn't too readable ?

over 4 years ago · Santiago Trujillo
3 Respostas
Responde à pergunta

0

Alright, I think I've found the trick. Here's the C source code, but I'll reimplement something similar in Python to demonstrate.

In addition to the usual __init__ (which you're overriding), Python also has a magic method called __new__. When you construct an A as A(B()), it's doing something roughly like

b = B()
a = A.__new__(A, b)
a.__init__(b)

Now, you've overridden A.__init__, so Exception.__init__ never gets called. But A.__new__ is simply Exception.__new__ (more precisely, it's BaseException.__new__, which is essentially the C source code I linked). And, based on the linked code, that's roughly

class BaseException:
  def __new__(cls, *args):
    obj = object.__new__(cls)
    obj.args = args
    return obj

So we explicitly store the arguments tuple in a field called args on the exception object. This is the actual arguments tuple passed to the constructor, even if we override __init__. So repr is just referencing self.args to get the original arguments back.

Note that I'm being a bit imprecise here. If you check BaseException.__new__ in the REPL, you'll see that it's still object.__new__. The C callbacks work differently and use some compiler magic we don't have access to, but the basic idea is the same.

over 4 years ago · Santiago Trujillo Relatório

0

When a Python object is created, it is the class's __new__ method that is called, and __init__ is then called on the new instance that the __new__ method returns (assuming it returned a new instance, which sometimes it doesn't).

Your overridden __init__ method doesn't keep a reference to b, but you didn't override __new__, so you inherit the __new__ method defined here (CPython source link):

static PyObject *
BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    // ...
    if (args) {
        self->args = args;
        Py_INCREF(args);
        return (PyObject *)self;
    }
    // ...
}

I omitted the parts that are not relevant. As you can see, the __new__ method of the BaseException class stores a reference to the tuple of arguments used when creating the exception, and this tuple is therefore available for the __repr__ method to print a reference to the objects used to instantiate the exception. So it's this tuple which retains a reference to the original argument b. This is consistent with the general expectation that repr should return Python code which would create an object in the same state, if it can.

Note that it is only args, not kwds, which has this behaviour; the __new__ method doesn't store a reference to kwds and __repr__ doesn't print it, so we should expect not to see the same behaviour if the constructor is called with a keyword argument instead of a positional argument. Indeed, that is what we observe:

>>> A(B())
A(<__main__.B object at 0x7fa8e7a23860>,)
>>> A(b=B())
A()

A bit strange since the two A objects are supposed to have the same state, but that's how the code is written, anyway.

over 4 years ago · Santiago Trujillo Relatório

0

We can explore the difference with Ipython's tab complete.

In [230]: a=A(B())
In [231]: a
Out[231]: __main__.A(<__main__.B at 0x7f29cd1e71c0>)

With ipython tab complete:

In [234]: a.
             args             a.bin           
             message          a.npy           
             with_traceback() a.t1  
In [234]: a.args
Out[234]: (<__main__.B at 0x7f29cd1e71c0>,)

The class without Exception, only has one attribute, message:

In [235]: a1=A1(B())
In [236]: a1
Out[236]: <__main__.A1 at 0x7f29ccc21a60>
In [237]: a1.message
Out[237]: 'hello'

and a plain Exception:

In [238]: e=Exception(B())
In [239]: e
Out[239]: Exception(<__main__.B at 0x7f29ccd0e790>)
In [240]: e.
             args            
             with_traceback()

And using a as an exception:

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>
over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda