This chapter discusses debugging procedures and provides a review of the most common Python errors. Error-handling procedures are also discussed, including how to get the most out of try-except
statements.
No matter how careful you are in writing code, errors are bound to happen. There are three main types of errors in Python: syntax errors, exceptions, and logic errors. Syntax errors prevent code from running in the first place. When an exception occurs, a script will stop running midprocess. A logic error means the script will run but produce undesired results. Syntax errors and exceptions are reviewed, with strategies on how to address them.
Syntax errors pertain to spelling, punctuation, indentation, and other conventions of the Python language. Common syntax errors result from misspelled keywords or variables, missing punctuation, and inconsistent indentation. See if you can spot the error in the following code:
import arcpy
arcpy.env.workspace = "C:/Data/mydata.gdb"
fclist = arcpy.ListFeatureClasses()
for fc in fclist
count = arcpy.GetCount_management(fc)
print(count)
The colon at the end of the first line of the for
loop is missing. The following syntax error is displayed when you try to run this code:
SyntaxError: invalid syntax
Python IDEs have built-in checking processes, which work somewhat like a spell checker in a word-processing application. Consider the example script in IDLE.
Script window in IDLE with the same code as in the text—i.e., with a missing colon at the end of the first line of the for
loop.
From the top menu, click Run > Check Module. This process performs a syntax check without running the code. A pop-up dialog box appears to report the syntax error.
Pop-up dialog box called SyntaxError with a message reporting “invalid syntax”.
In the script itself, the location of the syntax error is also shown as a red bar.
Script window in IDLE with the same code as in the text, showing a red horizontal bar at the end of line 4 to show the location of the syntax error.
The error check does not tell you exactly what the error is (i.e., a missing colon), only the location of the syntax error.
Changing line 4 to the following code fixes the syntax error:
for fc in fclist:
When you click Run > Check Module, no error message appears, and the interactive interpreter opens. This means the script is ready to run.
Basic error checking also includes indentation and spacing. For example, the following block of code uses inconsistent indentation:
for fc in fclist:
count = arcpy.GetCount_management(fc)
print(count)
Checking this code in IDLE produces the following message:
SyntaxError: unexpected indent
Similar errors can occur if your indentation uses a combination of spaces and tabs. Especially when you copy from other sources, such as Microsoft Word, Microsoft PowerPoint, or Adobe PDF, blocks of code may appear to be indented correctly visually but may use a combination of spaces and tabs.
Other IDEs have similar functionality for basic syntax checking. For example, Spyder displays syntax errors automatically as you write your script. In the example, a small red dot with a white cross appears to the left of the line number. Hovering over this symbol brings up a pop-up window with the message “invalid syntax.”
Script window in Spyder with the same code as in the earlier examples, showing an error symbol to the left of line 4 and a pop-up window reporting “invalid syntax.”
Similarly, inconsistent indentation produces a warning message: “unexpected indent.” One of the benefits of using an editor such as Spyder is that these errors and warnings appear immediately as you write your code, which is a great learning tool.
Script window in Spyder with the same code as in the earlier examples, showing a warning symbol to the left of line 6 and a pop-up window reporting “unexpected indent.”
The syntax error checking in PyCharm also is carried out as you write your code, but the display is more subtle. Syntax errors are shown as a red squiggly line, just as spelling errors are shown in Microsoft Word (among other applications). This can make the display hard to see because often the syntax error is only a single character wide.
Script window in PyCharm with the same code as in the earlier examples, showing a red squiggly line at the end of the first line of the for
loop.
In addition, the popular display theme Darcula (dark background, light code) makes these colors somewhat difficult to spot. The default display theme (light background, dark code) used in the figures typically makes it easier to see these colors.
When you place your cursor at the spot of the syntax error, the error shows up at the lower-left corner of the application in the status bar. When you pause with your cursor over the spot of the syntax error, a pop-up window appears with the syntax error as well.
Script window in PyCharm with the same code as in the earlier examples, showing a pop-up window with the message “Colon expected.”
Being able to recognize syntax errors as you write your code can be helpful. In addition, PyCharm can generate a report with all the errors and warnings in a script. From the top menu, click Code > Inspect Code, and then select the current script. Consider the following script with two syntax errors:
import arcpy
arcpy.env.workspace = "C:/Data/mydata.gdb"
fclist = arcpy.ListFeatureClasses()
for fc in fclist
count = arcpy.GetCount_management(fc)
print(count)
PyCharm’s code inspection produces the results shown in the figure.
The inspection results include the two syntax errors of a missing colon and inconsistent indentation, but also several warnings and spelling issues. Although these issues may warrant attention, they do not prevent the code from running. Some of the warnings are duplicates of the syntax errors, whereas some of the spelling issues are related to the names of datasets and/or variables for which normal spelling rules don’t apply.
Another helpful feature of PyCharm in this context is that it checks for the internal consistency of variable names. Consider the following code:
import arcpy
arcpy.env.workspace = "C:/Data/mydata.gdb"
fclist = arcpy.ListFeatureClasses()
for fc in mylist:
count = arcpy.GetCount_management(fc)
print(count)
There are no syntax issues, but the variable fclist
is assigned a value in line 3 and is incorrectly referred to as mylist
in line 4. This variable name is not a syntax error and does not prevent the code from running. However, the code would not run correctly because mylist
has not been given a value. PyCharm reports this issue as an unresolved reference.
Script window in PyCharm with the same code as in the text, showing a pop-up window with the message “Unresolved reference ‘mylist.’”
Like syntax errors, the feedback does not tell you exactly how to fix the issue, but it does point you in the right direction. Spyder has the same built-in feature and flags the variable mylist
as an “undefined name.”
Built-in code-checking features such as those provided by Spyder and PyCharm make these editors more versatile compared with IDLE, which only catches syntax errors.
Syntax errors are frustrating, but they are relatively easy to catch compared with other errors. Consider the following example that has the syntax corrected:
import arcpy
arcpy.env.workspace = "C:/Data/mydata.gdb"
fclist = arcpy.ListFeatureClasses()
for fc in fclist:
count = arcpy.GetCount_management(fc)
print(count)
When you run the script again, it runs without a syntax error. But what if no count is printed? Is that an error? Perhaps the workspace is incorrect, or perhaps there are no feature classes in the workspace. Perhaps the feature classes are all empty?
Rather than referring to these incidents as “errors,” programming languages commonly discern between a normal course of events and something exceptional. There might be errors, or there may simply be events you did not anticipate. These events are called exceptions. Exceptions refer to errors that are detected while the script is running. When an exception is detected, the script stops running unless the exception is handled properly. Exceptions are said to be “thrown,” as in “the script throws an exception.” If the exception is handled properly—that is, it is “caught”—the program can continue running. Later sections in the chapter provide examples of exceptions and proper error-handling techniques.
When code results in exceptions or logic errors, you may need to look more closely at the values of the variables in your script, which can be accomplished using a debugging procedure. Debugging is a methodological process for finding errors in your script. There are several possible debugging procedures, from basic to complex. Debugging procedures include the following:
This section reviews each of these approaches in more detail. Keep in mind that most of the time, debugging does not tell you why a script did not run properly, but it will tell you where—that is, on which line of code it failed. Typically, you must still figure out why the error occurred.
Error messages generated by ArcPy are usually informative. Consider the following example:
import arcpy
arcpy.env.workspace = "C:/Data"
infcs = ["streams.shp", "floodzone.shp"]
outfc = "union.shp"
arcpy.Union_analysis(infcs, outfc)
This script carries out a union between two input feature classes, which are entered as a list. The result should be a new output feature class in the same workspace. The error message is as follows:
ExecuteError: Failed to execute. Parameters are not valid.
ERROR 000366: Invalid geometry type
Failed to execute (Union).
This specific error message produced by ArcPy is also referred to as an ExecuteError
exception. The message is useful because it includes the text Invalid geometry type
. Closer inspection of the input feature classes reveals that one of the inputs (streams.shp) is a polyline feature class, and the Union tool works with polygon features only. Therefore, the error message does not tell you exactly what is wrong (that is, it does not say that streams.shp is of geometry type polyline and that the Union tool does not accept this geometry type), but it does point you in the right direction.
When a specific error code is included in the error message, such as ERROR 000366, you can learn more about it in ArcGIS Pro help. You can search for the meaning of an error message by entering the error code—i.e., “000366.”
The error code includes a description, saying that the geometry type is invalid, and a solution, to check which geometry type is supported for the operation and should be used as input.
Not all error messages are as useful. Consider the following script:
import arcpy
arcpy.env.workspace = "C:/mydata"
infcs = ["streams.shp", "floodzone.shp"]
outfc = "union.shp"
arcpy.Union_analysis(infcs, outfc)
This script is the same as before, but it uses a different workspace (C:\mydata), which does not exist. The error message in IDLE is as follows:
Traceback (most recent call last):
File "C:\Data\myunion.py", line 5, in <module>
arcpy.Union_analysis(infcs, outfc)
File "C:\Program Files\ArcGIS\Pro\Resources\ArcPy\arcpy\analysis.py", line 737, in Union
raise e
File "C:\Program Files\ArcGIS\Pro\Resources\ArcPy\arcpy\analysis.py", line 734, in Union
retval = convertArcObjectToPythonObject(gp.Union_analysis(*gp_fixargs((in_features, out_feature_class, join_attributes, cluster_tolerance, gaps), True)))
File "C:\Program Files\ArcGIS\Pro\Resources\ArcPy\arcpy\geoprocessing\_base.py", line 498, in <lambda>
return lambda *args: val(*gp_fixargs(args, True))
arcgisscripting.ExecuteError: Failed to execute. Parameters are not valid.
ERROR 000732: Input Features: Dataset streams does not exist or is not supported
Failed to execute (Union).
The error message is misleading. It appears to suggest that the error is on line 5 of the code (where the Union is carried out) and that there is an issue with the tool parameters. The error is, in fact, on line 2, where an invalid workspace is defined, and the invalid parameter message results from the fact that no feature classes could be obtained from the nonexistent workspace. Unfortunately, the built-in error reporting functions don’t always report a more specific error message, such as “workspace does not exist.”
Examining error messages carefully is useful because they may hold the answer to fixing a problem. But don’t stare yourself blind at them because the error may be something quite different, and the error messages could prove misleading.
When you have multiple lines of code that contain geoprocessing tools, it may not always be clear on which line an error occurred. In such cases, it may be useful to print messages after each geoprocessing tool or other important steps to confirm they were run successfully. Consider the following code:
import arcpy
arcpy.env.overwriteOutput = True
arcpy.env.workspace = "C:/Data"
arcpy.Buffer_analysis("roads.shp", "buffer.shp", "1000 METERS")
print("Buffer completed")
arcpy.Erase_analysis("buffer.shp", "zone.shp", "erase.shp")
print("Erase completed")
arcpy.Clip_analysis("erase.shp", "wetlands.shp", "clip.shp")
print("Clip completed")
If an error message occurs, the print messages will illustrate which steps are completed, regardless of how cryptic the error message is. The error can then most likely be traced to the block of code just before the print message that did not execute.
Printing also is used to check the values of variables in your script. Consider the example from chapter 6 in which a script is used to copy existing shapefiles in a workspace into a geodatabase:
import arcpy
import os
arcpy.env.workspace = "C:/Transportation"
outgdb = "C:/Transportation/City.gdb"
fcs = arcpy.ListFeatureClasses()
for fc in fcs:
desc = arcpy.da.Describe(fc)
outfc = os.path.join(outgdb, desc["baseName"])
arcpy.CopyFeatures_management(fc, outfc)
What if the output feature classes are not created, but running the script does not produce any meaningful errors? Paths and file names are a common issue, so one thing you can try is to print the values of the output feature classes as a check. Typically, you comment out the rest of the script to focus on only the path and file names:
import arcpy
import os
arcpy.env.workspace = "C:/Transportation"
outgdb = "C:/Transportation/City.gdb"
fcs = arcpy.ListFeatureClasses()
for fc in fcs:
desc = arcpy.da.Describe(fc)
outfc = os.path.join(outgdb, desc["baseName"])
print(outfc)
##arcpy.CopyFeatures_management(fc, outfc)
The printout would look something like this:
C:/Transportation/City.gdb\arterials
C:/Transportation/City.gdb\bike_routes
C:/Transportation/City.gdb\major_roads
The printout allows you to confirm that the values are as expected, and you can focus your efforts on other possible issues with the script.
Adding print messages can be effective, but they are most useful when you already have a good idea of what might be causing the error. One of the downsides of printing messages is that they must be cleaned up once the error is fixed, which can be a substantial amount of work.
Selectively commenting out code involves removing certain lines to see if this eliminates the error. The script in the earlier section already showed an example of this approach. If your script has a typical sequential workflow, you can work from the bottom up. For example, the following code illustrates how the lower lines of code are commented out, using double number signs (##) to isolate the error:
import arcpy
arcpy.env.overwriteOutput = True
arcpy.env.workspace = "C:/Data"
arcpy.Buffer_analysis("roads.shp", "buffer.shp", "1000 METERS")
##arcpy.Erase_analysis("buffer.shp", "streams.shp", "erase.shp")
##arcpy.Clip_analysis("erase.shp", "wetlands.shp", "clip.shp")
You can manually comment out code by typing the number sign twice at the start of a line of code. Python IDEs also include built-in functionality for this task, so you can comment out multiple lines of code in one step. In IDLE, for example, you highlight one or more lines and click Format > Uncomment Region.
As with printing messages, commenting out lines of code does not identify why an error occurs, but only helps you isolate where it occurs.
Another, more systematic approach to debugging code is to use a Python debugger. A debugger is a tool that allows you to step through your code line by line, to place breakpoints in your code to examine the conditions at that point, and to follow how certain variables change throughout your code. Python has a built-in debugger package called pdb
. It can be cumbersome to use because it lacks a user interface and can be used only from command line. However, Python editors such as IDLE, Spyder, and PyCharm include a debugging environment, which is easier to use. In the example that follows, the IDLE debugger is used. This debugger provides basic functionality, which is enough to illustrate how debugging works.
A typical debugging procedure is as follows:
The script used earlier in this section is used here to illustrate these steps. First, a check is run to determine any syntax issues. Once the syntax is checked, breakpoints are set in the script. Debugging works through a script line by line, but this process can be cumbersome. Breakpoints are set manually at specific points in the script. The debugger will run until a breakpoint is reached. To set breakpoints in a script in IDLE, place the cursor at the line of interest, right-click, and click Set Breakpoint. The line with the breakpoint turns yellow. In the example script, breakpoints are set on the lines with the for
loop and with the path and file name for the output feature class.
Script window in IDLE with the same code as in the text, with lines 6 and 8 highlighted in yellow to indicate breakpoints.
With the breakpoints set, you can start the debugger. In IDLE, you start from the Python Shell (or interactive window), not from the script window. If the Python Shell is not visible, from the top menu in the script window, click Run > Python Shell. From the top menu in the Python Shell, click Debug > Debugger. Below the prompt, the message [DEBUG ON] is printed.
At the same time, the Debug Control window opens.
All the controls are dimmed, because the script has not started running yet. To start the debugging, return to the script window, and run the script as usual. Because you are running it in debugging mode, only the first line of the script is run. The Debug Control window opens and shows the conditions after this first line of code.
The next steps in the debugging process are controlled form the Debug Control window. Click the Go button to run the script until the first breakpoint. This step brings up the conditions of the variables at that line in the script.
The Debug Control window confirms the location of the breakpoint (i.e., line 6 in the script), and it shows the important variables. In this case, it shows the value of the variable fcs, as follows:
['arterials.shp', 'bike_routes.shp', 'major_roads.shp']
This result simply provides a confirmation that the variable was assigned a meaningful value—in this case, a list of strings that point to feature classes in the workspace. You can scroll through the panel at the bottom of the dialog box to view all the variables. This list includes not only variables you created in the script, but also things such as the specific modules being used and their paths.
Click Go again to run the code until the next breakpoint. This time the variable fc is added and contains the value of the first element of the list of feature classes. Click Go again, and the full path of the output feature class is shown.
With every step that follows, the values of fc and outfc are updated to the next element in the list. Effectively, you are stepping through the for
loop one iteration at a time. This procedure is an effective way to confirm that the iteration works correctly.
Once all breakpoints are completed, the debugger stops, and the options in the Debug Control window are dimmed again.
There are several other options as part of the debugging process in IDLE. The Step tool allows you to step through the script, line by line, without jumping ahead to specified breakpoints. The Over and Out tools allow you to skip ahead as necessary. For example, if the current line of code contains a call to a different module, function, or method, using the Step tool results in stepping into that procedure. Using the Over tool runs the line of code without stepping into that procedure—you are “stepping over” the details of that procedure. Once you step into a procedure, you can use the Out tool to step out of the procedure without stepping through the rest of the lines of code. This feature allows you to fast-forward and return to the next line of code that called the procedure. Generally, stepping through your script line by line is not necessary and can, in fact, bring up code from modules being used that you normally don’t need to look at. The use of selective breakpoints is therefore strongly recommended.
Finally, the Quit tool brings the debugging of the current script to an end, but the debugging mode remains on. To stop debugging completely, from the top menu in the Python Shell, click Debug and uncheck the Debugger. This step prints [DEBUG OFF] below the prompt.
Debugging procedures in Spyder and PyCharm work in a similar manner although the functionality of the interface is slightly different. In Spyder, the Debug drop-down from the main menu gives you options to set breakpoints.
From the same menu, click Debug to start the debugging, and click Continue to jump to the next breakpoint.
During the debugging, you can view the variables and their values in the Variable explorer window at the upper right. Instead of using the GUI for debugging, you can also use the IPython debugger called ipdb
in the Python console.
In PyCharm, you can add breakpoints by clicking next to the line number—i.e., between the line number and the code.
To start debugging, from the top menu click Run > Debug. This step bring ups the Debug Tool window in the lower panel. This window provides a full set of controls for debugging, including various toolbars and windows to track variables.
A detailed review of debugging procedures using Spyder and PyCharm is beyond the scope of this chapter. PyCharm especially includes many different options for debugging, and these are described in detail in PyCharm’s online documentation. Although powerful, these tools can take time to learn. To automate geoprocessing tasks for relatively simple stand-alone scripts, the use of syntax checking and the Inspect Code tools in PyCharm are often enough.
Following are some general tips and tricks to help you debug your scripts:
Although debugging procedures can contribute to writing correct code, exception errors are still likely to occur in your scripts. Exceptions refer to errors that are detected as the script is running. One key reason for exceptions is that many scripts rely on user inputs, and you can’t always control the inputs other users provide. Well-written scripts, therefore, include error-handling procedures to handle exceptions. Error-handling procedures are written to avoid having a script fail and not provide meaningful feedback.
To handle exceptions, you could use conditional statements to check for certain scenarios using if
statements. You have already encountered some in previous chapters. For example, the existence of a path can be determined in Python using a built-in Python function such as os.path.exists()
. For catalog paths, you can use the Exists()
function of ArcPy to determine whether datasets exist. For example, the following code determines whether a shapefile exists:
import arcpy
arcpy.env.workspace = "C:/Data"
shape_exists = arcpy.Exists("streams.shp")
print(shape_exists)
The Exists()
function can be used for feature classes, tables, datasets, shapefiles, workspaces, layers, and files in the current workspace. The function returns a Boolean value indicating whether the element exists.
Besides determining whether data exists, you can determine whether the data is the right type by using the Describe()
and da.Describe()
functions of ArcPy. For example, if your script requires a feature class, you can use the datasetType
property to determine whether it is a feature class or something else.
Writing conditional statements for many possible errors is tedious. And it is impossible to foresee every error. In the example code earlier in this section, you would have to check the following: (1) whether the workspace is valid, (2) whether there is at least one feature class in the workspace, and (3) whether there is a feature class with at least one feature. These checks could easily double the code in the script.
There are two strategies to check for errors and report them in a meaningful manner:
try-except
statements.A powerful alternative to conditional statements is the use of Python exception objects. When Python encounters an error, it raises, or throws, an exception, which typically means the script stops running. If such an exception object is not handled, also referred to as caught or trapped, the script terminates with a runtime error, sometimes referred to as a traceback.
Consider the simple example of trying to divide by zero. In the Python window, this results in a runtime error, as shown in the figure.
The following sections illustrate how exceptions are raised and how the try-except
statement effectively traps errors.
Exceptions are raised automatically when something goes wrong while running a script, as illustrated in the examples in earlier sections. You also can raise exceptions yourself by using the raise
statement. For example, you can raise a generic exception using the raise Exception
statement, as follows:
raise Exception
This statement results in something like the following:
Traceback (most recent call last):
File "<string>", line 1, in <module>
Exception
You also can add a specific message, as follows:
raise Exception("invalid workspace")
This statement results in:
Exception: invalid workspace
There are many different types of exceptions. You can view them by importing the builtins
module and using the dir()
function to list all of them, as follows:
import builtins
dir(builtins)
Running this code results in a long printout (not shown in its entirety here):
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning' …]
Note that this code also prints other elements of the builtins
module, including all the built-in functions and constants, but the exceptions are printed first.
Each of these exceptions can be used in the raise
statement. For example:
raise ValueError
This statement results in:
ValueError
This example is a named exception—that is, the specific exception is called by name. Named exceptions allow a script to handle specific exceptions in different ways, which can be beneficial. Using the generic Exception
is referred to as an unnamed exception.
A complete description of built-in Python exceptions can be found in the Python documentation. In the documentation, go to Library Reference, and browse to the section “Built-in Exceptions.” The documentation also illustrates a hierarchy of errors: for example, ZeroDivisionError
is one of several types of arithmetic errors (ArithmeticError
).
Exceptions in a script can be handled—that is, caught or trapped—using a try-except
statement. When an exception is properly handled, the script does not produce a runtime error but instead reports a more meaningful error message to the user. The script finishes and reports information messages if an error occurred.
Consider the following script that divides two user-supplied numbers:
x = input("First integer: ")
y = input("Second integer: ")
print(int(x)/int(y))
The script will work fine until zero (0) is entered as the second number, resulting in the following error message:
ZeroDivisionError: division by zero
The try-except
statement traps this exception and provides additional error handling, as follows:
try:
x = input("First integer: ")
y = input("Second integer: ")
print(int(x)/int(y))
except ZeroDivisionError:
print("The second integer cannot be zero.")
Notice the structure of the try-except
statement. The first line of code consists of only the try
statement, followed by a colon. Next is a block of indented code with the procedure you want to carry out. Then comes the except
statement, which includes a specific exception, followed by a colon. Next is a block of indented code that will be carried out if the specific exception is raised. The exception ZeroDivisionError
is a named exception, which means it is built-in.
In this example, a simple if
statement might have been just as effective to determine whether the value of y is zero (0). However, for more elaborate code, you might need many such if
statements, and a single try-except
statement will suffice to trap the error.
Multiple except
statements can catch different named exceptions. For example:
try:
x = input("First integer: ")
y = input("Second integer: ")
print(int(x)/int(y))
except ZeroDivisionError:
print("The second integer cannot be zero.")
except ValueError:
print("Only integers are valid entries.")
You can also catch multiple exceptions with a single block of code by specifying them as a tuple, as follows:
except (ZeroDivisionError, ValueError):
print("Your entries were not valid.")
In this case, the error handling is not specific to the type of exception, and only a single message is printed, no matter which of the two errors caused the exception. The error message, in this case, is less specific because it describes several exceptions. Although combining exceptions in this manner makes your code shorter, more specific individual exceptions are generally preferred.
The exception object itself also can be called by providing an additional argument:
except (ZeroDivisionError, ValueError) as e:
print(e)
Running this code allows you to catch the exception object itself, and you can print it to see what happened rather than printing a custom error message.
It can be difficult sometimes to predict all the types of exceptions that might occur. Especially in a script that relies on user input, you may not be able to foresee all the possible scenarios. Therefore, to catch all the exceptions, no matter what type, you can simply omit the specific exception from the except
statement, as follows:
try:
x = input("First integer: ")
y = input("Second integer: ")
print(int(x)/int(y))
except Exception as e:
print(e)
In this example, the exception is unnamed.
You may wonder how this is different from simply not using a try-except
statement and allowing the error to occur, resulting in an error message. The key difference is that when you use a try-except
statement, the script continues and finishes normally. If the error is not trapped, the script is stopped midprocess. Therefore, with a try-except
statement, any code that follows it will still be executed, regardless of the error, whereas an error that is not trapped will result in the script being terminated before that portion of the code runs. Therefore, trapping errors results in more robust and user-friendly scripts.
The try-except
statement also can include an else
statement, which is like a conditional statement. For example:
while True:
try:
x = input("First integer: ")
y = input("Second integer: ")
print(int(x)/int(y))
except:
print("Please try again.")
else:
break
In this example, the try
block of code is repeated in a while
loop when an exception is raised. The loop is broken by the break
statement in the else
block only when no exception is raised. If a user enters invalid values such as a string, a float, or the value 0 for the second input, the script does not produce an error but prompts the user to try again. The script prints the result only when valid entries are provided.
One more addition to the try-except
statement is the finally
statement. Whatever the result of previous try
, except
, or else
blocks of code, the finally
block of code will always be executed. This block typically consists of cleanup tasks and can include checking in licenses or deleting references to files.
Instead of using one of the existing named exceptions, you also can create your own exceptions. Consider the following example code in which a user is asked to provide a new password, and the script checks whether the entry contains at least six characters:
try:
password = input("Enter a new password: ")
if len(password) > 5:
print("Your password is long enough.")
else:
raise ValueError
except ValueError:
print("Your password is too short.")
The named exception ValueError
is used here, but there are many other similar checks that could be performed, including allowable characters, using upper- and lowercase letters, using a mix of letters and numbers, and so on. Any of these errors would also be a ValueError
exception. It would therefore be helpful to have a specific exception for the case in which the password is too short. This can be accomplished using a custom exception. This involves creating a custom class using the class
keyword, as follows:
class TooShortError(ValueError):
pass
The custom exception is given a name and is based on the existing ValueError
exception.
Creating a new class uses the keyword class
, followed by the name of the class and any arguments. The line of code ends with a colon, and the block of code that follows contains what the class does. Because, in this case, the exception needs only to exist and does not need to do anything else, no code is needed. However, a colon must always be followed by a block of code, and therefore at least one line of code is needed that does not do anything. This line of code is accomplished using the pass
keyword, which means “there is no code to execute here.”
The entire script is now as follows:
class TooShortError(ValueError):
pass
try:
password = input("Enter a new password: ")
if len(password) > 5:
print("Your password is long enough.")
else:
raise TooShortError
except TooShortError:
print("Your password is too short.")
Creating the custom class is at the top of the script because any classes must be defined before they can be used. The rest of the script is the same, but the named exception ValueError
is replaced by the custom exception TooShortError
. The use of custom exception classes makes your code easier to understand.
So far, the exceptions raised are quite general. A Python script can, of course, fail for many reasons that are not specifically related to a geoprocessing tool, as the previous examples illustrate. However, because errors related to geoprocessing tools are somewhat unique in nature, they warrant more attention.
You can think of errors as falling into two categories: geoprocessing errors and everything else. When a geoprocessing tool writes an error message, ArcPy generates a system error. Specifically, when a geoprocessing tool fails to run, it throws an ExecuteError
exception, which can be used to handle specific geoprocessing errors. It is not one of the built-in Python exception classes, but it is generated by ArcPy, and thus the arcpy.ExecuteError
class must be used.
Consider this example:
import arcpy
arcpy.env.workspace = "C:/Data"
in_features = "streams.shp"
out_features = "streams.shp"
try:
arcpy.CopyFeatures_management(in_features, out_features)
except arcpy.ExecuteError:
print(arcpy.GetMessages(2))
except:
print("There has been a nontool error.")
The Copy Features tool generates an error because the input and output feature classes cannot be the same, as follows:
Failed to execute. Parameters are not valid.
ERROR 000725: Output Feature Class: Dataset C:/Data\streams.shp already exists.
Failed to execute (CopyFeatures).
In the example code, the first except
statement traps any geoprocessing errors, and the second except
statement traps any other errors not related to geoprocessing. This example illustrates how both named and unnamed exceptions can be used in the same script. It is important to first check the named exceptions, such as except arcpy.ExecuteError
, and then the unnamed exceptions. If the unnamed exceptions were checked first, the statement would catch all exceptions, including any arcpy.ExecuteError
exceptions. You would never know whether a named exception (that you put in the script) occurred or not.
In larger scripts, it can be difficult to determine the precise location of an error. You can use the Python traceback
module to isolate the location and cause of an error.
The traceback
structure is as follows:
import arcpy
import sys
import traceback
try:
<block of code including geoprocessing tools>
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = ("PYTHON ERRORS:\nTraceback info:\n" + tbinfo +
"\nError Info:\n" + str(sys.exc info() [1]))
msgs = "ArcPy ERRORS:\n" + arcpy.GetMessages(2) + "\n"
arcpy.AddError(pymsg)
arcpy.AddError(msgs)
print(pymsg)
print(msgs)
In this code, two types of errors are trapped: geoprocessing errors and all other types of errors. The geoprocessing errors are obtained using the arcpy.GetMessages()
function. The errors are returned for use in a script tool using arcpy.AddError()
and also printed to the standard Python output using print()
. All other types of errors are retrieved using the traceback
module. Some formatting is applied, and the errors are returned for use in a script tool and printed to the standard Python output.
Following is one more example of a try-except
statement using the finally
statement. In this example, a custom exception class is created to handle a license error. A license is checked out in the try
code block, and the license is checked in as part of the finally
code block. These steps ensure the license is checked in, no matter the outcome of running the earlier code blocks, as follows:
class LicenseError(Exception):
pass
import arcpy
try:
if arcpy.CheckExtension("3D") == "Available":
arcpy.CheckOutExtension("3D")
else:
raise LicenseError
arcpy.env.workspace = "C:/Raster"
arcpy.Slope_3d("elevation", "slope")
except LicenseError:
print("3D license is unavailable")
except:
print(arcpy.GetMessages(2))
finally:
arcpy.CheckInExtension("3D")
The general ExecuteError
exception class is useful, and some scripts rely on the simple but effective try-except
statement without using any additional exception classes. However, it is better to employ more explicit error checking, including custom exceptions such as the LicenseError
exception in the previous example.
Using the try-except
statement for error trapping is common. Sometimes, you will see an entire script wrapped in a try-except
statement. It looks something like the following, in which the try
code block can contain hundreds of lines of code:
import arcpy
import sys
import traceback
try:
## multiple lines of code here
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = ("PYTHON ERRORS:\nTraceback info:\n" + tbinfo +
"\nError Info:\n" + str(sys.exc_info()[1]))
msgs = "ArcPy ERRORS:\n" + arcpy.GetMessages(2) + "\n"
arcpy.AddError(pymsg)
arcpy.AddError(msgs)
This approach to error handling is a relatively common structure for complex scripts, and it is widely used in this or slightly modified form.
In addition to a try-except
statement for trapping errors in scripts, several other error-handling methods can be used. Some of them are covered in other chapters but they warrant further mention here:
arcpy.ValidateTableName()
and arcpy.ValidateFieldName()
functions, respectively.arcpy.CheckProduct()
function and for extensions using the arcpy.CheckExtension()
function.Following are several common errors to look out for when you are scanning your scripts and examining your data.
arcpy
or os
mylist
versus myList
for
, while
, else
, try
, except
)overwriteOutput
property of the env
class to True
.Result
object that is printed to the Results window, so you must use the getOutput()
method to obtain the number. Similarly, distinctions between feature classes and feature layers may seem somewhat trivial, but they can be just the difference between proper script execution and failure. Carefully examine script syntax, and determine the exact nature of the inputs and outputs.It is worth noting that many geoprocessing-related errors can be prevented when using script tools or Python toolboxes. Validation for preventing invalid parameters is included in these tools.
Some of these suggestions may appear rudimentary, but the solutions often can be simple if you only know where to look for them. The syntax of a good Python script can be relatively simple, which is part of the beauty of using Python.