Select to view content in your preferred language

"Table is being edited" error in Python script

3670
8
11-04-2010 08:27 AM
NicholasGross
Deactivated User
I wrote a script that is intended to find the highest currently existing feature ID and then increment any features with NULL ID's by 1. To do this, I had to add some temporary fields to a feature class. Now, in my 'finally:' block I'm trying to delete these temporary fields but I keep getting the following error:

<class 'arcgisscripting.ExecuteError'>: ERROR 000496: table is being edited
Failed to execute (DeleteField).

I did make use of search and update cursors, and I assume these are not being deleted properly and causing a file lock. I'm pretty new to Python, so I guess I don't know how to go about deleting them properly. What I've done so far is based off of what I found in the ArcGIS help documentation. Can anyone clear this issue up for me? Thanks!

import arcpy

try:

    MXD = arcpy.mapping.MapDocument("CURRENT")
    
    # Get selection feature class from map document
    FLList = arcpy.mapping.ListLayers(MXD, "*swDischarge*")[0]

    # Get 'select by' feature class from map document
    QSList = arcpy.mapping.ListLayers(MXD, "*Quarter*")[0]

    arcpy.AddMessage("Feature layers obtained.")    

    # Create search cursor and iterate to first (and only) record        
    QSCur = arcpy.SearchCursor(QSList, "", "", "GRIDID")
    QSRow = QSCur.next()
    # Get field value of current record
    QSValue = QSRow.getValue("GRIDID")

    arcpy.AddMessage("Quarter section value obtained.")    

    # Select all features within selected quarter section
    arcpy.SelectLayerByLocation_management(FLList, "INTERSECT", QSList)

    arcpy.AddMessage("Features in quarter section selected.")    

    # Add fields to store calulated ID info.
    arcpy.AddField_management(FLList, "QS", "TEXT", "", "", 4)
    arcpy.AddField_management(FLList, "IDCODE", "TEXT", "", "", 3)
    # Add field to store existing ID number sequence
    arcpy.AddField_management(FLList, "SEQCODEINT", "SHORT", 3)
    # Add field to store ID number converted to text
    arcpy.AddField_management(FLList, "SEQCODETXT", "TEXT", 3)

    arcpy.AddMessage("Temporary fields added.")    

    # Calculate SEQCODEINT field to extract number sequence
    arcpy.CalculateField_management(FLList, "SEQCODEINT", "Right([FACILITYID], 3)", "VB")

    # Create search cursor and iterate to first (and highest) field value
    SeqCur = arcpy.SearchCursor(FLList, "", "", "SEQCODEINT", "SEQCODEINT D")
    SeqRow = SeqCur.next()
    # Get field value of current record
    SeqValue = SeqRow.getValue("SEQCODEINT")

    arcpy.AddMessage("Highest ID value obtained.")    
    
    # Select from current selection all records with NULL facility identifiers
    arcpy.SelectLayerByAttribute_management(FLList, "SUBSET_SELECTION", "FACILITYID IS NULL")

    arcpy.AddMessage("Features with NULL ID values selected.")    
    
    # Create update cursor
    FullIDCurUp = arcpy.UpdateCursor(FLList)
    for FullIDRowUp in FullIDCurUp:
        # Increment ID by 1 each pass through
        SeqValue = SeqValue + 1
        # Convert ID number to text and pad with zero's
        TxtSeqValue = str(SeqValue)
        TxtSeqValueFill = TxtSeqValue.zfill(3)
        # Calculate new fields
        FullIDRowUp.QS = QSValue
        FullIDRowUp.IDCODE = "STO"
        FullIDRowUp.SEQCODEINT = SeqValue
        FullIDRowUp.SEQCODETXT = TxtSeqValueFill
        # Update records with new values
        FullIDCurUp.updateRow(FullIDRowUp)

    arcpy.AddMessage("Temporary field values updated.")        

    # Create update cursor
    IDCurUp = arcpy.UpdateCursor(FLList)
    for IDRowUp in IDCurUp:
        # Combine new field values and update FACILITYID
        IDRowUp.FACILITYID = IDRowUp.QS + IDRowUp.IDCODE + IDRowUp.SEQCODETXT
        IDCurUp.updateRow(IDRowUp)

    arcpy.AddMessage("Facility Identifier Update Successful!")          
    
# Get tool errors
except arcpy.ExecuteError:
    arcpy.AddError(arcpy.GetMessages(2))

# Get all other errors
except Exception as e:
    print e.message
    arcpy.AddError(e.message)

finally:

    # Delete cursors to prevent file lock.
    if QSCur:
        del QSCur
    if QSRow:
        del QSRow
    if QSValue:
        del QSValue
    if SeqCur:
        del SeqCur
    if SeqRow:
        del SeqRow
    if SeqValue:
        del SeqValue
    if FullIDCurUp:
        del FullIDCurUp
    if FullIDRowUp:
        del FullIDRowUp
    if IDCurUp:
        del IDCurUp
    if IDRowUp:
        del IDRowUp

    arcpy.AddMessage("Cursors deleted")

    # Delete temporary fields    
    arcpy.DeleteField_management(FLList, "QS")
    arcpy.DeleteField_management(FLList, "IDCODE")
    arcpy.DeleteField_management(FLList, "SEQCODEINT")
    arcpy.DeleteField_management(FLList, "SEQCODETXT")

    arcpy.AddMessage("Temporary fields deleted.")  
0 Kudos
8 Replies
ChrisSnyder
Honored Contributor
Yes, be absolutely sure that you delete the objects after you use them, otherwise locks will persist.

For example, see the del statement on the last line:

#v9.3 code BTW
searchRows = gp.searchcursor(remsoftIdPolygonFC)
searchRow = searchRows.next()
while searchRow:
    oidValue = searchRow.OBJECTID
    remsoftIdValue = searchRow.REMSOFT_ID
    if oidValue not in objectIdToRemsoftIdDict:
        objectIdToRemsoftIdDict[oidValue] = remsoftIdValue
    if remsoftIdValue not in remsoftIdToObjectIdDict:
        remsoftIdToObjectIdDict[remsoftIdValue] = oidValue
    searchRow = searchRows.next()
del searchRow, searchRows #VERY IMPORTANT!!!
0 Kudos
LoganPugh
Frequent Contributor
In general you want to delete cursor and row objects as soon as they are no longer needed. By putting them in the finally statement they won't be deleted until after an exception has occured, and as you surmised they probably are the cause of the error.
0 Kudos
NicholasGross
Deactivated User
Thanks for the quick replies! Deleting the cursors directly after their use solved the problem.
0 Kudos
NiklasNorrthon
Frequent Contributor
Thanks for the quick replies! Deleting the cursors directly after their use solved the problem.


I don't understand the obsession with "deleting" cursors from the ESRI python team.

The python del statement does not delete the object in question, it just removes the name, and therefore the decreases the reference count. When the reference count for an object reaches zero the object in question gets deleted, but it's not the del-statement that does that. If there are other references to the object it stays alive.

You can just as well say cur = None, cur = 'Go to the moon' or whatever.

In my opinion it is a design mistake not having a "close" method on the cursor objects. If that had been the case you'd say cur.close() and therefore releasing all file locks and other evil things associated with cursors.

Today if you do:
cur = arcpy.SearchCursor('some_fc')
cur2 = cur # This is BAD!
for row in cur:
    # do stuff
    if some_condition:
        break # just to get out before the cursor has reached the end and therefore invalidating it

del cur # cursor is NOT deleted!
cur2.next() # this one still works (unless the cursor has reached the end)

The cursor is still alive, files are locked and you have a difficult to find bug in your code.

If there was a close method on the cursors, it could easliy implement the __enter__ and __exit__ methods, and you could instead write:
with arcpy.SearchCursor('some_fc') as cur
    cur2 = cur # This is not so bad anymore
    for row in cur:
        # do stuff

# here we exit the with block, the __exit__ method on the arcpy cursor object gets called automatically
# which in turn calls cur.close()

# do more stuff

cur2.next() # oops, but this would raise an exception saying that the cursor is already closed
0 Kudos
ChrisSnyder
Honored Contributor
I think it has to do more with deleting/avoiding database locks than actually deleting objects.

For example, this results in an error:
searchRows = gp.searchRows(myFC)
searchRows =  gp.searchRows(myOtherFC)

This doesn't:
searchRows = gp.searchRows(myFC)
searchRows2 =  gp.searchRows(myOtherFC)

Nor does this:
searchRows = gp.searchRows(myFC)
del searchRows
searchRows =  gp.searchRows(myOtherFC)
0 Kudos
NiklasNorrthon
Frequent Contributor
I think it has to do more with deleting/avoiding database locks than actually deleting objects.

For example, this results in an error:
searchRows = gp.searchRows(myFC)
searchRows =  gp.searchRows(myOtherFC)

This doesn't:
searchRows = gp.searchRows(myFC)
searchRows2 =  gp.searchRows(myOtherFC)

Nor does this:
searchRows = gp.searchRows(myFC)
del searchRows
searchRows =  gp.searchRows(myOtherFC)


I assume you mean gp.SearchCursor everywhere where you say gp.searchRows, but not on the left hand side of the assignments.

I don't get your point. I don't see any difference between your first example that you say would result in an error, and the final one that wouldn't.

Python's del statement doesn't do anything magic. It just removes a name from it's internal lookup table, and therefore also decreases the reference count for the object that the name was referring to.

The following two examples are totally equivalent:
# With explicit del
my_name = 'Niklas'
del my_name
my_name = 'Niklas Norrthon'

# Without explicit del
my_name = 'Niklas'
my_name = 'Niklas Norrthon'

What I'm saying is that instead of deleting a name bound to a cursor object, you can just as well bind that it to something else (e.g. None).

My main point is that I'm missing a close method on the cursor classes. If there had been one, arcpy cursors could easily have implemented python's context manager interface, and then be used in with-statements, which would more or less eliminate the file locking issues.
0 Kudos
ChrisSnyder
Honored Contributor
Whoops - I guess I messed this one up in a few ways:

1) Yes, I meant searchRows = gp.searchcursor(fc)

2) Instead of searchcursor, I'm referring to update and/or insertcursors, as these cursors put locks on databases.

The ESRI help can explain it better than I can: http://webhelp.esri.com/arcgisdesktop/9.3/index.cfm?id=968&pid=951&topicname=Cursors_and_locking

Basically, the idea is to remove the database locks. In the absence of a .close method, del works mighty fine, and accomplishes the same goal.

Some historical perspective:
The v9.0 - 9.3.1 GP object model diagrams all referenced a never-implemented .reset() cursor method. Back in the old forum, ESRI got quite a bit of flack for not implementing this. It was assumed that .reset() would work the same as .close().
0 Kudos
DerivenC
Deactivated User
niklas.norrthon is dead on and helped me solve my own problem.  del does nothing for you as far as an immediate release.  I've seen several threads where someone will say "it releases when I run it here but not in there" or "it worked once but then fails the second time" or etcetera.

My code is absolutely deleting the "object" variable names, even in except:'s.  But the sr.lock files remain.  Oh and I'm using ArcGIS 10 SP2.

So after reading niklas.norrthon's response, it hit me .. force garbage collection...

import gc # import this at the top

# .. your code here

gc.collect() # right after your del entries
time.sleep(1) # just to be sure, I like to sleep a second


Problem solved.
0 Kudos