"For"循环第一次迭代

98

我想问一下,是否有一种优雅的Pythonic方法,在第一次循环迭代时执行某个函数。 我能想到的唯一可能性是:

first = True
for member in something.get():
    if first:
        root.copy(member)
        first = False
    else:
        somewhereElse.copy(member)
    foo(member)
14个回答

93

类似这样的代码应该可以运行。

for i, member in enumerate(something.get()):
    if i == 0:
         # Do thing
    # Code for everything

不过,我强烈建议您思考一下自己的代码,看看是否真的需要这样做,因为这种方式有点“肮脏”。更好的方法是提前获取需要特殊处理的元素,然后在循环中为所有其他元素执行常规处理。

我能想到不这样做的唯一原因是对于从生成器表达式获取的大列表(您不希望提前获取它,因为它无法适应内存)或类似情况。


实际上,你并不需要一个大列表。如果 something.get() 返回一个生成器(而不是一个列表),那么就没问题了。 - Peter Rowell
这对于一个包含元组的循环如何工作呢?例如:for i, a, b, c, in os.walk(input_dir):?这会导致 ValueError: need more than 3 values to unpack - bmikolaj
你的示例代码有多个错误:for表达式中有尾随逗号,并且没有调用enumerate()。你需要在for循环内手动解包元组:for i,tuple in enumerate(os.walk(...)): a, b, c = tuple - Daniel Bruce
但是这将检查 something 容器中的元素数量次数的条件... - aderchox

53

Head-Tail设计模式中,你有几个选择。

seq= something.get()
root.copy( seq[0] )
foo( seq[0] )
for member in seq[1:]:
    somewhereElse.copy(member)
    foo( member )
这个或者那个。
seq_iter= iter( something.get() )
head = seq_iter.next()
root.copy( head )
foo( head )
for member in seq_iter:
    somewhereElse.copy( member )
    foo( member )

有些人抱怨这段代码中的"redundant foo(member)"不符合"DRY"原则,这是一个荒谬的说法。如果这是真的,那么所有函数都只能使用一次。定义函数的意义在于可以有多个引用,否则就没有意义了。


你这样做会在命名空间中添加一个额外的 member,从而污染它。 - Skilldrick
5
从技术上讲,这并不符合DRY原则,因为你在两个地方复制了处理任何成员的语义,但由于代码非常短且紧密,所以我认为这一点无关紧要。然而,如果两者之间有更多的代码,或者将两者抽象为单独的函数,则我会正确指出它违反了DRY原则。 - Daniel Bruce
@Skilldrick:示例代码还使用了一个名称member,污染了命名空间并赋予了两个不同的含义。其中一个member是头部;另一个members则是尾部。但它们都是成员。我不确定你想表达什么意思。 - S.Lott
@Daniel Bruce:我无法看出在两个(或更多)地方使用foo是如何违反DRY原则的。这是一个被重复使用的函数。这不是函数定义的意义所在吗? - S.Lott
...或者为了避免切片,你可以直接使用第二个代码片段;这样做比较整洁,因为你不必处理索引。 - musicinmybrain
显示剩余2条评论

16

这样怎么样:

my_array = something.get()
for member in my_array:
    if my_array.index(member) == 0:
        root.copy(member)
    else:
        somewhereElse.copy(member)
    foo(member)

或者也许是:

for index, member in enumerate(something.get()):
    if index == 0:
        root.copy(member)
    else:
        somewhereElse.copy(member)
    foo(member)

index-method 的文档。


1
如果my_array有其他与第一个成员相等的成员,则第一种选项无效。 - Joooeey
如果你写成 if member is my_array[0],那么代码会更加安全易读。但是,如果 my_array 在索引 0 和另一个索引处引用了同一个对象,这种方法仍然无法正常工作。 - Joooeey

8

这是可行的:

for number, member in enumerate(something.get()):
    if not number:
        root.copy(member)
    else:
        somewhereElse.copy(member)
    foo(member)

在大多数情况下,我建议只迭代whatever[1:]并在循环外执行根操作;当然这取决于你的使用场景,但通常更易读。

1
“-1:‘not number’?”这真的是非常晦涩难懂。为什么不用数字==0呢? - S.Lott
1
我会使用number == 0,因为它与您感兴趣的语义相匹配(可迭代对象的第一项,其索引为0)。但是,这种语法远非“极端晦涩”,尽管更适用于测试序列是否为空。 - musicinmybrain
2
然后将其设置为数字 == 0,如果这样更清晰。 0是唯一的评估为False的数字。 - balpha
1
我熟悉使用 not something 检测零的情况,具体要看上下文。有些代码/数据场景非常混乱,最好使用 not 来消除杂散的 None 甚至是 "" 如果代码很糟糕,这些都是可能的话。在其他情况下,你明确地 想检测到其他类似的假值。在这里,你正在处理从 enumerate 生成的数字,所以为了绝对清晰,number == 0 我认为会更好一些,但使用 not 绝不会 "极其难懂"!哈哈,如果你编写 Python,而你不知道什么是假值,那你就麻烦了。 - NeilG

7

在这里,我可以用一种Python惯用语,看起来很“漂亮”。虽然,最有可能我会使用你在提问中建议的形式,只是为了让代码更明显,尽管不太优雅。

def copy_iter():
    yield root.copy
    while True:
        yield somewhereElse.copy

for member, copy in zip(something.get(), copy_iter()):
    copy(member)
    foo(member)

很抱歉 - 我在编辑之前发布的第一篇文章无法工作,我忘记为“copy”对象实际获取迭代器了。


哈哈,我在看到你的解决方案之前就想到了同样的方法,但是使用了itertools :-) - fortran

6
我认为这很优雅,但对于它所做的事情来说可能太复杂了...
from itertools import chain, repeat, izip
for place, member in izip(chain([root], repeat(somewhereElse)), something.get()):
    place.copy(member)
    foo(member)

4

如果 something.get() 遍历了 something,你也可以按照以下方式完成:

root.copy(something.get())

for member in something.get():
  #  the rest of the loop

3

使用iter,并消耗第一个元素如何?

编辑:回到问题,有一个常见的操作是要对所有元素执行,然后对第一个元素执行另一个操作,对其余元素执行另一个操作。

如果只是单个函数调用,我建议写两次。这不会毁掉世界。如果涉及更多操作,您可以使用装饰器将“第一个”函数和“其余”函数与一个公共操作包装起来。

def common(item):
    print "common (x**2):", item**2

def wrap_common(func):
    """Wraps `func` with a common operation"""
    def wrapped(item):
        func(item)
        common(item)
    return wrapped

@wrap_common
def first(item):
    """Performed on first item"""
    print "first:", item+2

@wrap_common
def rest(item):
    """Performed on rest of items"""
    print "rest:", item+5

items = iter(range(5))
first(items.next())

for item in items:
    rest(item)

输出:

first: 2
common (x**2): 0
rest: 6
common (x**2): 1
rest: 7
common (x**2): 4
rest: 8
common (x**2): 9
rest: 9
common (x**2): 16

或者您可以使用切片:

first(items[0])
for item in items[1:]:
    rest(item)

3

我认为S.Lott的第一种解决方案是最好的选择,但如果你使用的是相当新的Python(>= 2.6,因为在该版本之前似乎没有izip_longest可用),那么还有另一个选择,可以让你针对第一个元素和后续元素执行不同的操作,并且可以很容易地修改以执行不同的操作,如第1个、第2个、第3个元素等。

from itertools import izip_longest

seq = [1, 2, 3, 4, 5]

def headfunc(value):
    # do something
    print "1st value: %s" % value

def tailfunc(value):
    # do something else
    print "this is another value: %s" % value

def foo(value):
    print "perform this at ANY iteration."

for member, func in izip_longest(seq, [headfunc], fillvalue=tailfunc):
    func(member)
    foo(member)

2
这可能很聪明,但远非清晰或直观。 - Aaron McMillin

1

循环之前不能执行root.copy(something.get())吗?

编辑:抱歉,我错过了第二部分。但你明白我的意思。否则,枚举并检查0

编辑2:好的,放弃了愚蠢的第二个想法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接