37

我的问题是一个普遍的问题,如何在中间的一个可能返回时链接一系列属性查找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

但这很丑陋。有没有更好的办法?

4

7 回答 7

24

最直接的方法是包装在try...except块中。

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

当删除每个测试会在失败情况下引发相同的异常时,确实没有理由在每个级别进行测试。我会认为这是 Python 中的惯用语。

于 2013-03-07T20:47:12.167 回答
16

您也许可以为此使用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
于 2013-03-07T20:01:51.167 回答
1

一种解决方案是将外部对象包装在为您处理 None 值的代理中。请参阅下面的开始实施。

导入单元测试

class SafeProxy(object):

    def __init__(self, instance):
        self.__dict__["instance"] = instance

    def __eq__(self, other):
        return self.instance==other

    def __call__(self, *args, **kwargs):
        return self.instance(*args, **kwargs)

    # TODO: Implement other special members

    def __getattr__(self, name):
        if hasattr(self.__dict__["instance"], name):
            return SafeProxy(getattr(self.instance, name))

        if name=="val":
            return lambda: self.instance

        return SafeProxy(None)

    def __setattr__(self, name, value):
        setattr(self.instance, name, value)


# Simple stub for creating objects for testing
class Dynamic(object):
    def __init__(self, **kwargs):
        for name, value in kwargs.iteritems():
            self.__setattr__(name, value)

    def __setattr__(self, name, value):
        self.__dict__[name] = value


class Test(unittest.TestCase):

    def test_nestedObject(self):
        inner = Dynamic(value="value")
        middle = Dynamic(child=inner)
        outer = Dynamic(child=middle)
        wrapper = SafeProxy(outer)
        self.assertEqual("value", wrapper.child.child.value)
        self.assertEqual(None, wrapper.child.child.child.value)

    def test_NoneObject(self):
        self.assertEqual(None, SafeProxy(None))

    def test_stringOperations(self):
        s = SafeProxy("string")
        self.assertEqual("String", s.title())
        self.assertEqual(type(""), type(s.val()))
        self.assertEqual()

if __name__=="__main__":
    unittest.main()

注意:我个人不确定我是否会在实际项目中使用它,但它是一个有趣的实验,我把它放在这里是为了让人们对此产生想法。

于 2013-03-07T21:03:44.407 回答
0

这是另一种潜在的技术,它在方法调用中隐藏了中间值的分配。首先我们定义一个类来保存中间值:

class DataHolder(object):
    def __init__(self, value = None):
            self.v = value

    def g(self):
            return self.v

    def s(self, value):
            self.v = value
            return value

x = DataHolder(None)

然后我们用它来存储调用链中每个链接的结果:

import bs4;

for html in ('<html><head></head><body></body></html>',
             '<html><head><title>Foo</title></head><body></body></html>'):
    soup = bs4.BeautifulSoup(html)
    print x.s(soup.head) and x.s(x.g().title) and x.s(x.g().string)
    # or
    print x.s(soup.head) and x.s(x.v.title) and x.v.string

我不认为这是一个好的解决方案,但为了完整起见,我将其包含在此处。

于 2013-03-07T23:54:45.580 回答
0

我处理这样的中间空属性的最佳方法是在 repl.it 上使用pydash作为示例代码

import pydash
title = pydash.get(soup, 'head.title.string', None)
于 2020-11-30T16:01:59.940 回答
0

我正在运行 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()

据我所知,这不是pythonic,您应该处理异常。
然而,在我的解决方案None中,函数中存在歧义,在这种情况下,我认为处理大约 50% 的时间发生的异常是一种糟糕的做法。
在函数之外并调用它的地方,我将处理异常。

于 2021-03-28T15:01:52.963 回答
-1

这就是我从@TAS 获得灵感的处理方式,是否有像 Ruby 的 andand 这样的 Python 库(或模式)?

class Andand(object):
    def __init__(self, item=None):
        self.item = item

    def __getattr__(self, name):
        try:
            item = getattr(self.item, name)
            return item if name is 'item' else Andand(item)
        except AttributeError:
            return Andand()     

    def __call__(self):
        return self.item


title = Andand(soup).head.title.string()
于 2014-04-24T16:50:33.107 回答