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

时间:2013-03-07 19:55:39

标签: python

我的问题是一般的问题,如果其中一个中间问题可能返回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

但这是冗长而低效的。

如果我认为可能的一个解决方案是创建一个对象({1}}),它将返回nil进行任何属性查找。这将允许我写:

None

但这非常难看。还有更好的方法吗?

7 个答案:

答案 0 :(得分:8)

最直接的方法是包裹try ... except块。

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

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

答案 1 :(得分:7)

您可以使用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

答案 2 :(得分:1)

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

import unittest

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

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

答案 3 :(得分: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

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

答案 4 :(得分:0)

这就是我在@TAS和Is there a Python library (or pattern) like Ruby's andand?

的灵感来处理它的方式
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()

答案 5 :(得分:0)

处理类似这样的中间null属性的最佳方法是使用pydash作为repl.it here上的示例代码

import pydash
title = pydash.get(soup, 'head.title.string', None)

答案 6 :(得分: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% 的时间发生的异常是一种糟糕的做法。
在函数之外并调用它的地方,我会处理异常。