PyWart: NameError trackbacks are superfluous

Jason Swails jason.swails at gmail.com
Sun Mar 17 17:22:39 EDT 2013


On Sat, Mar 16, 2013 at 2:27 PM, Rick Johnson
<rantingrickjohnson at gmail.com>wrote:

>
> [snip junk]
> We don't need multiple layers of traces for NameErrors. Python does not
> have *real* global variables; and thank Guido for that! All we need to know
> is which module the error occurred in AND which line of that module
> contains the offensive lookup of a name that does not exist.
> [snip more junk]


2 comments here.

1) Where's the consistency??  NameError is an exception.  All other
exceptions get full tracebacks.  A NameError is not special enough to
deserve special treatment (zen of Python? PEP 8?  I don't remember).  If
you like the full traceback (like I do), it's there. If you just want the
error, look at the last frame only.

==== Example where you want a full traceback ====

Pyflakes doesn't catch all NameErrors.  If you don't set all of an object's
possible attributes inside its constructor, but some are applied later-on
(i.e., when they are needed, like in some sort of specialized setup routine
that you only call in certain circumstances), then you can benefit from a
full traceback even for a NameError. Consider the DataSet class below:

import numpy as np
from scipy.stats.kde import gaussian_kde as kde
class DataSet(object):

   def __init__(self, data):
      self._dataset = np.asarray(data)
   def setup_kde(self):
      "Set up KDE. Do not do by default since it is expensive for large
data sets"
      self.kde = kde(self._dataset)
   def resampled(self):
      return DataSet(self.kde.resample())

In this case generating the KDE can become time- and RAM-consuming for
large data sets, so it's worthwhile to only generate the KDE if you
actually plan on doing something with that requires it.  If you call
resampled before setup_kde, you get a NameError, and it would be helpful to
have the full traceback when debugging.  Yes, you can get around having the
'non-trivial' NameError, but having the full traceback if someone _did_
write code like this makes your job a hell of a lot easier to debug if you
don't have to go stack tracing yourself. And for a large project with
multiple coders that is built in stages and has functionality added as it's
needed, this type of situation is not at all unusual.  While one approach
is to take to the (email) streets and proclaim how incompetent all coders
that came before you truly were and that such a project is not worth
modifying in view of their ineptitude, the approach that does _not_ lead to
your firing would benefit from a full NameError traceback. Go ahead and
fire away about how stupid I am for the suggestion anyway, though.

2) Fine.  You don't like long tracebacks for NameErrors, write a 8-line
module that you import at the top of every program (like division from
__future__ if you work with Python 2 like I have to):

--- file ricksstupididea.py ---
import sys, traceback
def excepthook(exctype, value, tb):
   if exctype is NameError:
      print 'Traceback (only printing last frame):'
      print '  File "%s", line %d, in %s\n    %s' %
traceback.extract_tb(tb).pop()
      print 'NameError: %s' % value
   else:
      sys.__excepthook__(exctype, value, tb)
sys.excepthook = excepthook
--- end ricksstupididea.py ---

Once you do this, you get your special behavior for NameErrors like you
want.  Observe, padawan:

-- begin nameerror.py --
import ricksstupididea
def func1(x):
   return func2(x)

def func2(x):
   return func3(x)

def func3(x):
   return func4(x)

def func4(x):
   return noname

func1(1)

$ python noname.py
Traceback (only printing last frame):
  File "noname.py", line 12, in func4
    return noname
NameError: global name 'noname' is not defined

-- begin divzero.py --
import ricksstupididea
def func1(x):
   return func2(x)

def func2(x):
   return func3(x)

def func3(x):
   return func4(x)

def func4(x):
   return x / 0

func1(1)

$ python divzero.py
Traceback (most recent call last):
  File "divzero.py", line 14, in <module>
    func1(1)
  File "divzero.py", line 3, in func1
    return func2(x)
  File "divzero.py", line 6, in func2
    return func3(x)
  File "divzero.py", line 9, in func3
    return func4(x)
  File "divzero.py", line 12, in func4
    return x / 0
ZeroDivisionError: integer division or modulo by zero

GvR's time machine strikes again.  The only bad thing about this approach
is that you have to actually write that extra 8 lines of code, and I get to
keep the behavior I like rather than being forced into the (inconsistent)
behavior you would prefer I use instead.  But since the 8 lines has been
provided to you here free of charge, the only real cost for you is how I
like my NameErrors given to me.

I hereby leave you enlightened.

</rant>
Jason
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-list/attachments/20130317/d800ceda/attachment.html>


More information about the Python-list mailing list