Select to view content in your preferred language

Best Way to Pass a Function into arcpy.CalculateField_management?

5903
5
Jump to solution
12-03-2015 01:54 PM
MikeMacRae1
New Contributor II

I have a function that I use in a script in a couple different ways. One of the ways I would like to use it, is in the codeblock parameter of:

arcpy.CalculateField_management

The function is here and basically does some math and returns a value:

def getPercentOverlapValueOfAOI(area_of_overlap, extentarea):
    if round(area_of_overlap/extentarea * 100, 1) < 0.1:
        AOI_Percent_Value = str("<" +  "0.0")
    else:
        AOI_Percent_Value = str(round(area_of_overlap/extentarea * 100,1))
    return AOI_Percent_Value

and then further down in my script, I use the calculate field function:

arcpy.CalculateField_management(outClipFC, "PERCENTAGE_OF_OVERLAP_OF_LAYER", 
                                   "getPercentOverlapValueOfAOI(!AREA_OF_OVERLAP_HA_OF_LAYER!, {0})".format(extentArea), 
                                   "PYTHON", """getPercentOverlapValueOfAOI""")

Consistently, I am receiving the following error

ExecuteError: ERROR 000539: Runtime error

Traceback (most recent call last):

  File "<string>", line 1, in <module>

NameError: name 'getPercentOverlapValueOfAOI' is not defined

Now, I understand that in example #2 in the help​ menu, They use a variable "codeblock" and wrap the function in triple quotes. I believe calculatefield evaluates the code block string and interprets it as a function. The issue I have with that, is that I have a function that I use inside and outsiide of the arcpy.CalculateField_management function. I initially declare it without the triple quotes becuase the native python interpter will obviously not undertand that the triple quoted string is, in fact, a python function.

Now, I've tried a number of things to get this to work. For example, I assign a variable to the triple quote wrapped function:

codeblock = """def getPercentOverlapValueOfAOI(area_of_overlap):
               if round(area_of_overlap/extentArea * 100, 1) < 0.1:
                   AOI_Percent_Value = str("<" +  "0.0")
               else:
                   AOI_Percent_Value = str(round(area_of_overlap/extentArea * 100,1))
               return AOI_Percent_Value"""

and then pass the codeblock variable into the caluclate field function:

arcpy.CalculateField_management(outClipFC, "PERCENTAGE_OF_OVERLAP_OF_LAYER", 
                                "getPercentOverlapValueOfAOI(!AREA_OF_OVERLAP_HA_OF_LAYER!, {0})".format(extentArea), 
                                "PYTHON", codeblock)

Now, that seems to work, but it completely defeats the purpose of creating a function. One of the primary purposes of a function is to make the code reusable. It seems like I have to define my variable twice. Once at the beginning of my script and then again so that I can get it wrapped in triple quotes and then pass it to a variable that in turn, gets passed into the caluclate field function.

I've also tried to pass the function name into the expression via .format with the same error popping up ( I think I was trying to get creative and silly with this one):

arcpy.CalculateField_management(outClipFC, "PERCENTAGE_OF_OVERLAP_OF_LAYER", 
                                "{0}(!AREA_OF_OVERLAP_HA_OF_LAYER!, {1})".format(getPercentOverlapValueOfAOI, extentArea), 
                                "PYTHON", """getPercentOverlapValueOfAOI""")

My question is, what is the best way, to take a custom built function and pass it into the calculate field function as a piece of code block and then have the expression parameter recognize it.

0 Kudos
1 Solution

Accepted Solutions
MikeMacRae1
New Contributor II

So, I posted this question after working on the solution for a couple days and as soon as I posted, I found a possible solution. Thought I'd share. I did a quick google search on how to print out a python function definition. It took to to this page:

http://stackoverflow.com/questions/427453/how-can-i-get-the-source-code-of-a-python-function

A few of the answers suggestion to use a module called 'inspect' which evaluates the contents of classes, methods, functions, etc. Python Inspect Help. One of the inspect functions allows you to find the source code of a function

import inspect

inspect.getsource(myfunction)

so, I tried this (rememebering to pass a string (str) conversion of the inspection into the codeblock parameter):

arcpy.CalculateField_management(outClipFC, "PERCENTAGE_OF_OVERLAP_OF_LAYER", 
                                "getPercentOverlapValueOfAOI(!AREA_OF_OVERLAP_HA_OF_LAYER!, {0})".format(extentArea), 
                                "PYTHON", str(inspect.getsource(getPercentOverlapValueOfAOI)))

And it works. Hopefully this is helpfully to others.

View solution in original post

5 Replies
DarrenWiens2
MVP Honored Contributor

I'll give you the trick answer of "the best way to use your function is not to use the field calculator".

You can get the same result by calling your function as it is from within an UpdateCursor.

There may be a way to format your function call within a formatted string in the field calculator expression, but it seems unnecessarily complicated (as you're finding).

untested:

with arcpy.da.UpdateCursor(outClipFC, ["PERCENTAGE_OF_OVERLAP_OF_LAYER","AREA_OF_OVERLAP_HA_OF_LAYER"]) as cursor:
   for row in cursor:
       row[0] = getPercentOverlapValueOfAOI(row[1],extentArea)
       cursor.updateRow(row)
MikeMacRae1
New Contributor II

Hey Darren, thanks for the tip. I did come across a few suggestions to use UpdateCursor. This exposes my stubborness. I'm convinced to find a way, only because by using UpdateCUrsor, it would mean I'd have to change a little bit of my other code. If this doesn't work out, so be it. I'll use the Cursor

0 Kudos
DanPatterson_Retired
MVP Emeritus

You can import a function from within the script itself.  This can be useful for timing purposes for example, where the timeit.timeit variables need to be strings as in your case with the field calculator.  I wonder if that might work in your case.  I can't try since I am on an iThingy

import numpy as np
import timeit

def X_bench(num):
    """make X coordinates as a benchmark for making all the other values"""
    Xs = np.arange(10,num+10)
    return Xs

setup = "from __main__ import X_bench"
t = timeit.timeit("X_bench(5)",setup=setup,number=int(3))
print(t)
MikeMacRae1
New Contributor II

So, I posted this question after working on the solution for a couple days and as soon as I posted, I found a possible solution. Thought I'd share. I did a quick google search on how to print out a python function definition. It took to to this page:

http://stackoverflow.com/questions/427453/how-can-i-get-the-source-code-of-a-python-function

A few of the answers suggestion to use a module called 'inspect' which evaluates the contents of classes, methods, functions, etc. Python Inspect Help. One of the inspect functions allows you to find the source code of a function

import inspect

inspect.getsource(myfunction)

so, I tried this (rememebering to pass a string (str) conversion of the inspection into the codeblock parameter):

arcpy.CalculateField_management(outClipFC, "PERCENTAGE_OF_OVERLAP_OF_LAYER", 
                                "getPercentOverlapValueOfAOI(!AREA_OF_OVERLAP_HA_OF_LAYER!, {0})".format(extentArea), 
                                "PYTHON", str(inspect.getsource(getPercentOverlapValueOfAOI)))

And it works. Hopefully this is helpfully to others.

RebeccaStrauch__GISP
MVP Emeritus

This thread was helpful in helping me get the field calculator to work with a function.  I am using an update cursor later in my script for something else, but in this case, the field calculator was a better option.  Thanks to some of the blog post by Dan Patterson​ and some other threads, I was able to modify and simplify the results I needed from the my function...just need the orientation/angle of the line segment (it's a straight line).  I could never get the function call to work directly. even with the "import inspect", so I just calc'd it once manually, copied the snippet, then pulled the expression and code_block arguments out....just to make it cleaner...and was able to get it to work.  just sharing as another option.

fishnetFC =  r"C:\Prep.gdb\FlatTrans"
ptFields = [["X1", "!SHAPE.firstPoint.X!"], ["Y1", "!SHAPE.firstPoint.Y!"], ["X2", "!SHAPE.lastPoint.X!"], ["Y2", "!SHAPE.lastPoint.Y!"], ["angle", "999" ]]

for field in ptFields:
    print("adding field {0}...".format(field[0]))
    arcpy.AddField_management(fishnetFC, field[0], "DOUBLE", "", "", "", "", "NULLABLE", "NON_REQUIRED", "")
    print("  calcing field {0} to be {1}".format(field[0], field[1]))
    arcpy.CalculateField_management(fishnetFC, field[0], field[1], "PYTHON_9.3" )
    if field[0] == "angle":
        expression="calcOrientation( !X1!, !Y1!, !X2!, !Y2!)"

        code_block="def calcOrientation(x1, y1, x2, y2):  \n    radian = math.atan2((y2 - y1),(x2 - x1))  \n    angle = math.degrees(radian)  \n    return angle  \n"
        print("  recalculating {0} to correct value".format(field[0]))
        arcpy.CalculateField_management(fishnetFC, field[0], expression, "PYTHON_9.3", code_block)

And just for clarity, my function in a more readable format:

def calcOrientation(x1, y1, x2, y2):  
    radian = math.atan2((y2 - y1),(x2 - x1))  
    angle = math.degrees(radian)  
    return angle  
0 Kudos