How to chain attribute lookups that might return None in Python?

optional-chaining, python

Solution

You might be able to use `reduce` for this:

>>> class Foo(object): pass
... 
>>> a = Foo()
>>> a.foo = Foo()
>>> a.foo.bar = Foo()
>>> a.foo.bar.baz = Foo()
>>> a.foo.bar.baz.qux = Foo()
>>> 
>>> reduce(lambda x,y:getattr(x,y,''),['foo','bar','baz','qux'],a)
<__main__.Foo object at 0xec2f0>
>>> reduce(lambda x,y:getattr(x,y,''),['foo','bar','baz','qux','quince'],a)
''

In python3.x, I think that `reduce` is moved to `functools` though :(

I suppose you could also do this with a simpler function:

def attr_getter(item,attributes)
    for a in attributes:
        try:
            item = getattr(item,a)
        except AttributeError:
            return None #or whatever on error
    return item

Finally, I suppose the nicest way to do this is something like:

try:
   title = foo.bar.baz.qux
except AttributeError:
   title = None

Problem

My problem is a general one, how to chain a series of attribute lookups when one of the intermediate ones might return `None`, but since I ran into this problem trying to use Beautiful Soup, I'm going to ask it in that context. Beautiful Soup parses an HTML document and returns an object that can be used to access the structured content of that document. For example, if the parsed document is in the variable `soup`, I can get its title with: ``` title = soup.head.title.string ``` My problem is that if the document doesn't have a title, then `soup.head.title` returns `None` and the subsequent `string` lookup throws an exception. I could break up the chain as: ``` x = soup.head x = x.title if x else None title = x.string if x else None ``` but this, to my eye, is verbose and hard to read. I could write: ``` title = soup.head and soup.head.title and soup.title.head.string ``` but that is verbose and inefficient. One solution if thought of, which I think is possible, would be to create an object (call it `nil`) that would return `None` for any attribute lookup. This would allow me to write: ``` title = ((soup.head or nil).title or nil).string ``` but this is pretty ugly. Is there a better way?

Original source

Related problems