Data access 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 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, {datum_transformation}, {explicit})

Inserts rows

arcpy.da.SearchCursor(in_table, field_names, {where_clause}, {spatial_reference}, {explode_to_points}, {sql_clause}, {datum_transformation}, {spatial_filter}, {spatial_relationship}, {search_order})

Read-only access

arcpy.da.UpdateCursor(in_table, field_names, {where_clause}, {spatial_reference}, {explode_to_points}, {sql_clause}, {datum_transformation}, {explicit}, {spatial_filter}, {spatial_relationship}, {search_order})

Updates or deletes rows

Data access cursor functions (arcpy.da)
Legacy:

The data access module (arcpy.da) was added in ArcGIS 10.1. The original cursors are still supported; 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 InsertCursor, SearchCursor, and UpdateCursor topics.

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)
del cursor

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 typeMethod

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
with arcpy.da.InsertCursor("c:/base/data.gdb/roads_lut", 
                           ["roadID", "distance"]) as cursor:

    # 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 expect to use will improve the overall efficiency of the cursor.

Short and long field values are returned as integers. Float and double field values are returned as floats. Text field types are returned as strings. More information on additional field types is provided below.

Object identifier fields

When you create a table in ArcGIS, a unique, nonnullable integer field is added to the table to act as an object identifier (object ID). The object ID field is maintained by ArcGIS and guarantees a unique ID for each row in the table.

While all tables include an object ID field, the name of the field depends on the data type. The OBJECTID@ token can also be used as a shortcut in place of a table's object ID field name.

Geometry fields

In ArcGIS, the geometry data type indicates the type of geometry—point, line, polygon, multipoint, or multipatch—that the table stores. The geometry field stored is typically, but not always, named Shape.

Tokens can also be used as shortcuts in place of the geometry field name. 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 name.

Print the x,y coordinates of a point feature class using a search cursor.

import arcpy

infc = "c:/data/fgdb.gdb/fc"

# Enter a for loop for each feature
with arcpy.da.SearchCursor(infc, ['OID@', 'SHAPE@']) as cursor:
    for row in cursor:
        # Print the current point's object ID and x,y coordinates
        print(f'Feature {row[0]}: {row[1][0].X}, {row[1][0].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.

To learn more about accessing geometry in cursors, see Read geometries and Write geometries.

Global identifier fields

Global identifier fields (Global ID and GUID) store registry-style strings consisting of 36 characters enclosed in curly brackets. These strings uniquely identify a feature or table row in a geodatabase and across geodatabases.

In a cursor, GUID fields can accept string and universally unique identifier (UUID) values. When reading Global ID and GUID fields, a string is returned.

For a Global ID field, the GLOBALID@ token can be used as a shortcut in place of the field name.

Add a record with a GUID field using a UUID value.

import arcpy
import uuid

with arcpy.da.InsertCursor("c:/data/fgdb.gdb/fc", ['guid_field']) as cursor:
    id = uuid.uuid1()
    cursor.insertRow([id])

Add a record with a GUID field using a string.

import arcpy

with arcpy.da.InsertCursor("c:/data/fgdb.gdb/fc", ['guid_field']) as cursor:
    id = '0345e170-2614-11eb-812d-f8b156b0d357'
    cursor.insertRow([id])

Search for records using the Global ID token.

import arcpy

with arcpy.da.SearchCursor("c:/data/fgdb.gdb/fc", ["GLOBALID@"], where_clause="OWNER = 'John Doe'") as cursor:
    for row in cursor:
        print(row[0])

Date fields

Date fields can store dates, times, dates and times, and offsets.

In a cursor, a Date field can accept a datetime.datetime object or a string value. When reading date fields, a datetime.date object is returned.

Add records with a Date field using a datetime object.

import arcpy
from datetime import datetime

with arcpy.da.InsertCursor("c:/data/fgdb.gdb/fc", ['date_field']) as cursor:
    # Datetime object created using constructor arguments
    date_value = datetime(2020, 7, 1, 12, 30)
    cursor.insertRow([date_value])

    # Datetime object created using fromisoformat method
    date_value2 = datetime.fromisoformat("2020-07-01 12:30:00")
    cursor.insertRow([date_value2])

Add records with a Date field using ISO 8601 convention and month, day, year, time convention.

import arcpy

dates = ["2020-07-01 12:30:45",  # ISO 8601 convention
         "July 1 2020 12:30:45", # Month day year time convention
]

with arcpy.da.InsertCursor("c:/data/fgdb.gdb/fc", ['date_field']) as cursor:
    for i in dates:
        cursor.insertRow([i])

Date fields with high precision will store data down to the millisecond (1/1000 of a second) instead of to the second (1/60 of a minute). To learn more about high-precision date fields, see the Migrate Date Field To High Precision tool documentation.

Add dates with millisecond data into a high-precision Date field.

Note:

All values in this example are showing a microsecond value (123456), but a high-precision date field will round these to milliseconds (123).

impor arcpy
from datetime import datetime


dates = [datetime(2023, 8, 18, 17, 41, 13, 123456),  # datetime object
         datetime.fromisoformat("2023-08-18T17:42:15.123456"),  # datetime object from ISO 8601 string
         "2023-08-18T17:42:15.123456",  # ISO 8601 string
]

with arcpy.da.InsertCursor("c:/data/fgdb.gdb/fc", ['date_field']) as cursor:
    for i in dates:
        cursor.insertRow([i])

Date Only fields

A Date Only field can store a date.

In a cursor, a Date Only field can accept a datetime.date object or a string value. Date Only fields will also accept datetime.datetime objects, but will discard the time portion of the object. When reading a Date Only field, a datetime.date object is returned.

Add a record with a Date Only field using a date object.

import arcpy
from datetime import date

with arcpy.da.InsertCursor("c:/data/fgdb/gdb/fc", ['date_only']) as cursor:
    date_value = date(2000, 5, 26)
    cursor.insertRow([date_value])

Add a record with a Date Only field using a string value.

import arcpy

with arcpy.da.InsertCursor("c:/data/fgdb/gdb/fc", ['date_only']) as cursor:
    date_value = "November 1 2015"
    cursor.insertRow([date_value])

Time Only fields

A Time Only field can store a time.

In a cursor, a Time Only field can accept a datetime.time object or a string value. Time Only fields will also accept datetime.datetime objects, but will discard the date portion of the object. When reading a Time Only field, a datetime.time object is returned.

Add a record with a Time Only field using a datetime.datetime object.

import arcpy
from datetime import time

with arcpy.da.InsertCursor("c:/data/fgdb/gdb/fc", ['time_only']) as cursor:
    date_value = time(13, 34)
    cursor.insertRow([date_value])

Add a record with a Time Only field using a string value.

import arcpy

with arcpy.da.InsertCursor("c:/data/fgdb/gdb/fc", ['time_only']) as cursor:
    date_value = "16:53:45"
    cursor.insertRow([date_value])

Timestamp Offset fields

A Timestamp Offset field can store a date with an offset.

In a cursor, a Timestamp Offset field can accept a datetime.datetime object. If the datetime.datetime object has no time zone information, the time zone will be set to UTC by default. When reading a Timestamp Offset field, a datetime.datetime object is returned.

Add a record containing a Timestamp Offset field with the timezone as UTC.

import arcpy
from datetime import datetime

with arcpy.da.InsertCursor("c:/data/fgdb/gdb/fc", ['timestamp_offset']) as cursor:
    date_value = datetime(2004, 12, 19, 16, 49, tzinfo=datetime.timezone.utc)
    cursor.insertRow([date_value])

Add a record containing a Timestamp Offset field with a custom offset.

import arcpy
import datetime

tzinfo = datetime.timezone(-1 * datetime.timedelta(hours=4, minutes=15))

with arcpy.da.InsertCursor(fc, ['TIMESTAMPOFFSET']) as cursor:
    date_value = datetime.datetime(2004, 12, 19, 16, 49, tzinfo=tzinfo)
    cursor.insertRow([date_value])

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 a cursor, BLOB fields can accept strings, bytes objects, and memoryview objects. When reading BLOB fields, a memoryview object is returned.

Add a record with a BLOB field using a .png file.

import arcpy

data = open("c:/images/image1.png", "rb").read()
with arcpy.da.InsertCursor("c:/data/fgdb.gdb/fc", ['imageblob']) as cursor:
    cursor.insertRow([data])

Read a BLOB field as a memoryview object and write to a .png file.

import arcpy

with arcpy.da.SearchCursor("c:/data/fgdb.gdb/fc", ["imageblob"]) as cursor:
    memview = cursor.next()[0]
    open("c:/images/image1_copy.png", "wb").write(memview.tobytes())

Raster fields

A raster field can store raster data in or alongside the geodatabase.

In a search cursor, values from a raster field are read as a Raster object. Update and insert cursor do not support raster fields.

Read a raster field and write to a .tif file.

import arcpy

with arcpy.da.SearchCursor("c:/data/fgdb.gdb/fc", ['imag']) as cursor:
    raster = cursor.next()[0]
    raster.save("c:/images/image.tiff")

Big Integer fields

A Big Integer field can store an integer between -2 53 and 253-1

Add a record with a Big Integer field using an integer above 231.

import arcpy

with arcpy.da.InsertCursor("c:/data/fgdb.gdb/fc", ["Population"]) as cursor:
    cursor.insertRow(7654321000)

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.

Cursors support with statements to reset iteration and aid in removal of locks. However, using a del statement to delete the object or wrapping the cursor in a function to have the cursor object go out of scope should be considered to guard against all locking cases.

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 exists.