简体   繁体   中英

Python - safe & elegant way to set a variable from function that may return None

I'm looking for a more elegant way of declaring a variable value where the function may return None and there are chained methods following the function call.

In the example below I am using BeautifulSoup to pass an HTML doc and if the element I am looking for is not found, the initial function call returns None . The chained methods then break the code because .string is not a method of None object.

Which all makes sense, but I'm wondering if there's a cleaner way to write these variable declarations that won't break on a None value.

# I want to do something like this but it throws error if soup.find returns
# none because .string is not a method of None.
title = soup.find("h1", "article-title").string or "none"


# This works but is both ugly and inefficient
title = "none" if soup.find("h1", "article-title") is None else soup.find("h1", "article-title").string

# So instead I'm using this which feels clunky as well
title = soup.find("h1", "article-title")
title = "none" if title is None else title.string

Any better way?

I like Shashank's answer, but this might work for you as well:

class placeholder:
    string = "none"

title = (soup.find("h1", "article-title") or placeholder).string

This behavior of Beautiful Soup really annoys me as well. Here's my solution: http://soupy.readthedocs.org/en/latest/

This smooths over lots of edge cases in BeautifulSoup, allowing you to write queries like

dom.find('h1').find('h2').find('a')['href'].orelse('not found').val()

Which returns what you're looking for if it exists, or 'not found' otherwise.

The general strategy in soupy is to wrap the data you care about in thin wrapper classes. A simple example of such a wrapper:

class Scalar(object):
    def __init__(self, val):
        self._val = val
    def __getattr__(self, key):
        return Scalar(getattr(self._val, key, None))
    def __call__(self, *args, **kwargs):
        return Scalar(self._val(*args, **kwargs))
    def __str__(self):
        return 'Scalar(%s)' % self._val


s = Scalar('hi there')
s.upper()  # Scalar('HI THERE')
s.a.b.c.d  # Scalar(None)

If you want to be fancy about it, the mathematical property that lets you safely chain things forever is closure (ie methods return instances of the same type). Lots of BeautifulSoup methods don't have this property, which is what soupy addresses.

You can use the getattr built-in function to provide a default value in case the desired attribute is not found within a given object:

title = getattr(soup.find("h1", "article-title"), "string", "none")

Alternatively, you can use a try statement :

try:
    title = soup.find("h1", "article-title").string
except AttributeError:
    title = "none"

The first method is more elegant in my opinion.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM