【问题标题】:catch errors within generator and continue afterwards在生成器中捕获错误并在之后继续
【发布时间】:2012-11-30 12:18:55
【问题描述】:

我有一个应该运行几天的迭代器。我希望捕获并报告错误,然后我希望迭代器继续。或者整个过程可以重新开始。

函数如下:

def get_units(self, scraper):
    units = scraper.get_units()
    i = 0
    while True:
        try:
            unit = units.next()
        except StopIteration:
            if i == 0:
                log.error("Scraper returned 0 units", {'scraper': scraper})
            break
        except:
            traceback.print_exc()
            log.warning("Exception occurred in get_units", extra={'scraper': scraper, 'iteration': i})
        else:
            yield unit
        i += 1

因为scraper 可能是许多代码变体之一,它不可信,我不想处理那里的错误。

但是当units.next()出现错误时,整个事情就停止了。我怀疑是因为迭代器在其中一个迭代失败时抛出StopIteration

这是输出(只有最后几行)

[2012-11-29 14:11:12 /home/amcat/amcat/scraping/scraper.py:135 DEBUG] Scraping unit <Element div at 0x4258c710>
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article
[2012-11-29 14:11:13 /home/amcat/amcat/scraping/scraper.py:138 DEBUG] .. yields article Counter-Strike: Global Offensive Update Released
Traceback (most recent call last):
  File "/home/amcat/amcat/scraping/controller.py", line 101, in get_units
    unit = units.next()
  File "/home/amcat/amcat/scraping/scraper.py", line 114, in get_units
    for unit in self._get_units():
  File "/home/amcat/scraping/games/steamcommunity.py", line 90, in _get_units
    app_doc = self.getdoc(url,urlencode(form))
  File "/home/amcat/amcat/scraping/scraper.py", line 231, in getdoc
    return self.opener.getdoc(url, encoding)
  File "/home/amcat/amcat/scraping/htmltools.py", line 54, in getdoc
    response = self.opener.open(url, encoding)
  File "/usr/lib/python2.7/urllib2.py", line 406, in open
    response = meth(req, response)
  File "/usr/lib/python2.7/urllib2.py", line 519, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python2.7/urllib2.py", line 444, in error
    return self._call_chain(*args)
  File "/usr/lib/python2.7/urllib2.py", line 378, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.7/urllib2.py", line 527, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
HTTPError: HTTP Error 500: Internal Server Error
[2012-11-29 14:11:14 /home/amcat/amcat/scraping/controller.py:110 WARNING] Exception occurred in get_units

...code ends...

那么如何防止在发生错误时停止迭代呢?

编辑:这是 get_units() 中的代码

def get_units(self):
    """                                                                                                                                                                                                                                  
    Split the scraping job into a number of 'units' that can be processed independently                                                                                                                                                  
    of each other.                                                                                                                                                                                                                       

    @return: a sequence of arbitrary objects to be passed to scrape_unit                                                                                                                                                                 
    """
    self._initialize()
    for unit in self._get_units():
        yield unit

这是一个简化的 _get_units():

INDEX_URL = "http://www.steamcommunity.com"

def _get_units(self):
  doc = self.getdoc(INDEX_URL)  #returns a lxml.etree document

  for a in doc.cssselect("div.discussion a"):
    link = a.get('href')
    yield link

编辑:问题跟进:Alter each for-loop in a function to have error handling executed automatically after each failed iteration

【问题讨论】:

  • “当get_units 发生错误时”,您的意思是“当unit.next() 发生错误时”吗?
  • easy_install pep8,下次运行pep8 mysourcefile.py并在发布前解决所有问题:) ...并阅读python.org/dev/peps/pep-0008
  • 我已经修好了。另外,永远不要使用except:——而是更喜欢except Exception:——否则你甚至会捕捉到KeyboardInterrupt这样的东西,因此用户无法使用Ctrl-C退出程序。

标签: python exception-handling try-catch iteration


【解决方案1】:

StopIteration 在没有下一项时由生成器的next() 方法引发。它与生成器/迭代器内部的错误无关。

另外需要注意的是,根据迭代器的类型,它可能无法在异常后恢复。如果迭代器是一个带有next 方法的对象,它将起作用。但是,如果它实际上是一个生成器,它就不会。

据我所知,这是在units.next() 出现错误后您的迭代无法继续的唯一原因。 IE。 units.next() 失败,下次你调用它时,它无法恢复,它说它是通过抛出一个 StopIteration 异常来完成的。

基本上,您必须向我们展示scraper.get_units() 中的代码,以便我们了解为什么在单次迭代中出现错误后循环无法继续。如果get_units() 被实现为生成器函数,那就很清楚了。如果没有,可能是其他原因阻止它恢复。

更新:解释什么是生成器函数:

class Scraper(object):
    def get_units(self):
        for i in some_stuff:
            bla = do_some_processing()
            bla *= 2  # random stuff
            yield bla

现在,当您调用 Scraper().get_units() 时,它不会运行整个函数,而是返回一个生成器对象。对它调用next(),将执行到第一个yield。等等。现在如果在get_units 内的任何地方发生错误,它就会被污染,可以这么说,下次你调用next() 时,它会引发StopIteration,就好像它已经用完了要给的东西一样你。

强烈推荐阅读http://www.dabeaz.com/generators/(和http://www.dabeaz.com/coroutines/)。

UPDATE2:一个可能的解决方案https://gist.github.com/4175802

【讨论】:

  • 代码正在传入,但你能指出我对差异的解释吗?
  • 好吧,这就是我的答案。谢谢!我有近 50 个带发电机的刮刀,有什么办法可以避免全部重建吗?
  • 你能在错误发生的地方处理错误吗? IE。在原来的发电机里面?这样,您就可以从包装 get_units() 函数中隐藏任何失败的迭代。见stackoverflow.com/questions/7472786/…
  • 嗯,我可以,但这需要花费数小时重新构建所有功能。也不会很干。
  • 我确定有解决方案,但我们必须查看更多您的代码。该解决方案可能涉及完全不同的方法。例如,我可以想到一个用于在单个迭代中调用的包装函数,它将异常转换为一些返回值,然后下游消费者可以决定忽略这些值。然后try-except 子句将只在一个地方——那个包装函数。
猜你喜欢
  • 2017-11-04
  • 1970-01-01
  • 1970-01-01
  • 2015-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多