Accessing data using cursors

A cursor is a data access object that can be used to either iterate over the set of rows in a table or insert new rows into a table. Cursors have three forms: search, insert, or update. Cursors are commonly used to read existing geometries and write new geometries.

Each type of cursor is created by a corresponding ArcPy function (SearchCursor, InsertCursor, or UpdateCursor) on a table, table view, feature class, or feature layer. A search cursor can be used to retrieve rows. An update cursor can be used to positionally update and delete rows, while an insert cursor is used to insert rows into a table or feature class.

Cursor

Explanation

InsertCursor(dataset, {spatial_reference})

Inserts rows

SearchCursor(dataset, {where_clause}, {spatial_reference}, {fields}, {sort_fields})

Read-only access

UpdateCursor(dataset, {where_clause}, {spatial_reference}, {fields}, {sort_fields})

Updates or deletes rows

Cursor functions

NoteNote:

Cursors honor layer/table view definition queries and selections. The cursor object only contains the rows that would be used by any geoprocessing tool during an operation.

All three cursor functions create a cursor object that can be used to access row objects. The methods supported by the row object depend on the type of cursor created.

Cursors can only be navigated in a forward direction; they do not support backing up and retrieving rows that have already been retrieved or making multiple passes over data. If a script needs to make multiple passes over the data, the application must reexecute the cursor function. If both executions are made within the same edit session (or database transaction with the appropriate level of isolation), the application is guaranteed not to see any changes made to the data by other concurrently executing applications.

Search or update cursors are able to be iterated with a For loop or in a While loop using the cursor's next method to return the next row. When using the next method on a cursor to retrieve all rows in a table containing N rows, the script must make N calls to next. A call to next after the last row in the result set has been retrieved returns None, which is a Python data type that acts here as a placeholder.

This example shows a simple cursor operation. It prints out name and value of each string field for each row in a feature class.

import arcpy

fc = "D:/st_johns/roads.shp"

# Create a search cursor 
#
rows = arcpy.SearchCursor(fc) 

# Create a list of string fields
fields = arcpy.ListFields(fc, "", "String")

for row in rows:
    for field in fields:
        if field.type != "Geometry":
            print "%s: Value = %s" % (field.name, row.getValue(field.name))

All row objects retrieved from a table logically contain the same ordered set of fields. In particular, the order of fields in a row of a table is the same as the order of fields returned by the ListFields function. The row will only contain the visible fields of the table used to create the cursor, with each field name being a property of the object.

The cursor object

SearchCursor, UpdateCursor, and InsertCursor functions create a cursor object (sometimes referred to as an enumeration object) that can be used to iterate through the records. The methods of the cursor object created by the various cursor functions vary depending on the type of cursor created.

The following chart shows the methods supported for each cursor type:

Cursor type

Method

Effect on position

Search

next

Retrieves the next row object

Insert

newRow

Creates an empty row object

insertRow

Inserts a row object into the table

next

Retrieves the next row object

Update

updateRow

Updates the current row with a modified row object

deleteRow

Removes the row from the table

next

Retrieves the next row object

insertRow

The insert cursor is used to create new rows and insert them. The basic flow of processing is to use the newRow method on the cursor object in which rows are to be inserted. This new row object contains all the fields found on the table, and you can assign values to the fields on the row before inserting the row using insertRow.

import arcpy

# Create insert cursor for table
#
rows = arcpy.InsertCursor("D:/St_Johns/data.gdb/roads_lut")
x = 1

# Create 25 new rows. Set the initial row ID and distance values
#
while x <= 25:
    row = rows.newRow()
    row.rowid = x
    row.distance = 100
    rows.insertRow(row)
    x = x + 1

updateRow

The updateRow method is used to update the row at the current position of an update cursor. After fetching a row object from the cursor object, you modify the row as needed and call updateRow, passing in the modified row.

import arcpy

# Create update cursor for feature class
#
rows = arcpy.UpdateCursor("D:/St_Johns/data.gdb/roads")

for row in rows:
# Update the field used in buffer so the distance is based on the road
#   type. Road type is either 1, 2, 3, or 4. Distance is in meters.
#
    row.buffer_distance = row.road_type * 100
    rows.updateRow(row)

deleteRow

The deleteRow method is used to delete the row at the current position of an update cursor. After fetching the row object, you call deleteRow on the cursor to delete the row.

import arcpy

# Create update cursor for feature class
#
rows = arcpy.UpdateCursor("D:/St_Johns/data.gdb/roads")

for row in rows:
    # Delete all rows that have a roads type of 4
    if row.road_type == 4:
        rows.deleteRow(row)

getValue and setValue

There are two basic ways to get and set field values on a row:

Method

Explanation

getValue(field_name)

Gets the field value

isNull(field_name)

Is the field value null

setNull(field_name)

Sets the field value to null

setValue(field_name, object)

Sets field value

Row methods

The sample below creates a lookup table for all polygon feature classes in a workspace. The lookup table contains an ID that corresponds to the ObjectID for each feature in the feature class and a distance value that could be used for the Buffer tool.

import arcpy
from arcpy import env
import os

env.overwriteOutput = 1
env.workspace = arcpy.GetParameterAsText(0)

# List the polygon feature classes
#
fcs = arcpy.ListFeatureClasses("*","polygon")

# Loop through the results.
#
for fc in fcs:
   # Get the ObjectID field
   #
   desc = arcpy.Describe(env.workspace + os.sep + fc)
   OIDFieldName = desc.OIDFieldName

   # Create lookup table name
   #
   if fc.endswith(".shp"):
      tab = fc.replace(".", "_lut.")
   else:
      tab = fc + "_lut"

   # Create lookup table and add fields
   #
   arcpy.CreateTable_management(env.workspace, tab)
   arcpy.AddField_management(tab,"id","long")
   arcpy.AddField_management(tab,"dist","long")

   # Open insert cursor on new lookup table
   #
   tabcur = arcpy.InsertCursor(env.workspace + os.sep + tab)

   # Open search cursor on feature class
   #
   featcur = arcpy.SearchCursor(env.workspace + os.sep + fc)

   # Loop through the rows in the feature class
   #
   for f_row in featcur:
       # Create a new row for the lookup table and set its ID to be the
       #  same as the feature OID and set the distance to 100.
       #
       t_row = tabcur.newRow()
       t_row.id = f_row.getValue(OIDFieldName)
       t_row.dist = 100

       # Insert the row into the lookup table and get the next row
       #  from the feature class
       #
       tabcur.insertRow(t_row)

Cursors and locking

Insert and update cursors honor table locks set by ArcGIS applications. Locks prevent multiple processes from changing the same table at the same time. There are two types of locks—shared and exclusive.

Update and insert cursors cannot be created for a table or feature class if an exclusive lock exists for that dataset. The UpdateCursor or InsertCursor functions fail because of an exclusive lock on the dataset. If these functions successfully create a cursor, they apply an exclusive lock on the dataset so that two scripts cannot create an update or insert cursor on the same dataset.

Locks persist until the application or script releases the dataset, either by closing or releasing the cursor object explicitly. In a script, the cursor object should be deleted so the exclusive lock it placed on the dataset is released. Otherwise, all other applications or scripts could be unnecessarily prevented from accessing a dataset. The sample below shows how to open an update cursor and release it. An error handler is used to check if the UpdateCursor function fails because of another exclusive lock on the table.

import arcpy

# Create update cursor for feature class
#
try:
    # The variables row and rows are initially set to None, so that they
    #  can be deleted in the finally block regardless of where (or if)
    #  script fails.
    #
    row, rows = None, None

    rows = arcpy.UpdateCursor("D:/St_Johns/data.mdb/roads")

    # Update the field used in buffer so the distance is based on the 
    #   road type. Road type is either 1, 2, 3 or 4. Distance is in meters.
    #
    for row in rows:
        row.buffer_distance = row.road_type * 100
        rows.updateRow(row)

except:
    if not arcpy.GetMessages() == "":
        arcpy.AddMessage(arcpy.GetMessages(2))

finally:
    # Regardless of whether the script succeeds or not, delete 
    #  the row and cursor
    #
    if row:
        del row
    if rows:
        del rows

An edit session in ArcMap applies a shared lock to data during the edit session. An exclusive lock is applied when edits are saved. A dataset is not editable if an exclusive lock already exists.

When working in a Python editor, such as PythonWin, you may need to clean up object references to remove dataset locks set by cursors. Use the gc (garbage collection) module to control when unused objects are removed and/or explicitly delete references within your script.

Related Topics


12/15/2011