Search code examples
pythonkeyerror

Python KeyError and ValueError inconsistency?


I've found an inconsistency between ValueError and KeyError. The former will treat \n as a newline; the latter treats it as raw text. Is this behavior normal/expected?

TeX_dict is a dictionary to convert parts of dictionary keys to TeX formatted strings that are used when generating plots. part is one such part. Here are two examples of part:

  • a&b is successfully split and converted in the dictionary.
  • a,b&c is not.

When I raise a ValueError, the \n newline characters generate a new line. When I raise a KeyError, they do not.

geo_split = lambda thing: '\&'.join([
                                      TeX_dict[x]
                                      for x in thing.split('&')
                                    ])

try:

    TeX = geo_split(part)

except KeyError:

    msg = ("You have programmed limited functionality for "
           "geometric mean keys. They can only handle species "
           "that are hard coded into "
           "``pccv.TeX_labels.axis_labels``.\n\n" #
           "The following part failed: {}\n"
          ).format(part)

    raise ValueError(msg) # If this is KeyError, the new lines don't print.

Here is a sample output from each:

ValueError: You have programmed limited functionality for geometric mean keys. 
They can only handle species that are hard coded into 
``pccv.TeX_labels.axis_labels``.

KeyError: 'You have programmed limited functionality for geometric mean keys. 
They can only handle species that are hard coded into 
``pccv.TeX_labels.axis_labels``.\n\nThe following part failed: a,p1&p2\n' 

Solution

  • This is a peculiarity of the KeyError implementation.

    /* If args is a tuple of exactly one item, apply repr to args[0].
       This is done so that e.g. the exception raised by {}[''] prints
         KeyError: ''
       rather than the confusing
         KeyError
       alone.  The downside is that if KeyError is raised with an explanatory
       string, that string will be displayed in quotes.  Too bad.
       If args is anything else, use the default BaseException__str__().
    

    You could work around it by passing something that overrides __repr__:

    class Wrapper(str):
        def __repr__(self):
            return str(self)
    
    raise KeyError(Wrapper('hello\nthere'))