On Fri, 1 Jan 2016 03:46 am, Oscar Benjamin wrote: [...] > Exactly. The critical technique is looking at the traceback and splitting > it between what's your code and what's someone else's. Hopefully you don't > need to look at steves_library.py to figure out what you did wrong. > However if you do need to look at Steve's code you're now stumped because > he's hidden the actual line that raises. All you know now is that > somewhere in _validate the raise happened. Why hide that piece of > information and complicate the general interpretation of stack traces?
No. I don't hide anything. Here's a simple example, minus any hypothetical new syntax, showing the traditional way and the non-traditional way. # example.py def _validate(arg): if not isinstance(arg, int): # traditional error handling: raise in the validation function raise TypeError('expected an int') if arg < 0: # non-traditional: return and raise in the caller return ValueError('argument must be non-negative') def func(x): exc = _validate(x) if exc is not None: raise exc print(x+1) def main(): value = None # on the second run, edit this to be -1 func(value) main() And here's the traceback you get in each case. First, the traditional way, raising directly inside _validate: [steve@ando tmp]$ python example.py Traceback (most recent call last): File "example.py", line 17, in <module> main() File "example.py", line 15, in main func(value) File "example.py", line 8, in func exc = _validate(x) File "example.py", line 3, in _validate raise TypeError('expected an int') TypeError: expected an int What do we see? Firstly, the emphasis is on the final call to _validate, where the exception is actually raised. (As it should be, in the general case where the exception is an error.) If you're like me, you're used to skimming the traceback until you get to the last entry, which in this case is: File "example.py", line 3, in _validate and starting to investigate there. But that's a red herring, because although the exception is raised there, that's not where the error lies. _validate is pretty much just boring boilerplate that validates the arguments -- where we really want to start looking is the previous entry, func, and work backwards from there. The second thing we see is that the displayed source code for _validate is entirely redundant: raise TypeError('expected an int') gives us *nothing* we don't see from the exception itself: TypeError: expected an int This is a pretty simple exception. In a more realistic example, with a longer and more detailed message, you might see something like this as the source extract: raise TypeError(msg) where the message is set up in the previous line or lines. This is even less useful to read. So it is my argument that the traditional way of refactoring parameter checks, where exceptions are raised in the _validate function, is sub-optimal. We can do better. Here's the traceback we get from the non-traditional error handling. I edit the file to change the value = None line to value = -1 and re-run it: [steve@ando tmp]$ python example.py Traceback (most recent call last): File "example.py", line 17, in <module> main() File "example.py", line 15, in main func(value) File "example.py", line 10, in func raise exc ValueError: argument must be non-negative Nothing is hidden. We still see the descriptive exception and error message, and the line raise exc is no worse than "raise TypeError(msg)" -- all the detail we need is immediately below it. The emphasis here is on the call to func, since that's the last entry in the call stack. The advantage is that we don't see the irrelevant call to _validate *unless we go looking for it in the source code*. We start our investigate where we need to start, namely in func itself. Of course, none of this is mandatory, nor is it new. Although I haven't tried it, I'm sure that this would work as far back as Python 1.5, since exceptions are first-class values that can be passed around and raised when required. It's entirely up to the developer to choose whether this non-traditional idiom makes sense for their functions or not. Sometimes it will, and sometimes it won't. The only new part here is the idea that we could streamline the code in the caller if "raise None" was a no-op. Instead of writing this: exc = _validate(x) if exc is not None: raise exc we could write: raise _validate(x) which would make this idiom more attractive. -- Steven -- https://mail.python.org/mailman/listinfo/python-list