"For" loop first iteration
Asked Answered
M

14

104

I would like to inquire if there is an elegant pythonic way of executing some function on the first loop iteration. The only possibility I can think of is:

first = True
for member in something.get():
    if first:
        root.copy(member)
        first = False
    else:
        somewhereElse.copy(member)
    foo(member)
Melodramatize answered 18/12, 2009 at 11:3 Comment(1)
This is the easiest to understand code and hence more 'Pythonic' than the answers.Teeny
T
54

You have several choices for the Head-Tail design pattern.

seq= something.get()
root.copy( seq[0] )
foo( seq[0] )
for member in seq[1:]:
    somewhereElse.copy(member)
    foo( member )

Or this

seq_iter= iter( something.get() )
head = seq_iter.next()
root.copy( head )
foo( head )
for member in seq_iter:
    somewhereElse.copy( member )
    foo( member )

People whine that this is somehow not "DRY" because the "redundant foo(member)" code. That's a ridiculous claim. If that was true then all functions could only be used once. What's the point of defining a function if you can only have one reference?

Tardy answered 18/12, 2009 at 11:9 Comment(7)
You're polluting the namespace with an extra member this way.Apollonius
Technically it isn't DRY as you are replicating the semantics of how you deal with any member in two places, but as the code is so short and close together I think that point is moot. However, if there was more code between the two, or if the two were abstracted out into separate functions, then I would rightfully point out that it violates the DRY principle.Charlena
@Skilldrick: The sample code also polluted the namespace with two different meanings for one name, member. One member was the head; the other members where the tail. They were all members however. I'm not sure what point you're making.Tardy
@Daniel Bruce: I can't see how using foo in two (or more) places violated DRY. It's a function that's reused. Isn't that the point of a function definition?Tardy
DRY or not, this is the easiest to read as long as you aren’t repeating more than a line or two of code, and that trumps the other concerns in my book. If the sequence is long and you don’t want to create a temporary copy of all-elements-but-one, you could use itertools.islice.Alligator
...or to avoid slicing you could just use the second code snippet; that’s kind of tidy because you don’t have to muck with indices.Alligator
I'd really like to be able to enter an iteration half way down the loop...Aloise
C
94

Something like this should work.

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

However, I would strongly recommend thinking about your code to see if you really have to do it this way, because it's sort of "dirty". Better would be to fetch the element that needs special handling up front, then do regular handling for all the others in the loop.

The only reason I could see for not doing it this way is for a big list you'd be getting from a generator expression (which you wouldn't want to fetch up front because it wouldn't fit in memory), or similar situations.

Charlena answered 18/12, 2009 at 11:7 Comment(4)
Actually, you don't need to have a big list. If something.get() returns a generator (as opposed to a list) then you're golden.Gorton
How would this work for a loop that has a tuple? ie. for i, a, b, c, in os.walk(input_dir):? This gives ValueError: need more than 3 values to unpack.Bullroarer
Your example code has multiple errors: trailing comma in for-expression, and no call to enumerate(). You would have to manually unpack the tuple inside the for-loop: python for i,tuple in enumerate(os.walk(...)): a, b, c = tuple Charlena
But this will check the condition as many times as there are elements in the something container...Debose
T
54

You have several choices for the Head-Tail design pattern.

seq= something.get()
root.copy( seq[0] )
foo( seq[0] )
for member in seq[1:]:
    somewhereElse.copy(member)
    foo( member )

Or this

seq_iter= iter( something.get() )
head = seq_iter.next()
root.copy( head )
foo( head )
for member in seq_iter:
    somewhereElse.copy( member )
    foo( member )

People whine that this is somehow not "DRY" because the "redundant foo(member)" code. That's a ridiculous claim. If that was true then all functions could only be used once. What's the point of defining a function if you can only have one reference?

Tardy answered 18/12, 2009 at 11:9 Comment(7)
You're polluting the namespace with an extra member this way.Apollonius
Technically it isn't DRY as you are replicating the semantics of how you deal with any member in two places, but as the code is so short and close together I think that point is moot. However, if there was more code between the two, or if the two were abstracted out into separate functions, then I would rightfully point out that it violates the DRY principle.Charlena
@Skilldrick: The sample code also polluted the namespace with two different meanings for one name, member. One member was the head; the other members where the tail. They were all members however. I'm not sure what point you're making.Tardy
@Daniel Bruce: I can't see how using foo in two (or more) places violated DRY. It's a function that's reused. Isn't that the point of a function definition?Tardy
DRY or not, this is the easiest to read as long as you aren’t repeating more than a line or two of code, and that trumps the other concerns in my book. If the sequence is long and you don’t want to create a temporary copy of all-elements-but-one, you could use itertools.islice.Alligator
...or to avoid slicing you could just use the second code snippet; that’s kind of tidy because you don’t have to muck with indices.Alligator
I'd really like to be able to enter an iteration half way down the loop...Aloise
M
15

how about:

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

or maybe:

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

Documentation of index-method.

Midinette answered 20/12, 2009 at 13:6 Comment(2)
The first option doesn't work if my_array has other members that compare equal to the first member.Zosi
It's saver and easier to read if you write if member is my_array[0]. But this still doesn't work if my_array references the same object at index 0 and another index.Zosi
M
8

This works:

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

In most cases, though, I'd suggest just iterating over whatever[1:] and doing the root thing outside the loop; that's usually more readable. Depends on your use case, of course.

Moneywort answered 18/12, 2009 at 11:7 Comment(4)
-1: "not number"? That's really toweringly obscure. What's wrong with number == 0?Tardy
I’d use number == 0 because that matches the semantics you’re interested in (the first item of the iterable, which has index 0). However, this syntax is far from “toweringly obscure,” though more useful for testing sequence emptiness.Alligator
Then make it number == 0, if that seems clearer. 0 is the only number evaluating to False.Moneywort
I'm familiar with using not something to detect zero. It depends on the context. Some code / data scenarios are so woolly that it's best to use not so that you can eliminate stray None and even "" if the code is so bad that those are possible. In other contexts you specifically don't want to detect other falsish values. Here you're dealing with numbers generated from enumerate so to be absolutely clear number == 0 I think would be better, but there's no way it's "toweringly obscure" to use not! Lol, if you code Python and you don't know what's falsish you're in trouble.Aloise
C
7

Here, I could come with a Pythonic idiom that can look "pertty". Although, most likely I'd use the form you suggested in asking the question, just for the code to remain more obvious, though less elegant.

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

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

(sorry - the first I posted, before editing, form would not work, I had forgotten to actually get an iterator for the 'copy' object)

Cardinale answered 18/12, 2009 at 11:36 Comment(1)
he he, I came up to the same solution before seeing yours, but using itertools :-)Declass
D
6

I think this is quite elegant, but maybe too convoluted for what it does...

from itertools import chain, repeat, izip
for place, member in izip(chain([root], repeat(somewhereElse)), something.get()):
    place.copy(member)
    foo(member)
Declass answered 18/12, 2009 at 11:55 Comment(0)
M
4

If something.get() iterates over something, you can do it also as follows:

root.copy(something.get())

for member in something.get():
  #  the rest of the loop
Mathias answered 18/12, 2009 at 11:8 Comment(0)
W
3

How about using iter, and consuming the first element?

Edit: Going back on the OP's question, there is a common operation that you want to perform on all elements, and then one operation you want to perform on the first element, and another on the rest.

If it's just a single function call, I'd say just write it twice. It won't end the world. If it's more involved, you can use a decorator to wrap your "first" function and "rest" function with a common operation.

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)

Output:

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

or you could do a slice:

first(items[0])
for item in items[1:]:
    rest(item)
Weakkneed answered 18/12, 2009 at 12:37 Comment(0)
C
3

I think the first S.Lott solution is the best, but there's another choice if you're using a pretty recent python (>= 2.6 I think, since izip_longest doesn't seem available before that version) that lets doing different things for the first element and successive one, and can be easily modified to do distinct operations for 1st, 2nd, 3rd element... as well.

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)
Centiliter answered 18/12, 2009 at 14:23 Comment(2)
This maybe clever, but it's far from clear or intuitive.Liner
This is wrong if seq is empty, as the fillvalue=tailfunc will then be mistreated as a member.Rufusrug
A
1

Can't you do root.copy(something.get()) before the loop?

EDIT: Sorry, I missed the second bit. But you get the general idea. Otherwise, enumerate and check for 0?

EDIT2: Ok, got rid of the silly second idea.

Apollonius answered 18/12, 2009 at 11:7 Comment(0)
G
1

I don't know Python, but I use almost the exact pattern of your example.
What I do also is making the if condition the most frequent, so usually check for if( first == false )
Why? for long loops, first will be true only one time and will be false all the other times, meaning that in all loops but the first, the program will check for the condition and jump to the else part.
By checking for first being false, there will be only one jump to the else part. I don't really know if this adds efficiency at all, but I do it anyway, just to be in peace with my inner nerd.

PS: Yes, I know that when entering the if part, it also has to jump over the else to continue execution, so probably my way of doing it is useless, but it feels nice. :D

Grady answered 18/12, 2009 at 12:50 Comment(0)
D
0

Your question is contradictory. You say "only do something on first iteration", when in fact you are saying do something different on first/subsequent iterations. This is how I would attempt it:

copyfn = root.copy
for member in something.get():
    copyfn(member)
    foo(member)
    copyfn = somewhereElse.copy
Dreamy answered 20/2, 2017 at 22:16 Comment(0)
C
0

Here is what works for me

    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  ))
Coffman answered 28/7, 2020 at 21:52 Comment(0)
R
0

With a helper that only runs for the first member and then replaces itself appropriately:

def copy(member):
    root.copy(member)
    global copy
    copy = somewhereElse.copy

for member in something.get():
    copy(member)
    foo(member)

(If your loop is inside a function, replace global with nonlocal.)

Rufusrug answered 5/4, 2023 at 11:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.