307

I'm writing my own container, which needs to give access to a dictionary inside by attribute calls. The typical use of the container would be like this:

dict_container = DictContainer()
dict_container['foo'] = bar
...
print dict_container.foo

I know that it might be stupid to write something like this, but that's the functionality I need to provide. I was thinking about implementing this in a following way:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        try:
            return self.dict[item]
        except KeyError:
            print "The object doesn't have such attribute"

I'm not sure whether nested try/except blocks are a good practice, so another way would be to use hasattr() and has_key():

def __getattribute__(self, item):
        if hasattr(self, item):
            return object.__getattribute__(item)
        else:
            if self.dict.has_key(item):
                return self.dict[item]
            else:
                raise AttributeError("some customised error")

Or to use one of them and one try catch block like this:

def __getattribute__(self, item):
    if hasattr(self, item):
        return object.__getattribute__(item)
    else:
        try:
            return self.dict[item]
        except KeyError:
            raise AttributeError("some customised error")

Which option is most Pythonic and elegant?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Michal
  • 6,411
  • 6
  • 32
  • 45

11 Answers11

267

Your first example is perfectly fine. Even the official Python documentation recommends this style known as EAFP.

Personally, I prefer to avoid nesting when it's not necessary:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(self, item)
    except AttributeError:
        pass  # Fallback to dict
    try:
        return self.dict[item]
    except KeyError:
        raise AttributeError("The object doesn't have such attribute") from None

PS. has_key() has been deprecated for a long time in Python 2. Use item in self.dict instead.

lqc
  • 7,434
  • 1
  • 25
  • 25
  • 2
    `return object.__getattribute__(item)` is incorrect and will produce a `TypeError` because the wrong number of arguments are being passed. It should instead be `return object.__getattribute__(self, item)`. – martineau Jun 20 '14 at 01:05
  • 20
    PEP 20: flat is better than nested. – 0 _ Jan 25 '15 at 18:25
  • 14
    What does the `from None` mean in the last line? – niklas Mar 27 '17 at 23:10
  • 2
    @niklas It essentially suppresses exception context ("during handling this exception another exception occurred"-esque messages). See [here](https://stackoverflow.com/a/24752607) – Kade Feb 27 '18 at 18:32
  • The fact that the Python docs recommend nesting try's is kind of insane. It's obviously horrendous style. The correct way of handling a chain of operations where things can fail like that would be using some sort of monadic construct, which Python doesn't support. – Henry Henrinson Sep 18 '19 at 18:43
  • Why is there a `from None` when there isn't any other exception in the context? Doesn't this risk suppressing useful info if another exception handler calls and triggers the AttributeError? – deed02392 Jul 26 '23 at 11:02
  • 1
    @deed02392 There is a context of KeyError and that is the only information that will be suppressed. If you're at the stage when using ```'__getattribute__' ``` makes sense, it is usually because you want to provide a meaningful abstraction. Presenting KeyError will only confuse the user of the abstraction as they never created any dictionaries. – lqc Aug 09 '23 at 07:50
26

While in Java it's indeed a bad practice to use exceptions for flow control (mainly because exceptions force the JVM to gather resources (more here)), in Python you have two important principles: duck typing and EAFP. This basically means that you are encouraged to try using an object the way you think it would work, and handle when things are not like that.

In summary, the only problem would be your code getting too much indented. If you feel like it, try to simplify some of the nestings, like lqc suggested in the suggested answer above.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bruno Penteado
  • 2,234
  • 2
  • 23
  • 26
20

Just be careful - in this case the first finally is touched, but skipped too.

def a(z):
    try:
        100/z
    except ZeroDivisionError:
        try:
            print('x')
        finally:
            return 42
    finally:
        return 1


In [1]: a(0)
x
Out[1]: 1
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sławomir Lenart
  • 7,543
  • 4
  • 45
  • 61
15

For your specific example, you don't actually need to nest them. If the expression in the try block succeeds, the function will return, so any code after the whole try/except block will only be run if the first attempt fails. So you can just do:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass
    # execution only reaches here when try block raised AttributeError
    try:
        return self.dict[item]
    except KeyError:
        print "The object doesn't have such attribute"

Nesting them isn't bad, but I feel like leaving it flat makes the structure more clear: you're sequentially trying a series of things and returning the first one that works.

Incidentally, you might want to think about whether you really want to use __getattribute__ instead of __getattr__ here. Using __getattr__ will simplify things because you'll know that the normal attribute lookup process has already failed.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
13

A good and simple example for nested try/except could be the following:

import numpy as np

def divide(x, y):
    try:
        out = x/y
    except:
        try:
            out = np.inf * x / abs(x)
        except:
            out = np.nan
    finally:
        return out

Now try various combinations and you will get the correct result:

divide(15, 3)
# 5.0

divide(15, 0)
# inf

divide(-15, 0)
# -inf

divide(0, 0)
# nan

(Of course, we have NumPy, so we don't need to create this function.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ravaging Care
  • 832
  • 2
  • 9
  • 11
8

In my opinion, this would be the most Pythonic way to handle it, although and because it makes your question moot. Note that this defines __getattr__() instead of __getattribute__() , because doing so means it only has to deal with the "special" attributes being kept in the internal dictionary.

def __getattr__(self, name):
    ''' Only called when an attribute lookup in the "usual" places has failed. '''
    try:
        return self.my_dict[name]
    except KeyError:
        raise AttributeError("some customized error message")
martineau
  • 119,623
  • 25
  • 170
  • 301
  • 2
    Note that raising an exception in an `except` block may give confusing output in Python 3. That's because (per PEP 3134) Python 3 tracks the first exception (the `KeyError`) as the "context" of the second exception (the `AttributeError`), and if it reaches the top level, it will print a traceback that includes both exceptions. This can be helpful when a second exception was not expected, but if you're deliberately raising the second exception, it's undesirable. For Python 3.3, PEP 415 added the ability to suppress the context by using `raise AttributeError("whatever") from None`. – Blckknght Jun 10 '13 at 02:42
  • 3
    @Blckknght: Printing a traceback that includes both exceptions would be fine in this case. In other words, I don't think your blanket statement that it's always undesirable is true. In the usage here, it's turning a `KeyError` into an `AttributeError` and showing that's what happened in a traceback would be useful and appropriate. – martineau Jun 10 '13 at 09:42
  • For more complicated situations you may be right, but I think that when you're converting between exception types you often know that the details of the first exception don't matter to the outside user. That is, if `__getattr__` raises an exception the bug is probably a typo in the attribute access, not an implementation bug in the current class's code. Showing the earlier exception as context can muddle that. And even when you suppress the context with `raise Whatever from None`, you can still get at the previous exception if necessary via `ex.__context__`. – Blckknght Jun 11 '13 at 02:41
  • 2
    I wanted to accept your answer however in the question I was more curious whether or not using nested try/catch block is a good practice. On the other hand it's the most elegant solution and I'm going to use it in my code. Many thanks Martin. – Michal Jun 11 '13 at 09:52
  • Michal: You're welcome. It's also faster than using `__getattribute__()`. – martineau Jun 11 '13 at 12:28
6

According to the documentation, it is better to handle multiple exceptions through tuples or like this:

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as e:
    print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error: ", sys.exc_info()[0]
    raise
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Blairg23
  • 11,334
  • 6
  • 72
  • 72
  • 3
    This answer doesn't really address the original question but for anyone reading it note the "bare" except at the end is a terrible idea (usually) as it will catch everything including eg NameError & KeyboardInterrupt - which is not usually what you meant! – lost Jan 10 '19 at 08:02
  • Given that the code re-raises the same exception right after the print statement, is this really a big deal. In this case it can provide more context on the exception without hiding it. If it didn't re-raise then I would totally agree, but I don't think there is a risk to hiding an exception that you didn't intend to. – NimbusScale Jun 11 '20 at 21:25
5

In Python it is easier to ask for forgiveness than permission. Don't sweat the nested exception handling.

(Besides, has* almost always uses exceptions under the cover anyway.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
3

I like to avoid raising a new exception while handling an old one. It makes the error messages confusing to read.

For example, in my code, I originally wrote

try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    raise KeyError(key)

And I got this message.

>>> During handling of above exception, another exception occurred.

I wanted this:

try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    pass
raise KeyError(key)

It doesn't affect how exceptions are handled. In either block of code, a KeyError would have been caught. This is merely an issue of getting style points.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Steve Zelaznik
  • 616
  • 7
  • 16
2

If try-except-finally is nested inside a finally block, the result from "child" finally is preserved. I have not found an official explanation yet, but the following code snippet shows this behavior in Python 3.6.

def f2():
    try:
        a = 4
        raise SyntaxError
    except SyntaxError as se:
        print('log SE')
        raise se from None
    finally:
        try:
            raise ValueError
        except ValueError as ve:
            a = 5
            print('log VE')
            raise ve from None
        finally:
            return 6
        return a

In [1]: f2()
log SE
log VE
Out[2]: 6
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Guanghua Shu
  • 95
  • 4
  • 14
-1

I don't think it's a matter of being Pythonic or elegant. It's a matter of preventing exceptions as much as you can. Exceptions are meant to handle errors that might occur in code or events you have no control over.

In this case, you have full control when checking if an item is an attribute or in a dictionary, so avoid nested exceptions and stick with your second attempt.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
owobeid
  • 173
  • 1
  • 9
  • 1
    From the docs: _In a multi-threaded environment, the LBYL_ (Look Before You Leap) _approach can risk introducing a race condition between “the looking” and “the leaping”. For example, the code, if key in mapping: return mapping[key] can fail if another thread removes key from mapping after the test, but before the lookup. This issue can be solved with locks or by using the EAFP_ (Easier to ask for forgiveness than permission) _approach._ – Nuno André Jul 20 '16 at 14:06