1

I am somewhat familiar with using the python language in the Calculate Field tool in ArcGIS Pro, but was wondering how this is done in a stand alone Python script with a raster .img (Combine20012004FltPths.img) file.

I am trying to reclassify a new field I have created using integer values from another field. However, when I try to run the code in IDLE, I get a "SyntaxError: invalid syntax".

Here is my python script for the Code Block and the arcpy management Calculate Field tool:

# Code Block for Calculate Field
codeBlock = def reclass(FlightPat1):
                if (FlightPat1 == 0):
                    return "Unclassified"
                elif (FlightPat1 == 11):
                    return "Open Water"
                elif (FlightPat1 == 12):
                    return "Perennial Snow/Ice"
                elif (FlightPat1 == 21):
                    return "Developed, Open Space"
                elif (FlightPat1 == 22):
                    return "Developed, Low Intensity"
                elif (FlightPat1 == 23):
                    return "Developed, Medium Intensity"
                elif (FlightPat1 == 24):
                    return "Developed, High Intensity"
                elif (FlightPat1 == 31):
                    return "Barren Land"
                elif (FlightPat1 == 41):
                    return "Deciduous Forest"
                elif (FlightPat1 == 42):
                    return "Evergreen Forest"
                elif (FlightPat1 == 43):
                    return "Mixed Forest"
                elif (FlightPat1 == 52):
                    return "Shrub/Scrub"
                elif (FlightPat1 == 71):
                    return "Herbaceuous"
                elif (FlightPat1 == 81):
                    return "Hay/Pasture"
                elif (FlightPat1 == 82):
                    return "Cultivated Crops"
                elif (FlightPat1 == 90):
                    return "Woody Wetlands"
                elif (FlightPat1 == 95):
                    return "Emergent Herbaceuous Wetlands"


#Reclassify field based off another field
arcpy.CalculateField_management("Combine20012004FltPths.img", "NLCD01Clss",
                                "Reclass(!FlightPat1!)", expression_type = "PYTHON3",
                                code_block = codeBlock, field_type = "TEXT")

Would I need to convert the attribute table of my .img (Combine20012004FltPths.img) file into a dBASE or geodatabase table or some other table format, then run the Code Block and Calculate Field tool for it to work?

3
  • Start and end the code block with """ (3 double quotes) this instructs python that all of the text is part of a string including the newlines. Note your function returns nothing if the case isn't found, that's sloppy and calculate field may not like that, include else: return None or else: return '' to return something rather than reaching the end of the def and falling off the end. Commented Sep 4, 2020 at 2:46
  • 2
    Given that you use return inside each elif, they could all just be if instead. Personally, I'd use a dictionary lookup, so your function would be return lookup[FlightPat1] if FlightPat1 in lookup else None
    – Vince
    Commented Sep 4, 2020 at 4:58
  • I like the dictionary method @Vince, I've used that a couple of times myself, either hard coded or built on-the-fly from a lookup table, but I think the real gem is in your return statement, I've not seen it done that way before but now that I see it that way makes sense, sort of like a list comprehension, that looks like a real space saver. Commented Sep 4, 2020 at 5:56

1 Answer 1

4

The key to providing a code block to CalculateField is to understand that a separate Python parser is used inside the utility. The code_block must therefore be a string containing viable code. You can accomplish this by using triple-quotes around the text, so that Python handles line continuation correctly:

# Code Block for Calculate Field
codeBlock = """
def reclass(flightPat1):   # Note lowercase variable name -- upcase is for Classes
   {rest of function}
"""

But that's only the first iteration of improvement. There are two basic approaches to coding a switchyard function. The first uses a cascade of conditional tests, and has two major forms: With and without early exit. First without:

# Code Block for Calculate Field
codeBlock = """
def reclass(flightPat1):
    result = None        #! Initialize so that it always returns a default value
    if (flightPat1 == 0):
        result = "Unclassified"
    elif (flightPat1 == 11):
        result = "Open Water"
    ...
    return result
"""

The early exit form should be familiar, but given that return is used, no else is needed:

# Code Block for Calculate Field
codeBlock = """
def reclass(flightPat1):
    if (flightPat1 == 0):
        return "Unclassified"
    if (flightPat1 == 11):
        return "Open Water"
    ...
    return None    #! Suffices for final else
"""

The second approach leverages the dictionary datatype. It is faster, and more pythonic:

# Code Block for Calculate Field
codeBlock = """
lookup = {
     0 : "Unclassified",
    11 : "Open Water",
    ...
}
def reclass(flightPat1):
    return lookup[flightPat1] if flightPat1 in lookup else None
"""

The sexy part about dictionary use is that you can combine this with the fact that you're just compiling a string here, so you can assemble it dynamically (in this case, from a file geodatabase table):

# Code Block for Calculate Field
lookupSrc = r"C:\Temp\gis_se.gdb\answer_lookup"
codeBlock = """
def reclass(flightPat1):
    lookup = {
@TERMS
    }
    return lookup[flightPat1] if flightPat1 in lookup else None
""".replace('@TERMS',',\n'.join(
    ["{:10d} : '{:s}'".format(rec[0],rec[1].replace("'","\\'")) 
        for rec in arcpy.da.SearchCursor(lookupSrc,['ival','sval'])]))

Okay, so yeah, that's a bit hot-n-heavy, but how many times do you get to do a list comprehension on a cursor, a format, and two replacements in one extended line? And it does work:

>>> print(codeBlock)

def reclass(flightPat1):
    lookup = {
         0 : 'Unclassified',
        11 : 'Open Water',
        12 : 'Perennial Snow/Ice',
        21 : 'Developed, Open Space',
        22 : 'Developed, Low Intensity',
        23 : 'Developed, Medium Intensity',
        24 : 'Developed, High Intensity',
        31 : 'Barren Land',
        41 : 'Deciduous Forest',
        42 : 'Evergreen Forest',
        43 : 'Mixed Forest',
        52 : 'Shrub/Scrub',
        71 : 'Herbaceuous',
        81 : 'Hay/Pasture',
        82 : 'Cultivated Crops',
        90 : 'Woody Wetlands',
        94 : 'Apostrophe\'s Example',
        95 : 'Emergent Herbaceuous Wetlands'
    }
    return lookup[flightPat1] if flightPat1 in lookup else None

The obtuse part could be de-inlined to self-document:

# Code Block for Calculate Field
lookupSrc = r"C:\Temp\gis_se.gdb\answer_lookup"
codeFormat = """
def reclass(flightPat1):
    lookup = {
@TERMS
    }
    return lookup[flightPat1] if flightPat1 in lookup else None
"""
termList = []
with arcpy.da.SearchCursor(lookupSrc,['ival','sval']) as cursor:
    for row in cursor:
        ival = row[0]
        sval = row[1].replace("'","\\'")    #! Handle case where sval contains apostrophe
        term = "{:10d} : '{:s}'".format(ival,sval)
        termList.append(term)

termString = ',\n'.join(termList)    #! str.join() adds text between list elements
codeBlock = codeFormat.replace('@TERMS',termString) 
                                       
2
  • Thank you for all those different approaches you shared. I am not getting a syntax error anymore when I run the codeBlock with the 3 quotation marks before and after. However, I am now getting a: Traceback (most recent call last): File "<pyshell#47>", line 3, in <module> codeBlock) File "C:\ArcGIS\...\management.py", line 3647, in CalculateField raise e ExecuteError: Failed to execute. Parameters are not valid. ERROR 000800: The value is not a member of VB | PYTHON | PYTHON_9.3. Failed to execute (CalculateField). when I run the arcpy.CalculateField_management() tool.
    – NamuDude
    Commented Sep 4, 2020 at 17:24
  • I got it all to work now! I had to replace expression_type = "PYTHON3" with expression_type = "PYTHON_9.3" in the arcpy.CalculateField_management() tool. Thank you again.
    – NamuDude
    Commented Sep 4, 2020 at 17:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.