Why does "for point in part:" work in a standalone ArcPy script, but not in the Field Calculator?

737
5
Jump to solution
02-21-2022 10:00 PM
Bud
by
Notable Contributor

ArcMap 10.7.1 — Oracle 18c SDE.St_Geometry — Polyline FC


I have an ArcPy function that works as expected in a standalone ArcPy script (PyScripter):

def new_shape(geom):
    spatial_reference =  geom.spatialReference
    parts = arcpy.Array()
    for i in range(geom.partCount):
        part = geom.getPart(i)
        points = arcpy.Array()
        for point in part:
            point.M = geom.measureOnLine(point)
            points.append(point)
        parts.append(points)
    return arcpy.Polyline(parts, spatial_reference)

That function works correctly, including the following for loop. It returns a reconstructed shape to the main ArcPy Script.

for point in part:
    point.M = geom.measureOnLine(point)
    points.append(point)

However, when I use that same function in the Field Calculator, the script runs without errors, but it returns an empty shape.

The problem seems to be the for loop: for point in part:. When I debug the code, I can see that the script doesn't enter the for loop.


I was able to work around the issue by using a different style of for loop and the .getObject()  array property. Now, the script enters the for loop and constructs the shape from the points, as expected:

for j in range(part.count):
    point = part.getObject(j)
    point.M = geom.measureOnLine(point)
    points.append(point)

Question:

Why does for point in part: work in a standalone ArcPy script, but not in the Field Calculator?

I would like to understand this better — in case I'm doing something wrong.

0 Kudos
1 Solution

Accepted Solutions
JoshuaBixby
MVP Esteemed Contributor

@Bud, you posted this question on GIS StackExchange ( arcmap - Why does "for point in part:" work in a standalone ArcPy script, but not in the Field Calcu... ) , and I just posted an answer there.  I will put the answer here as well since I suspect a fair number of Esri Community members aren't users of GIS SE.

The difference in behavior is due to the fact that objects within geoprocessing tools are not the same as objects within ArcPy. The pattern of directly iterating over items in a container, for point in part:, is a Python use pattern that is implemented in most ArcPy classes but not in ArcObjects classes being used by geoprocessing tools.

Looking to Esri's A quick tour of ArcPy -- ArcMap | Documentation

ArcPy provides access to geoprocessing tools as well as additional functions, classes, and modules that allow you to create simple or complex workflows. Broadly speaking, ArcPy is organized in tools, functions, classes, and modules.

Technically speaking, geoprocessing tools are functions available from arcpy—that is, they are accessed like any other Python function. However, to avoid confusion, a distinction is always made between tool and nontool functions (such as utility functions like ListFeatureClasses()).

The key is in the second paragraph, i.e., geoprocessing tools are functions available from ArcPy but are not native ArcPy functions. Field Calculator is an example of a geoprocessing tool available through ArcPy as opposed to an ArcPy function that supports Python use patterns. Typically with geoprocessing tools, users either pass basic Python data types (e.g., string) or ArcPy objects that are then converted to geoprocessing objects behind the scenes. Field Calculator is somewhat unique in that users can pass free-form Python code within a Python string for the tool to execute.

In ArcPy, arcobjects are created from geoprocessing code and then extended to add support for core Python functionality and patterns. For ArcPy Array, that includes implementing the Python __iter__ special method that supports the pattern of directly iterating over items in a container.  From 3. Data model — Python 3.10.2 documentation

object.__iter__(self)

This method is called when an iterator is required for a container. This method should return a new iterator object that can iterate over all the objects in the container. For mappings, it should iterate over the keys of the container.

If you look at the ArrayMixin class within the mixins module, you can see the implementation of the __iter__ special method is similar to the Python code that currently works within Field Calculator.

# represenative code since ArcPy modules are copyrighted
def __iter__(self):
    for index in xrange(len(self)):
        yield conversionObject(self.GetObject(index))

You can verify that a geoprocessing array does not implement the __iter__ special method while ArcPy Array does implement it:

>>> import arcpy
>>> 
>>> # Create geoprocessing array
>>> gp_arr = arcpy.gp.CreateObject("Array")
>>> type(gp_arr)
<type 'geoprocessing array object'>
>>> 
>>> # Create ArcPy Array
>>> arcpy_arr = arcpy.Array()
>>> type(arcpy_arr)
<class 'arcpy.arcobjects.arcobjects.Array'>
>>> 
>>> # Check the geoprocessing array for the __iter__ method
>>> gp_arr.__iter__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Array: Get attribute __iter__ does not exist
>>> 
>>> # Check the ArcPy Array for the __iter__ method
>>> arcpy_arr.__iter__
<bound method Array.__iter__ of <Array []>>
>>> 

 

View solution in original post

5 Replies
JoshuaBixby
MVP Esteemed Contributor

@Bud, you posted this question on GIS StackExchange ( arcmap - Why does "for point in part:" work in a standalone ArcPy script, but not in the Field Calcu... ) , and I just posted an answer there.  I will put the answer here as well since I suspect a fair number of Esri Community members aren't users of GIS SE.

The difference in behavior is due to the fact that objects within geoprocessing tools are not the same as objects within ArcPy. The pattern of directly iterating over items in a container, for point in part:, is a Python use pattern that is implemented in most ArcPy classes but not in ArcObjects classes being used by geoprocessing tools.

Looking to Esri's A quick tour of ArcPy -- ArcMap | Documentation

ArcPy provides access to geoprocessing tools as well as additional functions, classes, and modules that allow you to create simple or complex workflows. Broadly speaking, ArcPy is organized in tools, functions, classes, and modules.

Technically speaking, geoprocessing tools are functions available from arcpy—that is, they are accessed like any other Python function. However, to avoid confusion, a distinction is always made between tool and nontool functions (such as utility functions like ListFeatureClasses()).

The key is in the second paragraph, i.e., geoprocessing tools are functions available from ArcPy but are not native ArcPy functions. Field Calculator is an example of a geoprocessing tool available through ArcPy as opposed to an ArcPy function that supports Python use patterns. Typically with geoprocessing tools, users either pass basic Python data types (e.g., string) or ArcPy objects that are then converted to geoprocessing objects behind the scenes. Field Calculator is somewhat unique in that users can pass free-form Python code within a Python string for the tool to execute.

In ArcPy, arcobjects are created from geoprocessing code and then extended to add support for core Python functionality and patterns. For ArcPy Array, that includes implementing the Python __iter__ special method that supports the pattern of directly iterating over items in a container.  From 3. Data model — Python 3.10.2 documentation

object.__iter__(self)

This method is called when an iterator is required for a container. This method should return a new iterator object that can iterate over all the objects in the container. For mappings, it should iterate over the keys of the container.

If you look at the ArrayMixin class within the mixins module, you can see the implementation of the __iter__ special method is similar to the Python code that currently works within Field Calculator.

# represenative code since ArcPy modules are copyrighted
def __iter__(self):
    for index in xrange(len(self)):
        yield conversionObject(self.GetObject(index))

You can verify that a geoprocessing array does not implement the __iter__ special method while ArcPy Array does implement it:

>>> import arcpy
>>> 
>>> # Create geoprocessing array
>>> gp_arr = arcpy.gp.CreateObject("Array")
>>> type(gp_arr)
<type 'geoprocessing array object'>
>>> 
>>> # Create ArcPy Array
>>> arcpy_arr = arcpy.Array()
>>> type(arcpy_arr)
<class 'arcpy.arcobjects.arcobjects.Array'>
>>> 
>>> # Check the geoprocessing array for the __iter__ method
>>> gp_arr.__iter__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Array: Get attribute __iter__ does not exist
>>> 
>>> # Check the ArcPy Array for the __iter__ method
>>> arcpy_arr.__iter__
<bound method Array.__iter__ of <Array []>>
>>> 

 

Bud
by
Notable Contributor

Thanks very much. That really helps.
I posted an ArcGIS Pro Idea about it here: Enhance the geoprocessing array: Directly iterate over items in a container

Feel free to suggest improvements to that idea.

0 Kudos
JoshuaBixby
MVP Esteemed Contributor

If you feel/believe a reply answers your question, please mark it as the solution to let others know it answered the question for you.

0 Kudos
Bud
by
Notable Contributor


I thought this was interesting:

"Luckily, arcpy can be accessed inside field calculator, so it's a simple call to arcpy.Array():"

def plineM(shp):
  for part in arcpy.Array(shp.getPart(0)):
    for p in part:
      return None if p is None else p.X          

plineM(!Shape!)

Script (ArcPy) vs field calculator (Python Parser) behaviour of getPart in ArcGIS for Desktop?