Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > A quirk/gotcha of for i, x in enumerate(seq) when seq is empty

Reply
Thread Tools

A quirk/gotcha of for i, x in enumerate(seq) when seq is empty

 
 
Alex Willmer
Guest
Posts: n/a
 
      02-24-2012
This week I was slightly surprised by a behaviour that I've not
considered before. I've long used

for i, x in enumerate(seq):
# do stuff

as a standard looping-with-index construct. In Python for loops don't
create a scope, so the loop variables are available afterward. I've
sometimes used this to print or return a record count e.g.

for i, x in enumerate(seq):
# do stuff
print 'Processed %i records' % i+1

However as I found out, if seq is empty then i and x are never
created. The above code will raise NameError. So if a record count is
needed, and the loop is not guaranteed to execute the following seems
more correct:

i = 0
for x in seq:
# do stuff
i += 1
print 'Processed %i records' % i

Just thought it worth mentioning, curious to hear other options/
improvements/corrections.
 
Reply With Quote
 
 
 
 
Steven D'Aprano
Guest
Posts: n/a
 
      02-24-2012
On Thu, 23 Feb 2012 16:30:09 -0800, Alex Willmer wrote:

> This week I was slightly surprised by a behaviour that I've not
> considered before. I've long used
>
> for i, x in enumerate(seq):
> # do stuff
>
> as a standard looping-with-index construct. In Python for loops don't
> create a scope, so the loop variables are available afterward. I've
> sometimes used this to print or return a record count e.g.
>
> for i, x in enumerate(seq):
> # do stuff
> print 'Processed %i records' % i+1
>
> However as I found out, if seq is empty then i and x are never created.


This has nothing to do with enumerate. It applies to for loops in
general: the loop variable is not initialised if the loop never runs.
What value should it take? Zero? Minus one? The empty string? None?
Whatever answer Python choose would be almost always wrong, so it refuses
to guess.


> The above code will raise NameError. So if a record count is needed, and
> the loop is not guaranteed to execute the following seems more correct:
>
> i = 0
> for x in seq:
> # do stuff
> i += 1
> print 'Processed %i records' % i


What fixes the problem is not avoiding enumerate, or performing the
increments in slow Python instead of fast C, but that you initialise the
loop variable you care about before the loop in case it doesn't run.

i = 0
for i,x in enumerate(seq):
# do stuff

is all you need: the addition of one extra line, to initialise the loop
variable i (and, if you need it, x) before hand.




--
Steven
 
Reply With Quote
 
 
 
 
Paul Rubin
Guest
Posts: n/a
 
      02-24-2012
Alex Willmer <(E-Mail Removed)> writes:
> i = 0
> for x in seq:
> # do stuff
> i += 1
> print 'Processed %i records' % i
>
> Just thought it worth mentioning, curious to hear other options/
> improvements/corrections.


Stephen gave an alternate patch, but you are right, it is a pitfall that
can be easy to miss in simple testing.

A more "functional programming" approach might be:

def do_stuff(x): ...

n_records = sum(1 for _ in imap(do_stuff, seq))
 
Reply With Quote
 
Ethan Furman
Guest
Posts: n/a
 
      02-24-2012
Steven D'Aprano wrote:
> On Thu, 23 Feb 2012 16:30:09 -0800, Alex Willmer wrote:
>
>> This week I was slightly surprised by a behaviour that I've not
>> considered before. I've long used
>>
>> for i, x in enumerate(seq):
>> # do stuff
>>
>> as a standard looping-with-index construct. In Python for loops don't
>> create a scope, so the loop variables are available afterward. I've
>> sometimes used this to print or return a record count e.g.
>>
>> for i, x in enumerate(seq):
>> # do stuff
>> print 'Processed %i records' % i+1
>>
>> However as I found out, if seq is empty then i and x are never created.

>
> This has nothing to do with enumerate. It applies to for loops in
> general: the loop variable is not initialised if the loop never runs.
> What value should it take? Zero? Minus one? The empty string? None?
> Whatever answer Python choose would be almost always wrong, so it refuses
> to guess.
>
>
>> The above code will raise NameError. So if a record count is needed, and
>> the loop is not guaranteed to execute the following seems more correct:
>>
>> i = 0
>> for x in seq:
>> # do stuff
>> i += 1
>> print 'Processed %i records' % i

>
> What fixes the problem is not avoiding enumerate, or performing the
> increments in slow Python instead of fast C, but that you initialise the
> loop variable you care about before the loop in case it doesn't run.
>
> i = 0
> for i,x in enumerate(seq):
> # do stuff
>
> is all you need: the addition of one extra line, to initialise the loop
> variable i (and, if you need it, x) before hand.


Actually,

i = -1

or his reporting will be wrong.

~Ethan~
 
Reply With Quote
 
Mark Lawrence
Guest
Posts: n/a
 
      02-24-2012
On 24/02/2012 03:49, Ethan Furman wrote:
> Steven D'Aprano wrote:
>> On Thu, 23 Feb 2012 16:30:09 -0800, Alex Willmer wrote:
>>
>>> This week I was slightly surprised by a behaviour that I've not
>>> considered before. I've long used
>>>
>>> for i, x in enumerate(seq):
>>> # do stuff
>>>
>>> as a standard looping-with-index construct. In Python for loops don't
>>> create a scope, so the loop variables are available afterward. I've
>>> sometimes used this to print or return a record count e.g.
>>>
>>> for i, x in enumerate(seq):
>>> # do stuff
>>> print 'Processed %i records' % i+1
>>>
>>> However as I found out, if seq is empty then i and x are never created.

>>
>> This has nothing to do with enumerate. It applies to for loops in
>> general: the loop variable is not initialised if the loop never runs.
>> What value should it take? Zero? Minus one? The empty string? None?
>> Whatever answer Python choose would be almost always wrong, so it
>> refuses to guess.
>>
>>
>>> The above code will raise NameError. So if a record count is needed, and
>>> the loop is not guaranteed to execute the following seems more correct:
>>>
>>> i = 0
>>> for x in seq:
>>> # do stuff
>>> i += 1
>>> print 'Processed %i records' % i

>>
>> What fixes the problem is not avoiding enumerate, or performing the
>> increments in slow Python instead of fast C, but that you initialise
>> the loop variable you care about before the loop in case it doesn't run.
>>
>> i = 0
>> for i,x in enumerate(seq):
>> # do stuff
>>
>> is all you need: the addition of one extra line, to initialise the
>> loop variable i (and, if you need it, x) before hand.

>
> Actually,
>
> i = -1
>
> or his reporting will be wrong.
>
> ~Ethan~


Methinks an off by one error

--
Cheers.

Mark Lawrence.

 
Reply With Quote
 
Peter Otten
Guest
Posts: n/a
 
      02-24-2012
Ethan Furman wrote:

> Steven D'Aprano wrote:
>> On Thu, 23 Feb 2012 16:30:09 -0800, Alex Willmer wrote:
>>
>>> This week I was slightly surprised by a behaviour that I've not
>>> considered before. I've long used
>>>
>>> for i, x in enumerate(seq):
>>> # do stuff
>>>
>>> as a standard looping-with-index construct. In Python for loops don't
>>> create a scope, so the loop variables are available afterward. I've
>>> sometimes used this to print or return a record count e.g.
>>>
>>> for i, x in enumerate(seq):
>>> # do stuff
>>> print 'Processed %i records' % i+1
>>>
>>> However as I found out, if seq is empty then i and x are never created.

>>
>> This has nothing to do with enumerate. It applies to for loops in
>> general: the loop variable is not initialised if the loop never runs.
>> What value should it take? Zero? Minus one? The empty string? None?
>> Whatever answer Python choose would be almost always wrong, so it refuses
>> to guess.
>>
>>
>>> The above code will raise NameError. So if a record count is needed, and
>>> the loop is not guaranteed to execute the following seems more correct:
>>>
>>> i = 0
>>> for x in seq:
>>> # do stuff
>>> i += 1
>>> print 'Processed %i records' % i

>>
>> What fixes the problem is not avoiding enumerate, or performing the
>> increments in slow Python instead of fast C, but that you initialise the
>> loop variable you care about before the loop in case it doesn't run.
>>
>> i = 0
>> for i,x in enumerate(seq):
>> # do stuff
>>
>> is all you need: the addition of one extra line, to initialise the loop
>> variable i (and, if you need it, x) before hand.

>
> Actually,
>
> i = -1
>
> or his reporting will be wrong.


Yes, either

i = -1
for i, x in enumerate(seq):
...
print "%d records" % (i+1)

or

i = 0
for i, x in enumerate(seq, 1):
...
print "%d records" % i

 
Reply With Quote
 
Rick Johnson
Guest
Posts: n/a
 
      02-24-2012
On Feb 23, 6:30*pm, Alex Willmer <(E-Mail Removed)> wrote:
> [...]
> as a standard looping-with-index construct. In Python for loops don't
> create a scope, so the loop variables are available afterward. I've
> sometimes used this to print or return a record count e.g.
>
> for i, x in enumerate(seq):
> * *# do stuff
> print 'Processed %i records' % i+1


You could employ the "else clause" of "for loops" to your advantage;
(psst: which coincidentally are working pro-bono in this down
economy!)

>>> for x in []:

.... print x
.... else:
.... print 'Empty Iterable'
Empty Iterable

>>> for i,o in enumerate([]):

.... print i, o
.... else:
.... print 'Empty Iterable'
Empty Iterable

 
Reply With Quote
 
Peter Otten
Guest
Posts: n/a
 
      02-24-2012
Rick Johnson wrote:

> On Feb 23, 6:30 pm, Alex Willmer <(E-Mail Removed)> wrote:
>> [...]
>> as a standard looping-with-index construct. In Python for loops don't
>> create a scope, so the loop variables are available afterward. I've
>> sometimes used this to print or return a record count e.g.
>>
>> for i, x in enumerate(seq):
>> # do stuff
>> print 'Processed %i records' % i+1

>
> You could employ the "else clause" of "for loops" to your advantage;


>>>> for x in []:

> ... print x
> ... else:
> ... print 'Empty Iterable'
> Empty Iterable
>
>>>> for i,o in enumerate([]):

> ... print i, o
> ... else:
> ... print 'Empty Iterable'
> Empty Iterable


No:


>>> for i in []:

.... pass
.... else:
.... print "else"
....
else
>>> for i in [42]:

.... pass
.... else:
.... print "else"
....
else
>>> for i in [42]:

.... break
.... else:
.... print "else"
....
>>>


The code in the else suite executes only when the for loop is left via
break. A non-empty iterable is required but not sufficient.
 
Reply With Quote
 
Peter Otten
Guest
Posts: n/a
 
      02-24-2012
Peter Otten wrote:

> The code in the else suite executes only when the for loop is left via
> break.


Oops, the following statement is nonsense:

> A non-empty iterable is required but not sufficient.


Let me try again:

A non-empty iterable is required but not sufficient to *skip* the else-suite
of a for loop.

 
Reply With Quote
 
Steven D'Aprano
Guest
Posts: n/a
 
      02-24-2012
On Fri, 24 Feb 2012 13:44:15 +0100, Peter Otten wrote:

>>>> for i in []:

> ... pass
> ... else:
> ... print "else"
> ...
> else
>>>> for i in [42]:

> ... pass
> ... else:
> ... print "else"
> ...
> else
>>>> for i in [42]:

> ... break
> ... else:
> ... print "else"
> ...
>>>>
>>>>

> The code in the else suite executes only when the for loop is left via
> break. A non-empty iterable is required but not sufficient.


You have a typo there. As your examples show, the code in the else suite
executes only when the for loop is NOT left via break (or return, or an
exception). The else suite executes regardless of whether the iterable is
empty or not.


for...else is a very useful construct, but the name is misleading. It
took me a long time to stop thinking that the else clause executes when
the for loop was empty.

In Python 4000, I think for loops should be spelled:

for name in iterable:
# for block
then:
# only if not exited with break
else:
# only if iterable is empty

and likewise for while loops.

Unfortunately we can't do the same now, due to the backward-incompatible
change in behaviour for "else".



--
Steven
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
#elements of seq A in seq B Neal Becker Python 2 08-21-2009 06:44 AM
Re: #elements of seq A in seq B Jan Kaliszewski Python 4 08-20-2009 11:25 PM
Using Prime Time To Find All The Paths Of A Seq. Cir., Not Only the Critical Ones ilterisderici@hotmail.com VHDL 0 03-16-2006 09:46 PM
seq. waveform csosz33@axelero.hu VHDL 5 08-26-2005 06:28 PM
TCP Seq number 0 Muhammad Atif Sajid Cisco 2 02-11-2004 07:53 AM



Advertisments