Select to view content in your preferred language

Why does the next() method fail after the last record, using arcpy.da.SearchCursor?

2668
10
Jump to solution
04-30-2014 03:40 PM
StephenLead
Honored Contributor
Using the older arcpy.SearchCursor, the next() method would return Nothing once the last record had been passed.

Using the newer arcpy.da.SearchCursor, I'm finding that the next() method crashes the script if there are no more records. To illustrate:

import arcpy   fc = "C:\Program Files (x86)\ArcGIS\Desktop10.2\ArcGlobeData\continent.shp" fields = ["CONTINENT"] where = "CONTINENT = 'Asia'" cursor = arcpy.da.SearchCursor(fc, fields, where) for i in range(0,5):     row = cursor.next()     print(row)


There is only one record in the cursor, so the first time it iterates the name is printed. But the second iteration throws a StopIteration error.

I believe this is a bug - the cursor should return Nothing, allowing us to handle the situation. At the very least, this behaviour should be documented.

(I know there is an alternative method using for row in cursor: but my point is that the next() method should not throw an error after the last record. Also, in my case I'm looking for situations where there are no records so I can perform an action - this doesn't work when using for row in cursor:).

Thanks,
Steve
Tags (2)
1 Solution

Accepted Solutions
JasonScheirer
Esri Alum
The .next() method raises an error by design: it follows to the letter the Python Iterator Protocol, which requires the .next() method on an object to raise a StopIteration exception when it is exhausted.

I very strongly recommend you refactor your script to read as such instead (using itertools😞

import itertools import arcpy  fc = "C:\Program Files (x86)\ArcGIS\Desktop10.2\ArcGlobeData\continent.shp" fields = ["CONTINENT"] where = "CONTINENT = 'Asia'" with arcpy.da.SearchCursor(fc, fields, where) as cursor:     for row in itertools.islice(cursor, 5):         print row

View solution in original post

0 Kudos
10 Replies
curtvprice
MVP Alum
Using the older arcpy.SearchCursor, the next() method would return Nothing once the last record had been passed.

Using the newer arcpy.da.SearchCursor, I'm finding that the next() method crashes the script if there are no more records.

(I know there is an alternative method using for row in cursor: but my point is that the next() method should not throw an error after the last record. Also, in my case I'm looking for situations where there are no records so I can perform an action - this doesn't work when using for row in cursor:).


I agree with you, Steve - may want to bring that up with support... I'm afraid they will however tell you that the new behavior is more pythonic, since the StopIteration error can easily handled as an expect exception and is more detailed information than just getting None. Since they did change the behavior, it should be documented under the .next() method I think!

That said, I recommending avoiding the old (9.2-style, really) enumeration approach using next() with arcpy.da.cursor. Also, the new suggested use of the "with" construct is much better as it closes the cursor for you when you exit the with block, no matter what happens.  As for handling the no rows situation, I've done that in the code below.

fc = "C:\Program Files (x86)\ArcGIS\Desktop10.2\ArcGlobeData\continent.shp"
fields = ["CONTINENT"]
where = "CONTINENT = 'Asia'"
k = 0
with arcpy.da.SearchCursor(fc, fields, where) as cursor:
    for row in cursor:
        print(row)
        k += 1
if k == 0: 
    print "No records!"
0 Kudos
StephenLead
Honored Contributor
Thanks Curtis. I'm doing something similar to handle it (maybe this is the correct method - but as you say it'd be nice if it was documented).

with arcpy.da.SearchCursor(outFC, fields, whereClause) as sCursor:
  try:
    #See if there are any records returned. If this throws an error, this means
    #there are no records, in which case we need to add one
    sRow = sCursor.next()                                   
  except:
    #Create a new point in the output geodatabase
    pt = (lon, lat)
    iCursor.insertRow([pt, ID])


I should probably check that the error was of type StopIteration for completeness.....
0 Kudos
RichardFairhurst
MVP Alum
When I want to verify a selection was made I use code like this ("parcels" is a layer that may or may not contain a selection):

if int(arcpy.GetCount_management(parcels).getOutput(0)) > 0:
0 Kudos
StephenLead
Honored Contributor
When I want to verify a selection was made I use code like this ("parcels" is a layer that may or may not contain a selection):

if int(arcpy.GetCount_management(parcels).getOutput(0)) > 0:


I guess I'd need to couple this with a Select By Attributes first, in order to ensure my whereClause was being used. But it's a good workaround - thanks.
0 Kudos
RichardFairhurst
MVP Alum
If the actual amount counted made a difference I store the result of the count to a variable and then use it in further tests.  This is particularly useful where a single row selection needs much less processing than a multi-record selection or where excessively large selections are not expected.  For example:

parcelsCount = int(arcpy.GetCount_management(parcels).getOutput(0))
if 0 < parcelsCount < 2:
  # do something
elif 1 < parcelsCount < 1000:
  # do something else
else:
  # do a different thing
0 Kudos
RichardFairhurst
MVP Alum
Since a cursor outputs to either a list or a dictionary I believe you can use the len() operator on the cursor output.  So I think this would work:

fc = "C:\Program Files (x86)\ArcGIS\Desktop10.2\ArcGlobeData\continent.shp"
fields = ["CONTINENT"]
where = "CONTINENT = 'Asia'"
with arcpy.da.SearchCursor(fc, fields, where) as cursor:
j = len(cursor)
if j > 5:
  j = 5
for i in range(0, j):
0 Kudos
StephenLead
Honored Contributor
Since a cursor outputs to either a list or a dictionary I believe you can use the len() operator on the cursor output


Unfortunately I don't think that's correct.

[ATTACH=CONFIG]33500[/ATTACH]

I've updated my original question to say that the fact that cursor.next() crashes after the last record should be reflected in the documentation - it's clear there are lots of workarounds to this "feature".

Thanks for the assistance,
Steve
0 Kudos
RichardFairhurst
MVP Alum
Unfortunately I don't think that's correct.

[ATTACH=CONFIG]33500[/ATTACH]

I've updated my original question to say that the fact that cursor.next() crashes after the last record should be reflected in the documentation - it's clear there are lots of workarounds to this "feature".

Thanks for the assistance,
Steve


It was worth a shot.  I wonder if the old cursor could do this (although I would still recommend using the da cursor)?  And if no geometry was involved the cursor could be read into a dictionary with a standard for loop and then processed much faster and fluidly than using just a cursor.
0 Kudos
JasonScheirer
Esri Alum
The .next() method raises an error by design: it follows to the letter the Python Iterator Protocol, which requires the .next() method on an object to raise a StopIteration exception when it is exhausted.

I very strongly recommend you refactor your script to read as such instead (using itertools😞

import itertools import arcpy  fc = "C:\Program Files (x86)\ArcGIS\Desktop10.2\ArcGlobeData\continent.shp" fields = ["CONTINENT"] where = "CONTINENT = 'Asia'" with arcpy.da.SearchCursor(fc, fields, where) as cursor:     for row in itertools.islice(cursor, 5):         print row
0 Kudos