我可以在列表推导中捕获错误以确保循环遍历所有项吗
我有一个列表推导式,用来过滤一个列表:
l = [obj for obj in objlist if not obj.mycond()]
但是,方法 mycond() 可能会抛出一个异常,我需要处理这个异常。我想在循环结束时收集所有的错误,以便显示哪个对象出现了问题,同时我也想确保能遍历列表中的所有元素。
我的解决方案是:
errors = []
copy = objlist[:]
for obj in copy:
try:
if (obj.mycond()):
# avoiding to touch the list in the loop directly
objlist.remove(obj)
except MyException as err:
errors = [err]
if (errors):
#do something
return objlist
在这篇帖子中(如何在遍历列表时删除列表元素而不重复),我问是否有更好的方法来遍历列表,避免列表重复。
社区的回答是,建议避免在原地修改列表,使用列表推导式,这在我忽略异常问题的情况下是可行的。
从你的角度来看,有没有其他的解决方案?我能否在使用列表推导式时以这种方式处理异常?在这种情况下,使用大列表(我该如何定义大列表?)我是否需要找到其他替代方案?
4 个回答
与其复制列表然后删除元素,不如从一个空列表开始,根据需要添加成员。可以这样做:
errors = []
newlist = []
for obj in objlist:
try:
if not obj.mycond():
newlist.append(obj)
except MyException as err:
errors.append(err)
if (errors):
#do something
return newlist
虽然语法看起来不那么优雅,但它基本上能完成与列表推导式相同的功能,而且没有多余的删除操作。
在列表的末尾以外的地方添加或删除元素会比较慢,因为当你删除一个元素时,后面的每个元素都需要重新调整位置。添加元素也是类似的,后面的元素需要向后移动。
有几个评论:
首先,列表推导式的写法 [expression for var in iterable]
确实会创建一个列表的副本。如果你不想创建副本,可以使用生成器表达式 (expression for var in iterable)
。
那么生成器是怎么工作的呢?其实就是通过不断调用 next(obj)
这个对象,直到出现 GeneratorExit
异常为止。
根据你最初的代码,看来你还是需要一个过滤后的列表作为输出。
所以你可以用这种方式来实现,性能损失很小:
l = []
for obj in objlist:
try:
if not obj.mycond()
l.append(obj)
except Exception:
pass
不过,你可以用生成器函数重新设计一下:
def FilterObj(objlist):
for obj in objlist:
try:
if not obj.mycond()
yield obj
except Exception:
pass
这样的话,你就可以安全地遍历它,而不需要在中间存储一个列表:
for obj in FilterObj(objlist):
obj.whatever()
我会使用一个小辅助函数:
def f(obj, errs):
try: return not obj.mycond()
except MyException as err: errs.append((obj, err))
errs = []
l = [obj for obj in objlist if f(obj, errs)]
if errs:
emiterrorinfo(errs)
注意,这样做的话,你的 errs
里会包含所有出错的对象 以及 每个对象对应的具体异常信息,这样就能更准确、全面地进行诊断。此外,你需要的 l
也能得到,而你的 objlist
依然保持完整,可以继续使用。这样不需要复制列表,也不需要对 obj
的类进行任何修改,整体代码结构也非常简单。