5

The Arcpy documentation seems to suggest that when writing python scripts that utilize/modify variables in arcpy.env, you should wrap everything in a bunch of try/except/(finally) loops with custom error types (which are generally a bad practice, IMO).

In the case here, they don't even guard against errors leaving the extension checked out:

import arcpy
import arcpyproduction

# Check out Production Mapping license
arcpy.CheckOutExtension("Foundation")

# Define map document, data frame, and polygon geometry coordinates
[snip]

# Create polygon geometry
for coordPair in coordList:
    x, y = coordPair.split(";")
    pnt = arcpy.Point(x,y)
    array.add(pnt)
array.add(array.getObject(0))
boundaryPolygon = arcpy.Polygon(array)

[snip]

# Check in extension
arcpy.CheckInExtension("Foundation")

I know that's not a huge risk, but there's got to be a better way, right?

1
5

I like to use context managers. In particular, I have three for

  • arcpy.env.workspace,
  • arcpy.env.overwriteOutput, and
  • arcpy.Check[In|Out]Extension

They are:

class Extension(object):
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        if arcpy.CheckExtension(self.name) == "Available":
            arcpy.CheckOutExtension(self.name)
        else:
            raise ValueError("%s license isn't available" % self.name)

    def __exit__(self, *args):
        arcpy.CheckInExtension(self.name)


class OverwriteState(object):
    def __init__(self, overwrite):
        self.orig_state = arcpy.env.overwriteOutput
        self.new_state = bool(overwrite)

    def __enter__(self, *args, **kwargs):
        arcpy.env.overwriteOutput = self.new_state

    def __exit__(self, *args, **kwargs):
        arcpy.env.overwriteOutput = self.orig_state


class WorkSpace(object):
    def __init__(self, path):
        self.orig_workspace = arcpy.env.workspace
        self.new_workspace = path

    def __enter__(self, *args, **kwargs):
        arcpy.env.workspace = self.new_workspace

    def __exit__(self, *args, **kwargs):
        arcpy.env.workspace = self.orig_workspace

If your version of arcpy is recent enough to be on Python 2.7, you can use them all at the same time like this:

with Extension("3D"), OverwriteState(True), Workspace("c:/GrosMorne"):
    arcpy.HillShade_3d("WesternBrook", "wbrook_hill", 300)
    arcpy.Aspect_3d("WesternBrook", "wbrook_aspect")

If you're stuck on Python 2.6, it's a little uglier:

with Extension("3D"):
    with OverwriteState(True):
        with: Workspace("c:/GrosMorne"):
            arcpy.HillShade_3d("WesternBrook", "wbrook_hill", 300)
            arcpy.Aspect_3d("WesternBrook", "wbrook_aspect")

These guard against errors in whatever processing you do so that the environment variables/extensions are reverted back to their original state no matter how the code within the with block exits (e.g., successfully, errored, interrupted).

That looks something like this:

arcpy.env.overwriteOutput = False
print('Before the context manager: {}'.format(arcpy.env.overwriteOutput))
with OverwriteState(True):
    print('Inside the context manager: {}'.format(arcpy.env.overwriteOutput))
    raise ValueError("arcpy is a bad time")
print('After the context manager: {}'.format(arcpy.env.overwriteOutput))

Output:

Before the context manager: False
Inside the context manager: True
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
[snip]
ValueError: arcpy is a bad time
---------------------------------------------------------------------------
After the context manager: False

After learning about some python standard library goodness, the context managers simplify to:

import arcpy
from contextlib import contextmanager

@contextmanager
def Extension(name):
    """ Safely use ArcGIS extensions """
    if arcpy.CheckExtension(name) == u"Available":
        status = arcpy.CheckOutExtension(name)
        yield status
    else:
        raise RuntimeError("%s license isn't available" % name)

    arcpy.CheckInExtension(name)


@contextmanager
def OverwriteState(state):
    """ Temporarily set the ``overwriteOutput`` env variable. """
    orig_state = arcpy.env.overwriteOutput
    arcpy.env.overwriteOutput = bool(state)
    yield state
    arcpy.env.overwriteOutput = orig_state


@contextmanager
def WorkSpace(path):
    """ Temporarily set the ``workspace`` env variable. """
    orig_workspace = arcpy.env.workspace
    arcpy.env.workspace = path
    yield path
    arcpy.env.workspace = orig_workspace
2
  • I really like this context manager-based approach - I had some functions I was using to get the same results, but this is more elegant. I wanted it generic though, so I wrote a context manager for all of the environment settings. gist.github.com/nickrsan/… - it seemed like enough of a shift that I shouldn't edit it into the answer though, but thought it was worth leaving here. Thanks for the great idea! – nicksan Jun 3 '17 at 18:19
  • @nicksan -- I think that's a great approach and totally belongs in this answer! I posted it as a wiki for a reason ;) – Paul H Jun 4 '17 at 15:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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