Weird exception handling behavior -- late evaluation in except clause

Discussion in 'Python' started by Roy Smith, Dec 2, 2012.

  1. Roy Smith

    Roy Smith Guest

    This is kind of weird (Python 2.7.3):

    try:
    print "hello"
    except foo:
    print "foo"

    prints "hello". The problem (IMHO) is that apparently the except clause
    doesn't get evaluated until after some exception is caught. Which means
    it never notices that foo is not defined until it's too late.

    This just came up in some code, where I was trying to catch a very rare
    exception. When the exception finally happened, I discovered that I had
    a typo in the except clause (I had mis-spelled the name of the
    exception). So, instead of getting some useful information, I got an
    AttributeError :-(

    Is this a bug, or intended behavior? It seems to me it would be much
    more useful (if slightly more expensive) to evaluate the names of the
    exceptions in the expect clause before running the try block.
     
    Roy Smith, Dec 2, 2012
    #1
    1. Advertising

  2. Roy Smith

    Hans Mulder Guest

    Re: Weird exception handling behavior -- late evaluation in exceptclause

    On 2/12/12 18:25:22, Roy Smith wrote:
    > This is kind of weird (Python 2.7.3):
    >
    > try:
    > print "hello"
    > except foo:
    > print "foo"
    >
    > prints "hello". The problem (IMHO) is that apparently the except clause
    > doesn't get evaluated until after some exception is caught. Which means
    > it never notices that foo is not defined until it's too late.
    >
    > This just came up in some code, where I was trying to catch a very rare
    > exception. When the exception finally happened, I discovered that I had
    > a typo in the except clause (I had mis-spelled the name of the
    > exception). So, instead of getting some useful information, I got an
    > AttributeError :-(
    >
    > Is this a bug, or intended behavior? It seems to me it would be much
    > more useful (if slightly more expensive) to evaluate the names of the
    > exceptions in the expect clause before running the try block.


    It's intended behaviour: Python runs your script from top to bottom.
    It doesn't peek ahead at except: clauses. Even it an exception is
    raised, the exception names are not eveluated all at once. Python
    begins by evaluating the first exception name. It the exception at
    hand is an instance of that, Python has found a match and the rest
    of the names are left unevaluated:

    >>> try:

    .... 1/0
    .... except ZeroDivisionError:
    .... print "hello"
    .... except 1/0:
    .... pass
    ....
    hello

    There's probably some lint-like tool that can find this kind of issue.


    Hope this helps,

    -- HansM
     
    Hans Mulder, Dec 2, 2012
    #2
    1. Advertising

  3. Roy Smith

    Terry Reedy Guest

    Re: Weird exception handling behavior -- late evaluation in exceptclause

    On 12/2/2012 12:25 PM, Roy Smith wrote:
    > This is kind of weird (Python 2.7.3):
    >
    > try:
    > print "hello"
    > except foo:
    > print "foo"
    >
    > prints "hello". The problem (IMHO) is that apparently the except clause
    > doesn't get evaluated until after some exception is caught. Which means
    > it never notices that foo is not defined until it's too late.
    >
    > This just came up in some code, where I was trying to catch a very rare
    > exception. When the exception finally happened, I discovered that I had
    > a typo in the except clause (I had mis-spelled the name of the
    > exception). So, instead of getting some useful information, I got an
    > AttributeError :-(


    You would have the same problem if you spelled the exception name right
    but misspelled a name in the corresponding block. This is PyLint,
    PyChecker, xxx territory.

    Note that proper checking requires execution of imports.

    import mymod

    try: pass
    except mymod.MymogException: pass # whoops

    > Is this a bug, or intended behavior? It seems to me it would be much
    > more useful (if slightly more expensive) to evaluate the names of the
    > exceptions in the expect clause before running the try block.


    'try:' is very cheap. Searching ahead any checking names in all the
    except clauses would be much more expensive -- and useless after the
    first time the code is run after being revised.

    --
    Terry Jan Reedy
     
    Terry Reedy, Dec 2, 2012
    #3
  4. Re: Weird exception handling behavior -- late evaluation in exceptclause

    On Sun, 02 Dec 2012 12:25:22 -0500, Roy Smith wrote:

    > This is kind of weird (Python 2.7.3):
    >
    > try:
    > print "hello"
    > except foo:
    > print "foo"
    >
    > prints "hello". The problem (IMHO) is that apparently the except clause
    > doesn't get evaluated until after some exception is caught. Which means
    > it never notices that foo is not defined until it's too late.


    This is exactly the same as, well, everything in Python. Nothing is
    evaluated until needed.

    Consider this piece of legal Python code:

    Err = None
    if condition(x) > 100:
    Err = OneException
    elif another_condition(x):
    Err = AnotherException
    try:
    spam(a, b, c)
    except Err:
    recover()


    And consider that spam() may very well set the global Err to yet another
    value, or delete it altogether, before raising. How should Python check
    that Err is defined to an actual exception ahead of time?

    The names of exceptions are no different from any other names in Python.
    They're merely names, and they're looked up at runtime. If you want to
    boggle/confuse/annoy your friends, shadowing builtins can help:


    py> def spam(x):
    .... global UnicodeEncodeError
    .... UnicodeEncodeError = ZeroDivisionError
    .... return 1/x
    ....
    py> try:
    .... spam(0)
    .... except UnicodeEncodeError:
    .... print("Divided by zero")
    ....
    Divided by zero


    (By the way, if you ever do this by accident, you can recover with a
    simple "del UnicodeEncodeError" to restore access to the builtin.)

    As Terry has said, those sort of pre-runtime checks are the
    responsibility of pylint or pychecker or equivalent. Python is too
    dynamic to allow the sort of compile-time checks you can get from a
    Haskell, C or Pascal, so the Python compiler delegates responsibility to
    third-party applications. There's no point in making an exceptions for
    exceptions.


    > This just came up in some code, where I was trying to catch a very rare
    > exception. When the exception finally happened, I discovered that I had
    > a typo in the except clause (I had mis-spelled the name of the
    > exception). So, instead of getting some useful information, I got an
    > AttributeError :-(


    One of the nice features of Python 3:

    py> try:
    .... 1/0
    .... except ZeroDividingError:
    .... pass
    ....
    Traceback (most recent call last):
    File "<stdin>", line 2, in <module>
    ZeroDivisionError: division by zero

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File "<stdin>", line 3, in <module>
    NameError: name 'ZeroDividingError' is not defined


    Very useful for debugging unexpected errors in except clauses. The
    downside is that until Python 3.3, there's no way to turn it off when you
    intentionally catch one exception and raise another in it's place.


    > Is this a bug, or intended behavior? It seems to me it would be much
    > more useful (if slightly more expensive) to evaluate the names of the
    > exceptions in the expect clause before running the try block.


    "Slightly" more expensive? Methinks you are underestimating the level of
    dynamism of Python.

    py> def pick_an_exception():
    .... global x
    .... if 'x' in globals():
    .... return TypeError
    .... x = None
    .... return NameError
    ....
    py> for i in range(3):
    .... try:
    .... print(x + 1)
    .... except pick_an_exception() as err:
    .... print("Caught", err)
    ....
    Caught name 'x' is not defined
    Caught unsupported operand type(s) for +: 'NoneType' and 'int'
    Caught unsupported operand type(s) for +: 'NoneType' and 'int'


    The except expression can be arbitrarily expensive, with arbitrary side-
    effects.



    --
    Steven
     
    Steven D'Aprano, Dec 2, 2012
    #4
  5. Re: Weird exception handling behavior -- late evaluation in exceptclause

    On Mon, Dec 3, 2012 at 8:31 AM, Steven D'Aprano
    <> wrote:
    > Consider this piece of legal Python code:
    >
    > Err = None
    > if condition(x) > 100:
    > Err = OneException
    > elif another_condition(x):
    > Err = AnotherException
    > try:
    > spam(a, b, c)
    > except Err:
    > recover()


    Legal it may be, but are there times when you actually _need_ this
    level of dynamism? It strikes me as a pretty weird way of going about
    things.

    I agree with the point you're making, but this feels like a contrived
    example, and I'm curious as to whether it can be uncontrived.

    ChrisA
     
    Chris Angelico, Dec 3, 2012
    #5
  6. Re: Weird exception handling behavior -- late evaluation in exceptclause

    On Mon, 03 Dec 2012 16:24:50 +1100, Chris Angelico wrote:

    > On Mon, Dec 3, 2012 at 8:31 AM, Steven D'Aprano
    > <> wrote:
    >> Consider this piece of legal Python code:
    >>
    >> Err = None
    >> if condition(x) > 100:
    >> Err = OneException
    >> elif another_condition(x):
    >> Err = AnotherException
    >> try:
    >> spam(a, b, c)
    >> except Err:
    >> recover()

    >
    > Legal it may be, but are there times when you actually _need_ this level
    > of dynamism? It strikes me as a pretty weird way of going about things.
    >
    > I agree with the point you're making, but this feels like a contrived
    > example, and I'm curious as to whether it can be uncontrived.



    Yeah, in hindsight it was a pretty crappy example. But this sort of
    dynamism really is useful:

    def testRaises(exc, func, *args):
    try:
    result = func(*args)
    except exc:
    return
    raise AssertionError("expected exception but didn't get one")


    def wrap(func, exc, default=None):
    @functools.wraps(func)
    def inner(*args):
    try:
    return func(*args)
    except exc:
    return default
    return inner


    --
    Steven
     
    Steven D'Aprano, Dec 3, 2012
    #6
  7. Re: Weird exception handling behavior -- late evaluation in exceptclause

    On Mon, Dec 3, 2012 at 6:30 PM, Steven D'Aprano
    <> wrote:
    > Yeah, in hindsight it was a pretty crappy example. But this sort of
    > dynamism really is useful:
    >
    > def testRaises(exc, func, *args):
    > try:
    > result = func(*args)
    > except exc:
    > return
    > raise AssertionError("expected exception but didn't get one")
    >
    >
    > def wrap(func, exc, default=None):
    > @functools.wraps(func)
    > def inner(*args):
    > try:
    > return func(*args)
    > except exc:
    > return default
    > return inner


    Ah, that makes good sense. The 'except' clause takes a parameter, so
    it follows logically that you could pass a parameter to something that
    wraps an except clause.

    ChrisA
     
    Chris Angelico, Dec 3, 2012
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. dejan budimir
    Replies:
    6
    Views:
    387
    Rolf Magnus
    Jul 4, 2004
  2. Prateek R Karandikar
    Replies:
    11
    Views:
    596
  3. George Young

    blanket except clause -- best practice?

    George Young, Oct 28, 2003, in forum: Python
    Replies:
    3
    Views:
    439
    John J. Lee
    Oct 29, 2003
  4. Bob Alistar
    Replies:
    0
    Views:
    476
    Bob Alistar
    Dec 4, 2003
  5. Nebur
    Replies:
    5
    Views:
    339
    Gabriel Genellina
    May 30, 2007
Loading...

Share This Page