Python:迭代通过对象执行代码在某些地方以及最后

时间:2014-12-16 18:15:51

标签: python list code-duplication

以下是一些解释的示例代码:

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)包装将在函数中重复的代码。

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;使用该值作为标志来表示&#34;写入,但现在退出&#34;:

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,但这最终可能会产生一些奇怪的边缘情况,如果&#39;如果&#39;语句是否需要使用任何更复杂的布尔逻辑。

是否有更清洁或更多的&#34; Pythonic&#34;没有代码重复的方法来实现这一目标?

谢谢!

4 个答案:

答案 0 :(得分:2)

当我发现自己编写这样的代码时,我会在循环结束后迭代一个集合并重复代码,我通常把它当作一个标志,表示我没有在右边迭代的事情。

在这种情况下,您将迭代对象列表。但是,我认为真正想要迭代的是一个对象的列表。 itertools.groupby对{{3}}有用。

您的代码有很多,所以我将使用一个简化的示例来说明如何摆脱重复的代码。对于(一个非常人为的)例子,我说有一个像这样的事情列表:

things = ["apples", "oranges", "pears", None, 
          "potatoes", "tomatoes", None,
          "oatmeal", "eggs"]

这是一个对象列表。仔细观察,有几组由None分隔的对象(请注意,您通常将things表示为嵌套列表,但为了示例的目的,请忽略它们)。我的目标是在一个单独的行上打印出每个小组:

apples, oranges, pears
potatoes, tomatoes
oatmeal, eggs

这里是&#34;丑陋的&#34;这样做的方式:

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)

如您所见,循环后我们有重复的print。讨厌!

以下是使用groupby的解决方案:

from itertools import groupby

for key, group in groupby(things, key=lambda x: x is not None):
    if key:
        print ", ".join(group)

groupby采用可迭代(things)和关键函数。它查看iterable的每个元素并应用键函数。当密钥更改值时,将形成一个新组。结果是一个返回(key, group)对的迭代器。

在这种情况下,我们会使用None的检查作为我们的关键功能。这就是我们需要if key:的原因,因为会有一些与我们列表中的None元素相对应的大小为1的组。我们只是跳过那些。

如您所见,groupby允许我们迭代我们 想要迭代的事物: group 对象。这对我们的问题更自然,因此代码简化了。看起来你的代码与上面的例子非常相似,只是你的key函数会检查对象的各种属性(obj.id < 400 ...)。我会将实施细节留给您......

答案 1 :(得分:2)

这是一种使用哨兵对象的方法。它类似于你的第二种选择,但我认为更干净。

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

答案 2 :(得分:1)

您可以将分解对象的代码分隔为生成器,以便不需要复制后面的处理步骤。

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)

答案 3 :(得分:1)

如果使用迭代器,有一个解决方案,next可以在最后给出一个特殊值。因此,您可以使用标记来检查当前对象是否为真对象,或者是否完成了迭代。

尝试这样的事情:

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