37

私の問題は一般的な問題です。中間の属性ルックアップの1つが返される可能性がある場合に、一連の属性ルックアップをチェーンする方法ですが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これは私が書くことを可能にするでしょう:

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

reducepython3.xでは、それは移動されたと思い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

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

このような中途半端なnull属性を処理するための私の最善の策は、ここのrepl.itのサンプルコードとしてpydashを使用することです。

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

Python3.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 に答える