Python: Iterate through object executing code both at certain places and also at end
下面是一些要解释的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 | outputText="" counter=0 for obj in specialObjects: if (obj.id < 400) or (obj.name.startswith("he")) or (obj.deliberateBreak==True): print"The object %s is causing a section break."%obj.details outputText = outputText.rjust(80) open("file%d.txt"%counter,"w").write(outputText) outputText="" outputText+=obj.shortValue() # THIS CODE IS DUPLICATED outputText = outputText.rjust(80) open("file%d.txt"%counter,"w").write(outputText) |
我需要做的是迭代这些特殊对象的列表,每次检查几个不同的条件。如果满足任何条件(如图所示),那么我需要获取当前输出缓冲区,将其写入一个文件,然后启动一个新的输出缓冲区并继续处理。
这里的问题是代码重复。注意这两行(outputText=和open)是如何复制的。如果我没有输入第二组行,最后一组对象将被处理,但它们的输出将永远不会被写入。
我可以想到两种可能的解决方案来防止代码重复。它们看起来都有点不雅,所以我想知道是否还有更好的方法。
1)包装将在一个函数中重复的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | outputText="" counter=0 for obj in specialObjects: if (obj.id < 400) or (obj.name.startswith("he")) or (obj.deliberateBreak==True): print"The object %s is causing a section break."%obj.details counter = writeData(outputText) outputText="" outputText+=obj.shortValue() writeData(outputText,counter) def writeData(outputText,counter): outputText = outputText.rjust(80) open("file%d.txt"%counter,"w").write(outputText) return counter+1 |
2)使用数字for循环,计数比对象列表的长度高一个;使用该值作为标志表示"写入,但现在退出":
1 2 3 4 5 6 7 8 9 10 11 | outputText="" counter=0 for obj in range(len(specialObjects))+1: if (obj = len(specialObjects)) or (specialObjects[obj].id < 400) or (specialObjects[obj].name.startswith("he")) or (specialOejcts[obj].deliberateBreak==True): print"The object %s is causing a section break."%specialObjects[obj].details outputText = outputText.rjust(80) open("file%d.txt"%counter,"w").write(outputText) outputText="" if (obj==len(specialObjects)): break outputText+=specialObjects[obj].shortValue() |
如果我必须选择一个,我可能会选择2,但如果需要使用更复杂的布尔逻辑,这可能最终会用"if"语句创建一些奇怪的边缘情况。
有没有一种更干净或更"Python式"的方法可以在不重复代码的情况下完成这项工作?
谢谢!
这里有一种处理哨兵物体的方法。这和你的第二个选择很相似,但我觉得更干净。
1 2 3 4 5 6 7 8 | for obj in itertools.chain(specialObjects, [None]): if (obj is None) or (obj.id < 400) or (obj.name.startswith("he")) or (obj.deliberateBreak==True): outputText = outputText.rjust(80) open("file%d.txt"%counter,"w").write(outputText) if obj is None: break print"The object %s is causing a section break."%obj.details outputText="" outputText+=obj.shortValue() |
当我发现自己在编写这样的代码时,我在一个集合上进行迭代,并在循环结束后重复代码,我通常把它当作我没有在正确的事情上进行迭代的标志。
在本例中,您将遍历对象列表。但我认为,您真正想要迭代的是一组对象的列表。这就是
您的代码有很多情况,所以我将使用一个简化的示例来说明如何消除重复的代码。例如,我有一个这样的列表:
1 2 3 | things = ["apples","oranges","pears", None, "potatoes","tomatoes", None, "oatmeal","eggs"] |
这是对象列表。仔细看,有几个由
1 2 3 | apples, oranges, pears potatoes, tomatoes oatmeal, eggs |
这样做的"丑陋"方式是:
1 2 3 4 5 6 7 8 9 | current_things = [] for thing in things: if thing is None: print",".join(current_things) current_things = [] else: current_things.append(thing) print",".join(current_things) |
如你所见,我们在循环之后有一个复制的
下面是使用
1 2 3 4 5 | from itertools import groupby for key, group in groupby(things, key=lambda x: x is not None): if key: print",".join(group) |
在这种情况下,我们将使用
如您所见,
如果使用迭代器,有一个解决方案,
尝试如下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | outputText="" counter=0 ending = object() it = iter(specialObjects) while True: obj = next(it, ending) if obj is ending or obj.id < 400 or obj.name.startswith("he") or obj.deliberateBreak: outputText = outputText.rjust(80) open("file%d.txt"%counter,"w").write(outputText) counter += 1 outputText="" if obj is ending: break outputText+=obj.shortValue() |
您可以将分解对象的代码分离到生成器中,这样以后的处理步骤就不需要重复了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def yield_sections(specialObjects): outputText = '' for obj in specialObjects: if (obj.id < 400) or (obj.name.startswith("he")) or (obj.deliberateBreak==True): yield outputText outputText = '' outputText += obj.shortValue() if outputText: yield outputText for counter, outputText in enumerate(yield_sections(specialObjects)): outputText = outputText.rjust(80) open("file%d.txt"%counter,"w").write(outputText) |