9

Not sure how possible this is, but here goes:

I'm trying to write an object with some slightly more subtle behavior - which may or may not be a good idea, I haven't determined that yet.

I have this method:

def __getattr__(self, attr):                                                                                                      
    try:                                                                       
        return self.props[attr].value                                          
    except KeyError:                                                           
        pass #to hide the keyerror exception                                   

    msg = "'{}' object has no attribute '{}'"                                  
    raise AttributeError(msg.format(self.__dict__['type'], attr)) 

Now, when I create an instance of this like so:

t = Thing()
t.foo

I get a stacktrace containing my function:

Traceback (most recent call last):
  File "attrfun.py", line 23, in <module>
    t.foo
  File "attrfun.py", line 15, in __getattr__
    raise AttributeError(msg.format(self._type, attr))
AttributeError: 'Thing' object has no attribute 'foo'

I don't want that - I want the stack trace to read:

Traceback (most recent call last):
  File "attrfun.py", line 23, in <module>
    t.foo
AttributeError: 'Thing' object has no attribute 'foo'

Is this possible with a minimal amount of effort, or is there kind of a lot required? I found this answer which indicates that something looks to be possible, though perhaps involved. If there's an easier way, I'd love to hear it! Otherwise I'll just put that idea on the shelf for now.

Community
  • 1
  • 1
Wayne Werner
  • 45,646
  • 26
  • 189
  • 275
  • 2
    I would strongly advise against this. In a month or two, you will forget where and why your function throws this specific exception. – ivan_pozdeev Nov 18 '14 at 12:27
  • if you just want a custom print use the module traceback, if you want a clean traceback just let it like this. Why use an other dict as attribute container ? self.__dict__ already exist. And change the traceback is not simple and it's not possible with pure python, you need to hack the interpreter with ctypes – Ludovic Viaud Nov 18 '14 at 12:33
  • @ivan_pozdeev On the one hand it seems like a bad thing to do, on the other hand - I don't know why I would ever be confused reading `AttributeError: 'X' has no attribute 'Y'`. – Wayne Werner Nov 18 '14 at 16:07
  • @WayneWerner You still need to know where this happens to tell: which object is the 'X' and why it's queried for 'Y' (extra confusion if reflection is involved). – ivan_pozdeev Nov 18 '14 at 16:29
  • Don't forget the code can throw exceptions on its own, too - sometimes, those and where you never expect. – ivan_pozdeev Nov 18 '14 at 16:35
  • That's what I'm *trying* to show. For instance, if you do `x = 3; x.fnord` you will get `AttributeError: 'int' has no attribute 'fnord'` and the most recent line in the stack trace will be `x.fnord`. Not `raise AttributeError("'int' has no attribute 'fnord'")` – Wayne Werner Nov 18 '14 at 20:35
  • possible duplicate of [How can I modify a Python traceback object when raising an exception?](http://stackoverflow.com/questions/1603940/how-can-i-modify-a-python-traceback-object-when-raising-an-exception) – ivan_pozdeev Nov 19 '14 at 03:35

2 Answers2

6

You cannot tamper with traceback objects (and that's a good thing). You can only control how you process one that you've already got.

The only exceptions are: you can

For your purpose, the way to go appears to be the 1st option: re-raise an exception from a handler one level above your function.

And, I'll say this again, this is harmful for yourself or whoever will be using your module as it deletes valuable diagnostic information. If you're dead set on making your module proprietary with whatever rationale, it's more productive for that goal to make it a C extension.

ivan_pozdeev
  • 31,209
  • 16
  • 94
  • 140
  • That looks like it's specifically for _printing_ the error message - I'm interested in actually changing the Exception itself. – Wayne Werner Nov 18 '14 at 16:13
  • the [traceback](https://docs.python.org/2/library/traceback.html) module contains several functions which allows you to walk through the stack. Get the frame (using [tb_frame()](https://docs.python.org/2/library/inspect.html#types-and-members)) and jump in the list. Regarding the exception, you can define a [new one](https://docs.python.org/2/tutorial/errors.html) following this documentation. – dcexcal Nov 19 '14 at 14:40
-1

You can get the current frame and any other level using the inspect module. For instance, here is what I use when I'd like to know where I'm in my code :

from inspect import currentframe

def get_c_frame(level = 0) :
    """
    Return caller's frame
    """
    return currentframe(level)

...
def locate_error(level = 0) :
    """
    Return a string containing the filename, function name and line
    number where this function was called.

    Output is : ('file name' - 'function name' - 'line number')
    """
    fi = get_c_frame(level = level + 2)
    return '({} - {} - {})'.format(__file__,
                               fi.f_code,
                               fi.f_lineno)
dcexcal
  • 177
  • 7