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.
Solved! Go to Solution.
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.
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)
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
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)
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.
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