Convert Field Expression to ArcGIS Python Script Tool

I've created the following field calculation that I'd like to convert to a python script tool:

def blocklow(a, b, c, d):
mylist = [a, b, c, d]  # create list from input range variables
if mylist == [0, 0, 0, 0]:
m = 0
else:
m = min(i for i in mylist if i > 0)  # find minimum value greater than zero
if m > 0 and m < 99:
return 10 # value for minimum block number between 1-99
elif m == 0:
return 0
else:
return int(math.floor(m / 100.0)) * 100 # round minimum to lowest hundred

esri_field_calculator_splitter blocklow( !LLO!, !LHI!, !RLO!, !RHI!)

The gist of it is there are four address range fields (integer type) and I'm after the lowest numeric value, rounded down to an even hundred, that isn't zero. It works, although I'm sure there is room for improvement. I'd like to convert this to a script tool. I've done it semi-successfully defining a parameter for the feature class and field to be calculated. However, if add parameters for the input fields of the calculation and then try to reference those in the function, it fails. Also, I'm using the arcpy.CalculateField_management approach but calling a function by as a string kind of sucks. I'm sure there's a more elegant and efficient way.

Here's what the script tool should look like: I'm really a rookie at best.

Update: here's the script I'm working on. I'm still off somewhere (probably everywhere). Any ideas:

import arcpy
arcpy.env.overwriteOutput = True

try:
inCenterline = arcpy.GetParameterAsText(0)
inBlockF = arcpy.GetParameterAsText(1)
inLF = arcpy.GetParameterAsText(2)
inLT = arcpy.GetParameterAsText(3)
inRF = arcpy.GetParameterAsText(4)
inRT = arcpy.GetParameterAsText(5)
expression = "blocklow(inLF, inLT, inRF, inRT)"
codeblock = """import math
def blocklow(a, b, c, d):
mylist = [a, b, c, d]  # create list from input range variables
if mylist == [0, 0, 0, 0]:
m = 0
else:
m = min(i for i in mylist if i > 0)  # find minimum value greater than zero
if m > 0 and m < 99:
return 10 # value for minimum block number between 1-99
elif m == 0:
return 0
else:
return int(math.floor(m / 100.0)) * 100 # round minimum to lowest hundred"""

arcpy.CalculateField_management(inCenterline, inBlockF, "blocklow(inLF, inLT, inRF, inRT)", "PYTHON_9.3", codeblock)

except:
arcpy.AddError("Could not complete the calculation")

UPDATE: Here's the finished script and tool based on the example and direction that Michael provided.

# ---------------------------------------------------------------------------
# Author: Josse Allen: Product Instructor, Spillman Technologies, Inc
# Original creation date: 2015-09-15
# Version: beta
# Revisions:
#   Revision notes:
# Description: this script is used with an ArcGIS script tool.  The tool
# evaluates the from-to/left-right address range values for each row in the
# centerline, finds the lowest value, and rounds DOWN to the LOWEST hundred.
#
# Platform: developed using Python 2.6, ArcGIS Desktop 10.0 - Basic
#
# Input requirements:
#   - Input centerline (containing from-to/left-right address range fields
#   - To/from address range fields for left/right sides (4 total, short or long)
#
# ---------------------------------------------------------------------------

import arcpy
# The output result will be calculated in the input!
arcpy.env.overwriteOutput = True

# Prelogic used to pass through for calculating the low block number

CodeBlockL = """
def blocklow(a, b, c, d):
mylist = [a, b, c, d]  # create list from input range variables
if mylist == [0, 0, 0, 0]:
m = 0
else:
m = min(i for i in mylist if i > 0)  # find minimum value greater than     zero
if m > 0 and m < 99:
return 10 # value for minimum block number between 1-99
elif m == 0:
return 0
else:
return int(math.floor(m / 100.0)) * 100 # round minimum to lowest hundred
""" # Finished Code block

# Prelogic used to pass through for calculating the high block number

CodeBlockH = """
def blockhigh(a, b, c, d):
h = max(a, b, c, d)  # find high value from input range variables
if h == 0:
return 0
else:
return int(math.ceil(h / 100.0)) * 100 - 1 # round to highest hundred less 1
""" # Finished Code block

try:
# Set input parameters
inCenterline = arcpy.GetParameterAsText(0)  # Source layer containing the address range fields
inLF = arcpy.GetParameterAsText(1)  # Script tool parameter for left from address value
inLT = arcpy.GetParameterAsText(2)  # Script tool parameter for left to address value
inRF = arcpy.GetParameterAsText(3)  # Script tool parameter for right from address value
inRT = arcpy.GetParameterAsText(4)  # Script tool parameter for right to address value

# Set variables for fields to be added

field_nameL = "BLOCKLO" # Field name to be created for the low block value
field_nameH = "BLOCKHI" # Field name to be created for the high block value

# create fields for output values
arcpy.AddMessage("Adding fields for block low and high values")
arcpy.AddField_management(inCenterline, field_nameL, "LONG", "", "", "", "", "NULLABLE", "NON_REQUIRED", "")
arcpy.AddField_management(inCenterline, field_nameH, "LONG", "", "", "", "", "NULLABLE", "NON_REQUIRED", "")

# Calculate the block low field
expressionL = "blocklow(!{0}!, !{1}!, !{2}!, !{3}!)".format(inLF, inLT, inRF, inRT)
arcpy.AddMessage("Calculating the low block value...")
arcpy.CalculateField_management(inCenterline, field_nameL, expressionL, "PYTHON_9.3", CodeBlockL)
arcpy.AddMessage("Low block calculation complete")

# Calculate the block high field
expressionH = "blockhigh(!{0}!, !{1}!, !{2}!, !{3}!)".format(inLF, inLT, inRF, inRT)
arcpy.AddMessage("Calculating the high block value...")
arcpy.CalculateField_management(inCenterline, field_nameH, expressionH, "PYTHON_9.3", CodeBlockH)
arcpy.AddMessage("High block calculation complete")

except:
arcpy.AddError("Could not complete the calculation")  • Have a read of this gis.stackexchange.com/questions/93591/… about taking a multiline function and using it in field calculator... as I see it you need surrond the block with """ and it's good to go. Use your existing code as a block and then call it.. – Michael Stimson Sep 15 '15 at 22:19
• @MichaelMiles-Stimson - I gave your suggestion a try - best I could apply it - but I'm still missing something. It's probably a terrible hack at best. I'm trying to post the code but can't figure out how to do that... give me a moment. Appreciate your response. – Hoss Sep 16 '15 at 0:07
• If you want to keep it as Calculate Field, run it normally and then under the Results window, right click the result and copy to Python snippet. – Paul Sep 16 '15 at 0:10
• Try doing it in a model, then when you get that to work export to python script to see how to code/indent/format the advanced syntax. I have used this approach before when the help docs weren't so helpful and I just needed to see how it converted... once you know how to format it properly you shouldn't need to go back to model builder.... ever. I like your approach @Paul - I've not done that before but it looks like a quicker way. – Michael Stimson Sep 16 '15 at 0:11
• Put the exclamation marks in the string using a sting format like "!" + inLT + "!" or "!{0}!".format(inLT) should do it, the exclamation marks are still needed in the string so that the parser knows it's a parameter. – Michael Stimson Sep 16 '15 at 0:57

You were so close at getting it right... As per one of my previous comments the exclamation marks need to be there so:

"blocklow(inLF, inLT, inRF, inRT)"

needs to change to:

"blocklow(!{0}!, !{1}!, !{2}!, !{3}!)".format(inLF, inLT, inRF, inRT)

This is python string manipulation where the values in the format statement get substituted into the string, so let's assume the fields are called A,B,C and D respectively python expands this at runtime to:

>>> inLF = "A"
>>> inLT = "B"
>>> inRF = "C"
>>> inRT = "D"
>>> print "blocklow(!{0}!, !{1}!, !{2}!, !{3}!)".format(inLF, inLT, inRF, inRT)
blocklow(!A!, !B!, !C!, !D!)

which is how CalculateField wants it, the exclamation marks are required in the expression string to tell the parser that they're field names and not a constant value.

Putting that all together into a cohesive script:

CodeBlock = """
def blocklow(a, b, c, d):
mylist = [a, b, c, d]  # create list from input range variables
if mylist == [0, 0, 0, 0]:
m = 0
else:
m = min(i for i in mylist if i > 0)  # find minimum value greater than zero
if m > 0 and m < 99:
return 10 # value for minimum block number between 1-99
elif m == 0:
return 0
else:
return int(math.floor(m / 100.0)) * 100 # round minimum to lowest hundred
""" # Finished Code block

import arcpy
arcpy.env.overwriteOutput = True

try:
inCenterline = arcpy.GetParameterAsText(0)
inBlockF = arcpy.GetParameterAsText(1)
inLF = arcpy.GetParameterAsText(2)
inLT = arcpy.GetParameterAsText(3)
inRF = arcpy.GetParameterAsText(4)
inRT = arcpy.GetParameterAsText(5)
expression = "blocklow(!{0}!, !{1}!, !{2}!, !{3}!)".format(inLF, inLT, inRF, inRT)

arcpy.CalculateField_management(inCenterline, inBlockF, expression, "PYTHON_9.3", CodeBlock)