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, and 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.

CursorExplanation

arcpy.da.InsertCursor(in_table, field_names)

Inserts rows

arcpy.da.SearchCursor(in_table, field_names, {where_clause}, {spatial_reference}, {explode_to_points}, {sql_clause})

Read-only access

arcpy.da.UpdateCursor(in_table, field_names, {where_clause}, {spatial_reference}, {explode_to_points}, {sql_clause})

Updates or deletes rows

Data access cursor functions (arcpy.da)
Legacy:

A new data access module (arcpy.da) was added in ArcGIS 10.1. The previously existing cursors (that are still listed under arcpy) are still functional and valid; however, the new arcpy.da cursors include significantly faster performance. In most cases, the help documentation describes the use of the arcpy.da cursors. For more information about the classic cursor model, see the table below.

CursorExplanation

arcpy.InsertCursor(dataset, {spatial_reference})

Inserts rows

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

Read-only access

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

Updates or deletes rows

Cursor functions (arcpy)
Note:

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

Cursors can only be navigated in a forward direction; they do not support backing up and retrieving rows that have already been retrieved. If a script needs to make multiple passes over the data, the cursor's reset method may be called.

Search or update cursors can be iterated with a for loop. The next row can also be accessed by explicitly using the 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 a StopIteration exception.

import arcpy

cursor = arcpy.da.SearchCursor(fc, ['fieldA', 'fieldB'])
for row in cursor:
    print(row)

Search and update cursors also support with statements.

import arcpy

with arcpy.da.SearchCursor(fc, ['fieldA', 'fieldB']) as cursor:
    for row in cursor:
        print(row)

Each row retrieved from a table is returned as a list of field values. The values will be returned in the same order as provided to the cursor's field_names argument. A cursor's fields property can also be used to confirm the order of field values.

Cursor object

SearchCursor, UpdateCursor, and InsertCursor create a cursor 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 by each cursor type:

Cursor typeMethodEffect on position

arcpy.da.SearchCursor

reset()

Resets the cursor to its starting position

arcpy.da.InsertCursor

insertRow()

Inserts a row into the table

arcpy.da.UpdateCursor

updateRow()

Updates the current row

deleteRow()

Removes the row from the table

reset()

Resets the cursor to its starting position

insertRow

An insert cursor is used to create rows and insert them. Once the cursor has been created, the insertRow method is used to insert a list (or tuple) of values that will make up the new row. Any field in the table that is not included in the cursor will be assigned the field's default value.

import arcpy

# Create insert cursor for table
cursor = arcpy.da.InsertCursor("c:/base/data.gdb/roads_lut", 
                               ["roadID", "distance"])

# Create 25 new rows. Set the initial row ID and distance values
for i in range(0,25):
    cursor.insertRow([i, 100])

updateRow

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

import arcpy

# Create update cursor for feature class
with arcpy.da.UpdateCursor("c:/base/data.gdb/roads",
                           ["roadtype", "distance"]) as cursor:
    for row in cursor:
        # Update the values in the distance field by multiplying 
        #   the roadtype by 100. Road type is either 1, 2, 3 or 4.
        row[1] = row[0] * 100
        cursor.updateRow(row)

deleteRow

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

import arcpy

# Create update cursor for feature class
with arcpy.da.UpdateCursor("c:/base/data.gdb/roads", 
                          ["roadtype"]) as cursor:
    # Delete all rows that have a roads type of 4
    for row in cursor:
        if row[0] == 4:
            cursor.deleteRow()

Accessing and setting field values

For each cursor, the fields used are supplied by a list (or tuple) of field names. When a row is returned from the cursor, it is returned as a list of field values that correspond by index position.

In the sample below, state name and population count are accessed by position.

import arcpy

fc = "c:/base/data.gdb/USA/States"

# Use SearchCursor to access state name and the population count
with arcpy.da.SearchCursor(fc, ['STATE_NAME', 'POP2000']) as cursor:
    for row in cursor:
        # Access and print the row values by index position.
        #   state name: row[0]
        #   population: row[1]
        print('{} has a population of {}'.format(row[0], row[1]))
Tip:

While all fields can be accessed using an asterisk (*), it is not generally recommended. The more fields specified, the slower the cursor will perform. Listing only the fields you are expecting to use will improve the overall efficiency of the cursor.

Tokens can also be used as shortcuts in place of field names. All tables include an ObjectID field, which may have many different names depending on the data type. Simple feature classes require a geometry field, typically (but not always) named Shape. The OID@ token can be used to access the ObjectID field, and the SHAPE@ token (which returns a geometry object) can be used to access the geometry field of a feature class without having prior knowledge of the field names.

Search cursor on a multipoint feature class
import arcpy

infc = arcpy.GetParameterAsText(0)

# Enter for loop for each feature
for row in arcpy.da.SearchCursor(infc, ["OID@", "SHAPE@"]):
    # Print the current multipoint's ID
    print("Feature {}:".format(row[0]))

    # For each point in the multipoint feature,
    #  print the x,y coordinates
    for pnt in row[1]:
        print("{}, {}".format(pnt.X, pnt.Y))

Additional geometry tokens can be used to access specific geometry information. Accessing the full geometry is more time-consuming. If you only need specific properties of the geometry, use tokens to provide shortcuts to access geometry properties. For instance, SHAPE@XY will return a tuple of x,y coordinates that represent the feature's centroid.

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, described as follows:

  • A shared lock is applied anytime a table or dataset is accessed. Multiple shared locks can exist for a table, but no exclusive locks are permitted if a shared lock exists. Displaying a feature class and previewing a table are examples of when a shared lock would be applied.
  • Exclusive locks are applied when changes are made to a table or feature class. Editing and saving a feature class in a map, changing a table's schema, or using an insert cursor on a feature class in a Python IDE are examples of when an exclusive lock is applied by ArcGIS.

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 function fails 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.

In Python, the lock persists until the cursor is released. Otherwise, all other applications or scripts could be unnecessarily prevented from accessing a dataset. A cursor can released by one of the following:

  • Include the cursor inside a with statement, which will guarantee the release of locks regardless of whether or not the cursor is successfully completed.
  • Call reset on the cursor.
  • The cursor is successfully completed.
  • Explicitly delete the cursor using the Python del statement.

An edit session 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.

Cursors and BLOB fields

A binary large object (BLOB) is data stored as a long sequence of binary numbers. ArcGIS stores annotation and dimensions as BLOBs, and items such as images, multimedia, or bits of code can be stored in this type of field. You can use a cursor to load or view the contents of a BLOB field.

In Python, BLOB fields can accept strings, bytearray, and memoryviews. When reading BLOB fields, a memoryview object is returned.

import arcpy
data = open("c:/images/image1.png", "rb").read()
ic = arcpy.da.InsertCursor("c:/data/fgdb.gdb/fc", ['imageblob'])
ic.insertRow([data])
import arcpy
sc = arcpy.da.SearchCursor("c:/data/fgdb.gdb/fc", ["imageblob"])
memview = sc.next()[0]
open("c:/images/image1_copy.png", "wb").write(memview.tobytes())