“ For”循环第一次迭代


76

我想询问在第一次循环迭代中是否存在一种优雅的pythonic方式来执行某些功能。我能想到的唯一可能性是:

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

Answers:


46

头尾设计模式有多种选择。

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 )

人们抱怨这不是“ DRY”,因为“冗余foo(member)”代码。这是一个荒谬的说法。如果是这样,那么所有功能只能使用一次。如果只能有一个引用,定义一个函数有什么意义?


您通过member这种方式额外污染了名称空间。
Skilldrick

5
从技术上讲,这不是DRY,因为您要在两个地方复制与任何成员打交道的语义,但是由于代码太短且太紧,我认为这很不重要。但是,如果两者之间有更多的代码,或者如果两者被抽象为单独的函数,那么我正确地指出它违反了DRY原理。
丹尼尔·布鲁斯

@Skilldrick:示例代码还用一种名称的两种不同含义污染了名称空间member。一个member是头。其他members在哪里的尾巴。他们都是成员。我不确定你在说什么。
S.Lott

@Daniel Bruce:我看不到foo在两个(或多个)位置使用DRY违反了怎么做。该函数已被重用。这不是函数定义的重点吗?
S.Lott

是否干燥,只要您不重复一行或两行以上的代码,这是最容易阅读的方法,并且胜过我书中的其他问题。如果序列很长,并且您不想创建all-elements-one的临时副本,则可以使用itertools.islice。
musicinmybrain

72

这样的事情应该起作用。

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

但是,我强烈建议您考虑一下您的代码,看看您是否真的必须这样做,因为它有点“脏”。最好是先获取需要特殊处理的元素,然后对循环中的所有其他元素进行常规处理。

我看不到这样做的唯一原因是要从生成器表达式中获得一个很大的列表(您不希望预先获取它,因为它无法容纳在内存中)或类似情况。


实际上,您不需要庞大的清单。如果something.get()返回一个生成器(而不是一个列表),那么您就很高兴。
彼得·罗威尔

对于具有元组的循环,这将如何工作?即。for i, a, b, c, in os.walk(input_dir):?这给了ValueError: need more than 3 values to unpack
2014年

您的示例代码有多个错误:for-expression中的逗号结尾,并且没有对enumerate()的调用。您必须手动在for循环内打开元组的包装: python for i,tuple in enumerate(os.walk(...)): a, b, c = tuple
Daniel Bruce

但这将检查条件的次数与某物容器中是否存在元素的次数相同
aderchox

13

怎么样:

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)

索引方法的文档。


1
如果my_array的其他成员与第一个成员比较相等,则第一个选项不起作用。
Joooeey

如果您编写,它既省力又易于阅读if member is my_array[0]。但是,如果my_array在索引0和另一个索引处引用相同的对象,这仍然行不通。
Joooeey

6

这有效:

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

不过,在大多数情况下,我建议只是迭代whatever[1:]并在循环外进行根操作。通常更具可读性。当然,这取决于您的用例。


1
-1:“不是数字”?这真是晦涩难懂。数字== 0有什么问题?
S.Lott

1
我将使用number == 0,因为它匹配您感兴趣的语义(可迭代的第一项,其索引为0)。但是,此语法远非“令人费解”,尽管对测试序列空度更为有用。
musicinmybrain

1
然后,将其设为== 0(如果看起来更清晰)。0是唯一评估为False的数字。
balpha

6

在这里,我可以带一个看起来很“漂亮”的Pythonic习惯用法。虽然,很可能我会使用您在提出问题时建议的形式,只是为了使代码保持更明显,尽管不太优雅。

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)

3

如果something.get()遍历某些东西,您也可以按照以下步骤进行操作:

root.copy(something.get())

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

3

我认为第一个S.Lott解决方案是最好的,但是如果您使用的是最新的python(我认为> = 2.6,因为izip_longest在该版本之前似乎不可用),还有另一种选择,它可以为第一个元素和后续元素,并且可以轻松修改以对第一个,第二个,第三个元素...进行不同的操作。

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)

这也许很聪明,但是还远远不够清晰或直观。
亚伦·麦克米林

3

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

编辑:返回OP的问题,有一个要在所有元素上执行的通用操作,然后一个要在第一个元素上执行的操作,另一个要在其余元素上执行。

如果只是一个函数调用,我会说只写两次。它不会终结世界。如果涉及更多,则可以使用装饰器通过通用操作包装“第一个”功能和“其余”功能。

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)

1

你不能root.copy(something.get())在循环之前做吗?

编辑:对不起,我错过了第二点。但是您了解了一般想法。否则,枚举并检查0

EDIT2:好的,摆脱了愚蠢的第二个想法。


1

我不了解Python,但是我几乎使用了您示例的确切模式。
我所做的也是使if病情最频繁,因此通常检查if( first == false )
为什么?对于长循环,first仅在一次循环中为true,而在其他所有时间均为false,这意味着在除第一个循环之外的所有循环中,程序都会检查条件并跳转至else部分。
通过首先检查是否为假,将只有一个跳转到其他部分。我真的不知道这是否可以提高效率,但是无论如何,我还是做到了,只是为了与内心的书呆子保持和平。

PS:是的,我知道在输入if部分时,它也必须跳过else才能继续执行,所以我的操作方式可能没用,但感觉很好。:D


0

您的问题是矛盾的。您说“只在第一次迭代中做某事”,而实际上您是在说第一次/后续迭代中要做一些不同的事情。这是我尝试的方式:

copyfn = root.copy
for member in something.get():
    copyfn(member)
    foo(member)
    copyfn = somewhereElse.copy

0

这对我有用

    dup_count = 0
    for x in reversed(dup_list):
        dup_count += 1
        if dup_count == 1:
            print("First obj {}: {}".format(dup_count,x))
        else:
            print("Object # {}:  {}".format( dup_count,x  ))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.