2013-03-07 39 views
10

Vấn đề của tôi là một vấn đề chung, làm thế nào để chuỗi một loạt các tra cứu thuộc tính khi một trong những trung gian có thể trả về None, nhưng vì tôi gặp phải vấn đề này khi cố gắng sử dụng Beautiful Soup, tôi sẽ hỏi nó trong ngữ cảnh đó .Làm thế nào để tra cứu thuộc tính chuỗi có thể trả về None trong Python?

Soup đẹp phân tích cú pháp tài liệu HTML và trả về một đối tượng có thể được sử dụng để truy cập nội dung có cấu trúc của tài liệu đó. Ví dụ, nếu tài liệu phân tích cú pháp là trong biến soup, tôi có thể nhận được danh hiệu của mình với:

title = soup.head.title.string 

Vấn đề của tôi là nếu các tài liệu không có một tiêu đề, sau đó soup.head.title lợi nhuận None và sau string tra cứu ném một ngoại lệ. Tôi có thể phá vỡ chuỗi như:

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

nhưng điều này, để mắt của tôi, là dài dòng và khó đọc.

tôi có thể viết:

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

nhưng đó là tiết và không hiệu quả.

Một giải pháp nếu nghĩ đến, mà tôi nghĩ là có thể, sẽ tạo một đối tượng (gọi là nil) sẽ trả lại None cho bất kỳ tra cứu thuộc tính nào. Điều này sẽ cho phép tôi viết:

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

nhưng điều này khá xấu xí. Có cách nào tốt hơn?

+2

lẽ giữ mã và bắt bạn + xử lý 'AttributeError' ngoại lệ trong trường hợp' None' được trả về. – crayzeewulf

+0

Bạn muốn nó quay trở lại cái gì? – mgilson

+1

['Maybe'monad in python] (http://stackoverflow.com/questions/8507200/maybe-kind-of-monad-in-python). Xem thêm [Monads in Python (với cú pháp đẹp!)] (Http://www.valuedlessons.com/2008/01/monads-in-python-with-nice-syntax.html) – jfs

Trả lời

4

Bạn có thể có thể sử dụng reduce cho việc này:

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

Trong python3.x, tôi nghĩ rằng reduce được chuyển đến functools dù :(


Tôi cho rằng bạn cũng có thể làm điều này với chức năng đơn giản hơn:

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

Cuối cùng, tôi giả sử cách đẹp nhất để làm điều này là một cái gì đó như:

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

'reduce' có sẵn dưới dạng' functools.reduce' từ 2.6 trở đi - vì vậy việc nhập có lẽ sẽ không làm tổn thương nhiều ... –

+0

@JonClements - Tốt để biết. Cảm ơn cho những người đứng đầu lên. – mgilson

+1

Tôi thấy giải pháp này xa hơn so với các giải pháp "tiết" được đề xuất trong câu hỏi. –

8

Cách đơn giản nhất là để bọc trong một try ... except khối.

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

Có thực sự không có lý do gì để kiểm tra ở mỗi cấp khi loại bỏ mỗi bài kiểm tra sẽ tăng cùng một ngoại lệ trong trường hợp thất bại. Tôi sẽ xem xét thành ngữ này trong Python.

1

Một giải pháp sẽ là bao bọc đối tượng bên ngoài bên trong Proxy xử lý Không có giá trị nào cho bạn. Xem dưới đây để bắt đầu triển khai.

nhập khẩu 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() 

Chú ý: Tôi là cá nhân không chắc chắn thời tiết Tôi sẽ sử dụng này trong một dự án thực tế, nhưng nó làm cho một thí nghiệm thú vị và tôi đặt nó ở đây để mọi người suy nghĩ về điều này.

+0

Đây là một giải pháp thông minh, và có lẽ những gì tôi đã có trong tâm trí khi tôi hỏi câu hỏi. Giải pháp này kết thúc là khá nặng, và cũng có những bất lợi mà tất cả các truy cập thuộc tính được thực hiện ngay cả khi một trong những trung gian trả về Không và có khả năng ngắn mạch đánh giá biểu hiện. –

0

Đây là một kỹ thuật tiềm ẩn khác, ẩn đi việc gán giá trị trung gian trong một cuộc gọi phương thức. Đầu tiên chúng ta định nghĩa một lớp học để giữ giá trị trung gian:

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) 

Sau đó chúng ta có được sử dụng nó để lưu trữ các kết quả của mỗi mắt xích trong chuỗi các cuộc gọi:

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 

tôi không xem xét một này giải pháp tốt, nhưng tôi đưa nó vào đây để hoàn thành.

0

Đây là cách tôi xử lý nó với cảm hứng từ @TAS và 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() 
Các vấn đề liên quan