如何在Python中链接可能返回None的属性查找?

40 投票
7 回答
15502 浏览
提问于 2025-04-17 18:19

我遇到的问题是,如何在一系列属性查找中连接起来,尤其是当其中一个中间的查找可能返回None的时候。因为我是在使用Beautiful Soup时碰到这个问题,所以我想在这个上下文中提问。

Beautiful Soup是一个用来解析HTML文档的工具,它会返回一个对象,这个对象可以用来访问文档中结构化的内容。比如,如果解析后的文档存储在变量soup中,我可以用下面的方式获取它的标题:

title = soup.head.title.string

我的问题是,如果文档没有标题,那么soup.head.title就会返回None,接下来再查找string就会出错。我可以把这个链条拆开成:

x = soup.head
x = x.title if x else None
title = x.string if x else None

但在我看来,这样写显得冗长且难以阅读。

我可以这样写:

title = soup.head and soup.head.title and soup.title.head.string

但这样也显得冗长且效率低下。

我想到的一个解决方案是创建一个对象(叫它nil),这个对象在查找任何属性时都会返回None。这样我就可以写:

title = ((soup.head or nil).title or nil).string

但这样看起来很丑陋。有没有更好的方法呢?

7 个回答

3

我正在使用 Python 3.9

Python 3.9.2 (tags/v3.9.2:1a79785, Feb 19 2021, 13:44:55) [MSC v.1928 64 bit (AMD64)]

而且 and 这个关键词解决了我的问题

memo[v] = short_combo and short_combo.copy()

根据我的理解,这种做法不太符合 Python 的风格,应该处理异常情况。
不过在我的解决方案中,函数内部存在 None 的模糊性,在这种情况下,我觉得处理大约 50% 的异常情况并不是一个好习惯。
在函数外部调用它的时候,我会处理这些异常。

24

最简单的方法就是把代码放在一个 try...except 块里。

try:
    title = soup.head.title.string
except AttributeError:
    print "Title doesn't exist!"

其实没有必要在每个层级都进行测试,因为如果去掉每个测试,失败时都会抛出相同的错误。我觉得这样做在Python中是很常见的写法。

19

你可以试试用 reduce 来实现这个功能:

>>> 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)
''

在 python3.x 版本中,我记得 reduce 被移到了 functools 这个模块里 :(


我想你也可以用一个更简单的函数来做到这一点:

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

最后,我觉得最好的方法可能是这样的:

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

撰写回答