Lesson 2: Python and programming basics
Lesson 2: Python and programming basics jed124The links below provide an outline of the material for this lesson. Be sure to carefully read through the entire lesson before returning to Canvas to submit your assignments.
Lesson 2 Overview
Lesson 2 Overview jed124In Lesson 1, you received an introduction to Python. Lesson 2 builds on that experience, diving into Python fundamentals. Many of the things you'll learn are common to programming in other languages. If you already have coding experience, this lesson may contain some review.
This lesson has a relatively large amount of reading from the course materials, the Zandbergen text, and the ArcGIS help. I believe you will get a better understanding of the Python concepts as they are explained and demonstrated from several different perspectives. Whenever the examples use the IPython console, I strongly suggest that you type in the code yourself as you follow the examples. This can take some time, but you'll be amazed at how much more information you retain if you try the examples yourself instead of just reading them.
At the end of the lesson, you'll be required to write a Python script that adds many of the things you've learned during the lesson. This will go much faster if you've taken the time to read all the required text and work through the examples. These assignments will build off of the previous lesson's concepts, but may require they be applied slightly differently.
Lesson 2 Checklist
Lesson 2 Checklist jed124Lesson 2 covers Python fundamentals (many of which are common to other programming languages) and gives you a chance to practice these in a project. To complete this lesson, you are required to do the following:
- Download the Lesson 2 data and extract it to C:\PSU\Geog485\Lesson2.
- Work through the online sections of the lesson.
- Read the remainder of Zandbergen chapters 4-6 that we didn't cover in Lesson 1 and sections 7.1 - 7.5. In the online lesson pages, I have inserted instructions about when it is most appropriate to read each of these chapters. There is more reading in this lesson than in a typical week. If you are new to Python, please plan some extra time to read these chapters. There are also some readings this week from the ArcGIS Help.
- Complete Project 2 and upload its deliverables to the Lesson 2 drop box in Canvas. The deliverables are listed in the Project 2 description page.
- Complete the Lesson 2 Quiz in Canvas.
Do items 1 - 3 (including any of the practice exercises you want to attempt) during the first week of the lesson. You will need the second week to concentrate on the project and quiz.
Lesson Objectives
By the end of this lesson, you should:
- understand basic Python syntax for conditional statements and program flow control (if-else, comparison operators, for loop, while loop);
- be familiar with more advanced data types (strings, lists), string manipulation, and casting between different types;
- know how to debug code and how to use the debugger;
- be able to program basic scripts that use conditional statements and loops to automate tasks.
2.1 More Python fundamentals
2.1 More Python fundamentals jed124In Lesson 1, you saw your first Python scripts and were introduced to the basics, such as importing modules, using arcpy, working with properties and methods, and indenting your code in try/catch blocks. In the following sections, you'll learn about more Python programming fundamentals such as working with lists, looping, if/then decision structures, manipulating strings, and casting variables.
Although this might not be the most thrilling section of the course, it's probably the most important section for you to spend time understanding and experimenting with on your own, especially if you are new to programming.
Programming is similar to playing sports: if you take time to practice the fundamentals, you'll have an easier time when you need to put all your skills together. For example, think about the things you need to learn in order to play basketball. A disciplined basketball player practices dribbling, passing, long-range shooting, layup shots, free throws, defense, and other skills. If you practice each of these fundamentals well individually, you'll be able to put them together when it's time to play a full game.
Learning a programming language is the same way. When faced with a problem, you'll be forced to draw on your fundamental skills to come up with a workable plan. You may need to include a loop in your program, store items in a list, or make the program do one of four different things based on certain user input. If you know how to do each of these things individually, you'll be able to fit the pieces together, even if the required task seems daunting.
Take time to make sure you understand what's happening in each line of the code examples, and if you run into a question, please post to the forums.
2.1.1 Lists
2.1.1 Lists jed124In Lesson 1, you learned about some common data types in Python, such as strings and integers. Sometimes you need a container type that can store multiple related values together. Python offers several ways of doing this, and the first one we'll learn about is the list.
Here's a simple example of a list. You can type this in the PyScripter Python Interpreter to follow along:
>>> suits = ['Spades', 'Clubs', 'Diamonds', 'Hearts']
This list named 'suits' stores four related string values representing the suits in a deck of cards. In many programming languages, storing a group of objects in sequence like this is done with arrays. While the Python list could be thought of as an array, it's a little more flexible than the typical array in other programming languages. This is because you're allowed to put multiple data types into one list.
For example, suppose we wanted to make a list for the card values you could draw. The list might look like this:
>>> values = ['Ace', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'Jack', 'Queen', 'King']
Notice that you just mixed string and integer values in the list. Python doesn't care. However, each item in the list still has an index, meaning an integer that denotes each item's place in the list. The list starts with index 0 and for each item in the list, the index increments by one. Try this:
>>> print (suits[0]) Spades >>> print (values[12]) King
In the above lines, you just requested the item with index 0 in the suits list and got 'Spades'. Similarly, you requested the item with index 12 in the values list and got 'King'.
It may take some practice initially to remember that your lists start with a 0 index. Testing your scripts can help you avoid off-by-one errors that might result from forgetting that lists are zero-indexed. For example, you might set up a script to draw 100 random cards and print the values. If none of them is an Ace, you've probably stacked the deck against yourself by making the indices begin at 1.
Remember you learned that everything is an object in Python? That applies to lists too. In fact, lists have a lot of useful methods that you can use to change the order of the items, insert items, sort the list, and so on. Try this:
>>> suits = ['Spades', 'Clubs', 'Diamonds', 'Hearts'] >>> suits.sort() >>> print (suits) ['Clubs', 'Diamonds', 'Hearts', 'Spades']
Notice that the items in the list are now in alphabetical order. The sort() method allowed you to do something in one line of code that would have otherwise taken many lines. Another helpful method like this is reverse(), which allows you to sort a list in reverse alphabetical order:
>>> suits.reverse() >>> print (suits) ['Spades', 'Hearts', 'Diamonds', 'Clubs']
Before you attempt to write list-manipulation code, check your textbook or the Python list reference documentation to see if there's an existing method that might simplify your work.
Inserting items and combining lists
What happens when you want to combine two lists? Type this in the PyScripter Interpreter:
>>> listOne = [101,102,103] >>> listTwo = [104,105,106] >>> listThree = listOne + listTwo >>> print (listThree) [101, 102, 103, 104, 105, 106]
Notice that you did not get [205,207,209]; rather, Python treats the addition as appending listTwo to listOne. Next, try these other ways of adding items to the list:
>>> listThree += [107] >>> print (listThree) [101, 102, 103, 104, 105, 106, 107] >>> listThree.append(108) >>> print (listThree) [101, 102, 103, 104, 105, 106, 107, 108]
To put an item at the end of the list, you can either add a one-item list (how we added 107 to the list) or use the append() method on the list (how we added 108 to the list). Notice that listThree += [107] is a shortened form of saying listThree = listThree + [107].
If you need to insert some items in the middle of the list, you can use the insert() method:
>>> listThree.insert(4, 999) >>> print (listThree) [101, 102, 103, 104, 999, 105, 106, 107, 108]
Notice that the insert() method above took two parameters. You might have even noticed a tooltip that shows you what the parameters mean.
The first parameter is the index position that the new item will take. This method call inserts 999 between 104 and 105. Now 999 is at index 4.
Getting the length of a list
Sometimes you'll need to find out how many items are in a list, particularly when looping. Here's how you can get the length of a list:
>>> myList = [4,9,12,3,56,133,27,3] >>> print (len(myList)) 8
Notice that len() gives you the exact number of items in the list. To get the index of the final item, you would need to use len(myList) - 1. Again, this distinction can lead to off-by-one errors if you're not careful.
Other ways to store collections of data
Lists are not the only way to store ordered collections of items in Python; you can also use tuples and dictionaries. Tuples are like lists, but they are immutable. This means that you can't change the objects inside the tuple. In some cases, a tuple might actually be a better structure for storing values like the suits in a deck of cards because this is a fixed list that you wouldn't want your program to change by accident.
Dictionaries differ from lists in that items are not indexed; instead, each item is stored with a key and the value as a key:value pair. We'll use dictionaries later in the course, and your reading assignment for this lesson covers dictionary basics. The best way to understand how dictionaries work is to play with some of the textbook examples in the PyScripter Python Interpreter (see Zandbergen 4.17).
2.1.2 Loops
2.1.2 Loops jed124A loop is a section of code that repeats an action. Remember, the power of scripting (and computing in general) is the ability to quickly repeat a task that might be time-consuming or error-prone for a human. Looping is how you repeat tasks with code; whether it's reading a file, searching for a value, or performing the same action on each item in a list.
for Loop
A for loop does something with each item in a list. Type this in the PyScripter Python Interpreter to see how a simple for loop works:
>>> for name in ["Carter", "Reagan", "Bush"]: print (name + " was a U.S. president.")
After typing this, you'll have to hit Enter twice in a row to tell PyScripter that you are done working on the loop and that the loop should be executed. You should see:
Carter was a U.S. president Reagan was a U.S. president Bush was a U.S. president
Notice a couple of important things about the loop above. First, you declared a new variable name after the word for to represent each item in the list as you iterated through. This variable name will get set to the current item in the list that is being iterated. Using the example above, the first iteration it is set to Carter. Once all of code logic is executed, and the iteration for that item is complete, name is then set to the next item in the list, which is Reagan. This continues until there is no more items in the list to iterate over, or a special command is used to stop the iteration.
The second thing to notice is that after the condition, or the first line of the loop, you typed a colon (:), then started indenting subsequent lines. Some programming languages require you to type some kind of special line or character at the end of the loop (for example, "Next" in Visual Basic, or "}" in JavaScript), but Python just looks for the place where you stop indenting. By pressing Enter twice, you told Python to stop indenting and that you were ready to run the loop.
for loops can also work with lists of numbers. Try this one in the PyScripter Python Interpreter:
>>> x = 2 >>> multipliers = [1,2,3,4] >>> for num in multipliers: print (x * num) 2 4 6 8
In the loop above, you multiplied each item in the list by 2. Notice that you can set up your list before you start coding the loop.
You could have also done the following with the same result:
>>> multipliers = [1,2,3,4] >>> for num in multipliers: x = 2 print (x * num)
The above code, however, is less efficient than what we did initially. Can you see why? This time, you are declaring and setting the variable x=2 inside the loop. The Python interpreter will now have to read and execute that line of code four times instead of one. You might think this is a trivial amount of work, but if your list contained thousands or millions of items, the difference in execution time would become noticeable. Declaring and setting variables outside a loop, whenever possible, is a best practice in programming.
While we're on the subject, what would you do if you wanted to multiply 2 by every number from 1 to 1000? It would definitely be too much typing to manually set up a multipliers list, as in the previous example. In this case, you can use Python's built-in range function. Try this:
>>> x = 2 >>> for num in range(1,1001): print (x * num)
The range function is your way of telling Python, "Start here and stop there." We used 1001 because the loop stops one item before the function's second argument (the arguments are the values you put in parentheses to tell the function how to run). If you need the function to multiply by 0 at the beginning as well, you could even get away with using one argument:
>>> x = 2 >>> for num in range(1001): print (x * num)
The range function has many interesting uses, which are detailed in this section's reading assignment in Zandbergen.
while Loops
A while loop executes until some condition is met. Here's how to code our example above using a while loop:
>>> x = 0
>>> while x < 1001:
print (x * 2)
x += 1while loops often involve the use of some counter that keeps track of how many times the loop has run. Sometimes you'll perform operations with the counter. For example, in the above loop, x was the counter, and we also multiplied the counter by 2 each time during the loop. To increment the counter, we used x += 1 which is shorthand for x = x + 1, or "add one to x".
Nesting loops
Some situations call for putting one loop inside another, a practice called nesting. Nested loops could help you print every card in a deck (minus the Jokers):
>>> suits = ['Spades', 'Clubs', 'Diamonds', 'Hearts'] >>> values = ['Ace', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'Jack', 'Queen', 'King'] >>> for suit in suits: for value in values: print (str(value) + " of " + str(suit))
In the above example, you start with a suit, then loop through each value in the suit, printing out the card name. When you've reached the end of the list of values, you jump out of the nested loop and go back to the first loop to get the next suit. Then you loop through all values in the second suit and print the card names. This process continues until all the suits and values have been looped through.
Looping in GIS models
You will use looping repeatedly (makes sense!) as you write GIS scripts in Python. Often, you'll need to iterate through every row in a table, every field in a table, or every feature class in a folder or a geodatabase. You might even need to loop through the vertices of a geographic feature.
You saw above that loops work particularly well with lists. arcpy has some methods that can help you create lists. Here's an example you can try that uses arcpy.ListFeatureClasses(). First, manually create a new folder C:\PSU\Geog485\Lesson2\PracticeData. Then copy the code below into a new script in PyScripter and run the script. The script copies all the data in your Lesson2 folder into the new Lesson2\PracticeData folder you just created.
# Copies all feature classes from one folder to another
import arcpy
try:
arcpy.env.workspace = "C:/PSU/Geog485/Lesson2"
# List the feature classes in the Lesson 2 folder
fcList = arcpy.ListFeatureClasses()
# Loop through the list and copy the feature classes to the Lesson 2 PracticeData folder
for featureClass in fcList:
arcpy.CopyFeatures_management(featureClass, "C:/PSU/Geog485/Lesson2/PracticeData/" + featureClass)
except:
print ("Script failed to complete")
print (arcpy.GetMessages(2))Notice above that once you have a Python list of feature classes (fcList), it's very easy to set up the loop condition (for featureClass in fcList:).
Another common operation in GIS scripts is looping through table data. The arcpy module contains some special data-access objects called 'cursors' that help you do this. Here's a short script showing how a cursor can loop through each row in a feature class and print the attribute value from the attribute field 'Name'. We'll cover cursors in detail in the next lesson, so don't worry if some of this code looks confusing right now. The important thing is to notice how a loop is used to iterate through each record:
import arcpy
inTable = "C:/PSU/Geog485/Lesson2/CityBoundaries.shp"
inField = "NAME"
rows = arcpy.SearchCursor(inTable)
#This loop goes through each row in the table
# and gets a requested field value
for row in rows:
currentCity = row.getValue(inField)
print (currentCity)
In the above example, a search cursor object is set to the variable named rows that retrieves data from the table. The for loop provides the iteration over the returned data by setting each item to the variable row, and makes it possible to perform an action on the row's attributes.
ArcGIS Help reading
Read the following in the ArcGIS Pro Help:
2.1.3 Decision structures
2.1.3 Decision structures jed124Many scripts that you write will need to have conditional logic that executes a block of code given a condition and perhaps executes a different block of code given a different condition. The "if", "elif", and "else" statements in Python provide this conditional logic. Try typing this example in the Python Interpreter:
x = 3
if x > 2:
print ("Greater than two")
Greater than two
In the above example, the keyword "if" denotes that some conditional test is about to follow. In this case, the condition of x being greater than two was met, so the script printed "Greater than two." Notice that you are required to put a colon (:) after the condition and indent any code executing because of the condition. For consistency in this class, all indentation is done using four spaces.
Using "else" is a way to run code if the condition isn't met. Try this:
x = 1
if x > 2:
print ("Greater than two")
else:
print ("Less than or equal to two")
Less than or equal to two
Notice that you don't have to put any condition after "else." It's a way of catching all other cases. Again, the conditional code is indented four spaces, which makes the code very easy for a human to scan. The indentation is required because Python doesn't require any type of "end if" statement (like many other languages) to denote the end of the code you want to execute.
If you want to run through multiple conditions, you can use "elif", which is Python's abbreviation for "else if":
x = 2
if x > 2:
print ("Greater than two")
elif x == 2:
print ("Equal to two")
else:
print ("Less than two")
Equal to two
In the code above, elif x == 2: tests whether x is equal to two. The == is a way to test whether two values are equal. Using a single = in this case would result in an error because = is used to assign values to variables. In the code above, you're not trying to assign x the value of 2, you want to check if x is already equal to 2, hence you use ==.
Caution: Using = instead of == to check for equivalency is a very common Python mistake, especially if you've used other languages where = is allowed for equivalency checks.
You can also use if, elif, and else to handle multiple possibilities in a set. The code below picks a random school from a list (notice we had to import the random module to do this and call a special method random.randrange()). After the school is selected and its name is printed, a series of if/elif/else statements appears that handles each possibility. Notice that the else statement is left in as an error handler; you should not run into that line if your code works properly, but you can leave the line in there to fail gracefully if something goes wrong.
import random
# Choose a random school from a list and print it
schools = ["Penn State", "Michigan", "Ohio State", "Indiana"]
randomSchoolIndex = random.randrange(0,4)
chosenSchool = schools[randomSchoolIndex]
print (chosenSchool)
# Depending on the school, print the mascot
if chosenSchool == "Penn State":
print ("You're a Nittany Lion")
elif chosenSchool == "Michigan":
print ("You're a Wolverine")
elif chosenSchool == "Ohio State":
print ("You're a Buckeye")
elif chosenSchool == "Indiana":
print ("You're a Hoosier")
else:
print ("This program has an error")Another way to handle the conditional logic above is to employ Python's match/case approach. To try this approach, replace the if block in the bottom half of the script with the snippet below:
# Depending on the school, print the mascot
match chosenSchool:
case "Penn State":
print ("You're a Nittany Lion")
case "Michigan":
print ("You're a Wolverine")
case "Ohio State":
print ("You're a Buckeye")
case "Indiana":
print ("You're a Hoosier")
case _:
print ("This program has an error")2.1.4 String manipulation
2.1.4 String manipulation jed124You've previously learned how the string variable can contain numbers and letters and represent almost anything. When using Python with ArcGIS, strings can be useful for storing paths to data and printing messages to the user. There are also some geoprocessing tool parameters that you'll need to supply with strings.
Python has some very useful string manipulation abilities. We won't get into all of them in this course, but the following are a few techniques that you need to know.
Concatenating strings
To concatenate two strings means to append or add one string on to the end of another. For example, you could concatenate the strings "Python is " and "a scripting language" to make the complete sentence "Python is a scripting language." Since you are adding one string to another, it's intuitive that in Python you can use the + sign to concatenate strings.
You may need to concatenate strings when working with path names. Sometimes it's helpful or required to store one string representing the folder or geodatabase from which you're pulling datasets and a second string representing the dataset itself. You put both together to make a full path.
The following example, modified from one in the ArcGIS Help, demonstrates this concept. Suppose you already have a list of strings representing feature classes that you want to clip. The list is represented by "featureClassList" in this script:
# This script clips all datasets in a folder
import arcpy
inFolder = "c:\\data\\inputShapefiles\\"
resultsFolder = "c:\\data\\results\\"
clipFeature = "c:\\data\\states\\Nebraska.shp"
# List feature classes
arcpy.env.workspace = inFolder
featureClassList = arcpy.ListFeatureClasses()
# Loop through each feature class and clip
for featureClass in featureClassList:
# Make the output path by concatenating strings
outputPath = resultsFolder + featureClass
# Clip the feature class
arcpy.Clip_analysis(featureClass, clipFeature, outputPath)
String concatenation is occurring in this line: outputPath = resultsFolder + featureClass. In longhand, the output folder "c:\\data\\results\\" is getting the feature class name added on the end. If the feature class name were "Roads.shp" the resulting output string would be "c:\\data\\results\\Roads.shp".
The above example shows that string concatenation can be useful in looping. Constructing the output path by using a set workspace or folder name followed by a feature class name from a list gives much more flexibility than trying to create output path strings for each dataset individually. You may not know how many feature classes are in the list or what their names are. You can get around that if you construct the output paths on the fly through string concatenation.
Casting to a string
Sometimes in programming, you have a variable of one type that needs to be treated as another type. For example, 5 can be represented as a number or as a string. Python can only perform math on 5 if it is treated as a number, and it can only concatenate 5 onto an existing string if it is treated as a string.
Casting is a way of forcing your program to think of a variable as a different type. Create a new script in PyScripter, and type or paste the following code:
x = 0
while x < 10:
print (x)
x += 1
print ("You ran the loop " + x + " times.")
Now, try to run it. The script attempts to concatenate strings with the variable x to print how many times you ran a loop, but it results in an error: "TypeError: must be str not int." Python doesn't have a problem when you want to print the variable x on its own, but Python cannot mix strings and integer variables in a printed statement. To get the code to work, you have to cast the variable x to a string when you try to print it.
x = 0
while x < 10:
print (x)
x += 1
print ("You ran the loop " + str(x) + " times.")
You can force Python to think of x as a string by using str(x). Python has other casting functions such as int() and float() that you can use if you need to go from a string to a number. Use int() for integers and float() for decimals.
Readings
It's time to take a break and do some readings from another source. If you are new to Python scripting, this will help you see the concepts from a second angle.
Finish reading Zandbergen chapters 4 - 6 as detailed below. This can take a few hours, but it will save you hours of time if you make sure you understand this material now.
ArcGIS Pro edition:
- Chapter 4 covers the basics of Python syntax, loops, strings, and other things we just learned. Please read Chapter 4, noting the following:
- You were already assigned 4.1-4.7 in Lesson 1, so you may skip those sections if you have a good handle on those topics.
- You may also skip 4.17-4.18, and 4.26.
- Section 4.25 demonstrates use of the input() function, using the IDLE and PyCharm IDEs. IDEs vary in their implementation of the type() function. In PyScripter, the user is expected to enter a value in the PyScripter Python Interpreter.
- Another way of providing input to a script in PyScripter is by going to Run > Command Line Parameters, checking the Use Command line Parameters box, entering the arguments in the box that appears, then clicking Run. - You've already read most of Chapter 5 on geoprocessing with arcpy. Now please read 5.10 & 5.14.
- Chapter 6 gives some specific instructions about working with ArcGIS datasets, which will be valuable during this week's assigned project. Please read all sections, 6.1-6.7.
If you still don't feel like you understand the material after reading the above chapters, don't re-read it just yet. Try some coding from the Lesson 2 practice exercises and assignments, then come back and re-read if necessary. If you are really struggling with a particular concept, type the examples in the console. Programming is like a sport in the sense that you cannot learn all about it by reading; at some point, you have to get up and do it.
2.1.5 Putting it all together
2.1.5 Putting it all together jed124Throughout this lesson, you've learned the basic programming concepts of lists, loops, decision structures, and string manipulation. In this section, we'll practice putting them all together to address a scenario. This will give us an opportunity to talk about strategies for approaching programming problems in general and you might be surprised at what you can do with just these skills.
The scenario we'll tackle is to simulate a one-player game of Hasbro's children's game "Hi Ho! Cherry-O." In this simple game of chance, you begin with 10 cherries on a tree. You take a turn by spinning a random spinner, which tells you whether you get to add or remove cherries on the turn. The possible spinner results are:
- Remove 1 cherry.
- Remove 2 cherries.
- Remove 3 cherries.
- Remove 4 cherries.
- Bird visits your cherry bucket (Add 2 cherries).
- Dog visits your cherry bucket (Add 2 cherries).
- Spilled bucket (Place all 10 cherries back on your tree).
You continue taking turns until you have 0 cherries left on your tree, at which point you have won the game. Your objective here is to write a script that simulates the game, printing the following:
- The result of each spin.
- The number of cherries on your tree after each turn. This must always be between 0 and 10;
- The final number of turns needed to win the game.
Approaching a programming problem
Although this example may seem juvenile, it's an excellent way to practice everything you just learned. As a beginner, you may seem overwhelmed by the above problem. A common question is, "Where do I start?" The best approach is to break down the problem into smaller chunks of things you know how to do.
One of the most important programming skills you can acquire is the ability to verbalize a problem and translate it into a series of small programming steps. Here's a list of things you would need to do in this script. Programmers call this pseudocode because it's not written in code, but it follows the sequence their code will need to take.
- Spin the spinner.
- Print the spin result.
- Add or remove cherries based on the result.
- Make sure the number of cherries is between 0 and 10.
- Print the number of cherries on the tree.
- Take another turn, or print the number of turns it took to win the game.
It also helps to list the variables you'll need to keep track of:
- Number of cherries currently on the tree (Starts at 10).
- Number of turns taken (Starts at 0).
- Value of the spinner (Random).
Let's try to address each of the pseudocode steps. Don't worry about the full flow of the script yet. Rather, try to understand how each step of the problem should be solved with code. Assembling the blocks of code at the end is relatively trivial.
Spin the spinner
How do you simulate a random spin? In one of our previous examples, we used the random module to generate a random number within a range of integers; however, the choices on this spinner are not linear. A good approach here is to store all spin possibilities in a list and use the random number generator to pick the index for one of the possibilities. On its own, the code would look like this:
import random
spinnerChoices = [-1, -2, -3, -4, 2, 2, 10]
spinIndex = random.randrange(0, 7)
spinResult = spinnerChoices[spinIndex]The list spinnerChoices holds all possible mathematical results of a spin (remove 1 cherry, remove 2 cherries, etc.). The final value 10 represents the spilled bucket (putting all cherries back on the tree).
You need to pick one random value out of this list to simulate a spin. The variable spinIndex represents a random integer from 0 to 6 that is the index of the item you'll pull out of the list. For example, if spinIndex turns out to be 2, your spin is -3 (remove 3 cherries from the tree). The spin is held in the variable spinResult.
The random.randrange() method is used to pick the random numbers. At the beginning of your script, you have to import the random module in order to use this method.
Print the spin result
Once you have a spin result, it only takes one line of code to print it. You'll have to use the str() method to cast it to a string, though.
print ("You spun " + str(spinResult) + ".")Add or remove cherries based on the result
As mentioned above, you need to have some variable to keep track of the number of cherries on your tree. This is one of those variables that it helps to name intuitively:
cherriesOnTree = 10
After you complete a spin, you need to modify this variable based on the result. Remember that the result is held in the variable spinResult, and that a negative spinResult removes cherries from your tree. So your code to modify the number of cherries on the tree would look like:
cherriesOnTree += spinResult
Remember, the above is shorthand for saying cherriesOnTree = cherriesOnTree + spinResult.
Make sure the number of cherries is between 0 and 10
If you win the game, you have 0 cherries. You don't have to reach 0 exactly, but it doesn't make sense to say that you have negative cherries. Similarly, you might spin the spilled bucket, which for simplicity we represented with positive 10 in the spinnerChoices. You are not allowed to have more than 10 cherries on the tree.
A simple if/elif decision structure can help you keep the cherriesOnTree within 0 and 10:
if cherriesOnTree > 10:
cherriesOnTree = 10
elif cherriesOnTree < 0:
cherriesOnTree = 0This means, if you wound up with more than 10 cherries on the tree, set cherriesOnTree back to 10. If you wound up with fewer than 0 cherries, set cherriesOnTree to 0.
Print the number of cherries on the tree
All you have to do for this step is to print your cherriesOnTree variable, casting it to a string, so it can legally be inserted into a sentence.
print ("You have " + str(cherriesOnTree) + "cherries on your tree.")Take another turn or print the number of turns it took to win the game
You probably anticipated that you would have to figure out a way to take multiple turns. This is the perfect scenario for a loop.
What is the loop condition? There have to be some cherries left on the tree in order to start another turn, so you could begin the loop this way:
while cherriesOnTree > 0:
Much of the code we wrote above would go inside the loop to simulate a turn. Since we need to keep track of the number of turns taken, at the end of the loop we need to increment a counter:
turns += 1
This turns variable would have to be initialized at the beginning of the script, before the loop.
This code could print the number of turns at the end of the game:
print ("It took you " + str(turns) + "turns to win the game.")Final code
Your only remaining task is to assemble the above pieces of code into a script. Below is an example of how the final script would look. Copy this into a new PyScripter script and try to run it:
# Simulates one game of Hi Ho! Cherry-O
import random
spinnerChoices = [-1, -2, -3, -4, 2, 2, 10]
turns = 0
cherriesOnTree = 10
# Take a turn as long as you have more than 0 cherries
while cherriesOnTree > 0:
# Spin the spinner
spinIndex = random.randrange(0, 7)
spinResult = spinnerChoices[spinIndex]
# Print the spin result
print ("You spun " + str(spinResult) + ".")
# Add or remove cherries based on the result
cherriesOnTree += spinResult
# Make sure the number of cherries is between 0 and 10
if cherriesOnTree > 10:
cherriesOnTree = 10
elif cherriesOnTree < 0:
cherriesOnTree = 0
# Print the number of cherries on the tree
print ("You have " + str(cherriesOnTree) + " cherries on your tree.")
turns += 1
# Print the number of turns it took to win the game
print ("It took you " + str(turns) + " turns to win the game.")
lastline = input(">")Analysis of the final code
Review the final code closely and consider the following things.
The first thing you do is import whatever supporting modules you need; in this case, it's the random module.
Next, you declare the variables that you'll use throughout the script. Each variable has a scope, which determines how broadly it is used throughout the script. The variables spinnerChoices, turns, and cherriesOnTree are needed through the entire script, so they are declared at the beginning, outside the loop. Variables used throughout your entire program like this have global scope. On the other hand, the variables spinIndex and spinResult have local scope because they are used only inside the loop. Each time the loop runs, these variables are re-initialized and their values change.
You could potentially declare the variable spinnerChoices inside the loop and get the same end result, but performance would be slower because the variable would have to be re-initialized every time you ran the loop. When possible, you should declare variables outside loops for this reason.
If you had declared the variables turns or cherriesOnTree inside the loop, your code would have logical errors. You would essentially be starting the game anew on every turn with 10 cherries on your tree, having taken 0 turns. In fact, you would create an infinite loop because there is no way to remove 10 cherries during one turn, and the loop condition would always evaluate to true. Again, be very careful about where you declare your variables when the script contains loops.
Notice that the total number of turns is printed outside the loop once the game has ended. The final line lastline = input(">") gives you an empty cursor prompting for input and is just a trick to make sure the application doesn't disappear when it's finished (if you run the script from a command console).
Summary
In the above example, you saw how lists, loops, decision structures, and variable casting can work together to help you solve a programming challenge. You also learned how to approach a problem one piece at a time and assemble those pieces into a working script. You'll have a chance to practice these concepts on your own during this week's assignment. The next and final section of this lesson will provide you with some sources of help if you get stuck.
Challenge activity
If the above activity made you enthusiastic about writing some code yourself, take the above script and try to find the average number of turns it takes to win a game of Hi-Ho! Cherry-O. To do this, add another loop that runs the game a large number of times, say 10000. You'll need to record the total number of turns required to win all the games, then divide by the number of games (use "/" for the division). Send me your final result, and I'll let you know if you've found the correct average.
2.2 Troubleshooting and getting help
2.2 Troubleshooting and getting help jed124If you find writing code to be a slow and sometimes confusing process, that’s a normal part of programming. A large portion of a programmer’s time is spent identifying and resolving bugs, and learning to work with new languages and technologies is an ongoing process that involves research, practice, and experimentation.
Success in software development is less about how many languages or acronyms you can list on a resume, and more about being self-sufficient: the ability to learn new tools and solve problems independently. This doesn’t mean programmers never ask for help. On the contrary, good programmers know when it’s appropriate to consult peers or supervisors. At the same time, many common challenges can be addressed using documentation, online examples, forums, existing code, programming books, or debugging tools. Relying on generative AI could also be detrimental since it is plagued with errors that could do damage to company data. For instance, if an interviewer asks, “What do you do when you run into a difficult programming problem? What resources do you turn to first?” a strong answer demonstrates that you can efficiently find solutions on your own. Many effective programmers start with well-phrased searches online. Resources like Stack Exchange or other forums often provide solutions to common questions quickly, which are sometimes faster than walking down the hall to ask a colleague. For more complex problems, collaboration is essential, but the ability to independently explore and solve routine challenges is a valuable skill that helps the team work efficiently.
In this section of the lesson, you'll learn about places where you can go for help when working with Python and when programming in general. You will have a much easier experience in this course if you remember these resources and use them as you complete your assignments.
2.2.1 Potential problems and quick diagnosis
2.2.1 Potential problems and quick diagnosis jed124The secret to successful programming is to run early, run often, and don't be afraid of things going wrong when you run your code the first time. Debugging, or finding mistakes in code, is a part of life for programmers. Here are some things that can happen:
- Your code doesn't run at all, usually because of a syntax error (you typed some illegal Python code).
- Your code runs, but the script doesn't complete and reports an error.
- Your code runs, but the script never completes. Often this occurs when you've created an infinite loop.
- Your code runs and the script completes, but it doesn't give you the expected result. This is called a logical error, and it is often the type of error that takes the most effort to debug.
Don't be afraid of errors
Errors happen. There are very few programmers who can sit down and, off the top of their heads, write dozens of lines of bug free code. This means a couple of things for you:
- Expect to spend some time dealing with errors during the script-writing process. Beginning programmers sometimes underestimate how much time this takes. To get an initial estimate, you can take the amount of time it takes to draft your lines of code, then double it or triple it to accommodate for error handling and final polishing of your script and tool.
- Don't be afraid to run your script and hit the errors. A good strategy is to write a small piece of functionality, run it to make sure it's working, then add on the next piece. For the sake of discussion, let's suppose that as a new programmer, you introduce a new bug once every 10 lines of code. It's much easier to find a single bug in 10 new lines of code than it is to find 5 bugs in 50 new lines of code. If you're building your script piece by piece and debugging often, you'll be able to make a quicker and more accurate assessment of where you introduced new errors.
Catching syntax errors
Syntax errors occur when you typed something incorrectly and your code refuses to run. Common syntax errors include forgetting a colon when setting a loop or an if condition, using single backslashes in a file name, providing the wrong number of arguments to a function, or trying to mix variable types incorrectly, such as dividing a number by a string.
When you try to run code with a syntax error in PyScripter, an error message will appear in the Python Interpreter, referring to the script file that was run, along with the line number that contained the error. For example, a developer transitioning from ArcMap to ArcGIS Pro might forget that the syntax of the print function is different, resulting in the error message, "SyntaxError: Missing parentheses in call to 'print'. Did you mean print(x)?"
Dealing with crashes
If your code crashes, you may see an error message in the Python Interpreter. Instead of allowing your eyes to glaze over or banging your head against the desk, you should rejoice at the fact that the software possibly reported to you exactly what went wrong! Scour the message for clues as to what line of code caused the error and what the problem was. Do this even if the message looks intimidating. For example, see if you can understand what caused this error message (as reported by the Spyder IDE):
runfile('C:/Users/detwiler/Documents/geog485/Lesson2/syntax_error_practice.py', wdir='C:/Users/detwiler/Documents/geog485/Lesson2')
Traceback (most recent call last):
File ~\AppData\Local\ESRI\conda\envs\arcgispro-py3-spyd\lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec
exec(code, globals, locals)
File c:\users\detwiler\documents\geog485\lesson2\syntax_error_practice.py:5
x = x / 0
ZeroDivisionError: division by zero
The message begins with a call to Python's runfile() function, which Spyder shows when the script executes successfully as well. Because there's an error in this example script, the runfile() bit is then followed by a "traceback," a report on the origin of the error. The first few lines refer to the internal Python modules that encountered the problem, which in most cases you can safely ignore. The part of the traceback you should focus on, the part that refers to your script file, is at the end; in this case, we're told that the error was caused in Line 5: x = x / 0. Dividing by 0 is not possible, and the computer won't try to do it. (PyScripter's traceback report for this same script lists only your script file, leaving out the internal Python modules, so you may find it an easier environment for locating errors.)
Error messages are not always as easy to decipher as in this case, unfortunately. There are many online forums where you might go looking for help with a broken script (Esri's GeoNet for ArcGIS-specific questions; StackOverflow, StackExchange, Quora for more generic questions). You can make it a lot easier for someone to help you if, rather than just saying something like, "I'm getting an error when I run my script. Can anyone see what I did wrong," you include the line number flagged by PyScripter along with the exact text of the error message. The exact text is important because the people trying to help you are likely to plug it into a search engine and will get better results that way. Or better yet, you could search the error message yourself! The ability to solve coding problems through the reading of documentation and searching online forums is one of the distinguishing characteristics of a good developer.
Ad-hoc debugging
Sometimes it's easy to sprinkle a few 'print' statements throughout your code to figure out how far it got before it crashed, or what's happening to certain values in your script as it runs. This can also be helpful to verify that your loops are doing what you expect and that you are avoiding off-by-one errors.
Suppose you are trying to find the mean (average) value of the items in a list with the code below.
#Find average of items in a list
list = [22,343,73,464,90]
for item in list:
total = 0
total += item
average = total / len(list)
print ("Average is " + str(average))
The script reports "Average is 18," which doesn't look right. From a quick visual check of this list you could guess that the average would be over 100. The script isn't erroneously getting the number 18 from the list; it's not one of the values. So where is it coming from? You can place a few strategic print statements in the script to get a better report of what's going on:
#Find average of items in a list
list = [22,343,73,464,90]
for item in list:
print ("Processing loop...")
total = 0
total += item
print (total)
print (len(list))
average = total / len(list)
print ("Performing division...")
print ("Average is " + str(average))
Now when you run the script you see.
Processing loop... 22 Processing loop... 343 Processing loop... 73 Processing loop... 464 Processing loop... 90 5 Performing division... Average is 18
The error now becomes more clear. The running total isn't being kept successfully; instead, it's resetting each time the loop runs. This causes the last value, 90, to be divided by 5, yielding an answer of 18. You need to initialize the variable for the total outside the loop to prevent this from happening. After fixing the code and removing the print statements, you get:
#Find average of items in a list
list = [22,343,73,464,90]
total = 0
for item in list:
total += item
average = total / len(list)
print ("Average is " + str(average))
The resulting "Average is 198" looks a lot better. You've fixed a logical error in your code: an error that doesn't make your script crash, but produces the wrong result.
Although debugging with print statements is quick and easy, you need to be careful with it. Once you've fixed your code, you need to remember to remove the statements in order to make your code faster and less cluttered. Also, adding print statements becomes impractical for long or complex scripts. You can pinpoint problems more quickly and keep track of many variables at a time using the PyScripter debugger, which is covered in the next section of this lesson.
2.2.2 Using the PyScripter debugger
2.2.2 Using the PyScripter debugger jed124Sometimes when other quick attempts at debugging fail, you need a way to take a deeper look into your script. Most integrated development environments (IDEs) like Pyscripter include some debugging tools that allow you to step through your script line-by-line to attempt to find an error. These tools allow you to keep an eye on the value of all variables in your script to see how they react to each line of code. The Debug toolbar can be a good way to catch logical errors where an offending line of code is preventing your script from returning the correct outcome. The Debug toolbar can also help you find which line of code is causing a crash.
The best way to explain the aspects of debugging is to work through an example. This time, we'll look at some code that tries to calculate the factorial of an integer (the integer is hard-coded to 5 in this case). In mathematics, a factorial is the product of an integer and all positive integers below it. Thus, 5! (or "5 factorial") should be 5 * 4 * 3 * 2 * 1 = 120.
The code below attempts to calculate a factorial through a loop that increments the multiplier by 1 until it reaches the original integer. This is a valid approach since 1 * 2 * 3 * 4 * 5 would also yield 120.
# This script calculates the factorial of a given
# integer, which is the product of the integer and
# all positive integers below it.
number = 5
multiplier = 1
while multiplier < number:
number *= multiplier
multiplier += 1
print (number)Even if you can spot the error, follow along with the steps below to get a feel for the debugging process and the PyScripter Debug toolbar.
Open PyScripter and copy the above code into a new script.
- Save your script as debugger_walkthrough.py. You can optionally run the script, but you won't get a result.
- Click View > Toolbars and ensure Debug is checked. You should see a toolbar like this:
Many IDEs have debugging toolbars like this, and the tools they contain are pretty standard: a way to run the code, a way to set breakpoints, a way to step through the code line by line, and a way to watch the value of variables while stepping through the code. We'll cover each of these in the steps below. - Move your cursor to the left of line 5 (number = 5) and click. If you are in the right area, you will see a red dot next to the line number, indicating the addition of a breakpoint. A breakpoint is a place where you want your code to stop running, so you can examine it line by line using the debugger. Often you'll set a breakpoint deep in the middle of your script, so you don't have to examine every single line of code. In this example, the script is very short, so we're putting the breakpoint right at the beginning. The breakpoint is represented by a circle next to the line of code, and this is common in other debuggers too.
Note that F5 is the shortcut key for this command. - Press the Debug file
button . This runs your script up to the breakpoint. In the Python Interpreter console, note that the debugfile() function is run on your script rather than the normal runfile() function. Also, instead of the normal >>> prompt, you should now see a [Dbg]>>> prompt. The cursor will be on that same line in PyScripter's Editor pane, which causes that line to be highlighted. - Click the Step over next function call button
or the Step into subroutine
button. This executes the line of your code, in this case the number = 5 line. Both buttons execute the highlighted statement, but it is important to note here that they will behave differently when the statement includes a call to a function. The Step over next function call button will execute all the code within the function, return to your script, and then pause at the script's next line. You'd use this button when you're not interested in debugging the function code, just the code of your main script. The Step into subroutine button, on the other hand, is used when you do want to step through the function code one line at a time. The two buttons will produce the same behavior for this simple script. You'll want to experiment with them later in the course when we discuss writing our own functions and modules. - Before going further, click the Variable window tab in PyScripter's lower pane. Here, you can track what happens to your variables as you execute the code line by line. The variables will be added automatically as they are encountered. At this point, you should see a globals {}, which contain variables from the python packages and locals {} that will contain variables created by your script. We will be looking at the locals dictionary so you can disregard the globals. Expanding the locals dictionary, you should see some are built in variables (__<name>__) and we can ignore those for now. The "number" variable should be listed, with a type of int. Expanding on the +, will expose more of the variables properties.
- Click the Step button again. You should now see the "multiplier" variable has been added in the Variable window, since you just executed the line that initializes that variable, as called out in the image.

Click the Step button a few more times to cycle through the loop. Go slowly, and use the Variable window to understand the effect that each line has on the two variables. (Note that the keyboard shortcut for the Step button is F8, which you may find easier to use than clicking on the GUI.) Setting a watch on a variable is done by placing the cursor on the variable and then pressing Alt+W or right clicking in the Watches pane and selecting Add Watch At Cursor. This isolates the variable to the Watch window and you can watch the value change as it changes in the code execution.

Step through the loop until "multiplier" reaches a value of 10. It should be obvious at this point that the loop has not exited at the desired point. Our intent was for it to quit when "number" reached 120.
Can you spot the error now? The fact that the loop has failed to exit should draw your attention to the loop condition. The loop will only exit when "multiplier" is greater than or equal to "number." That is obviously never going to happen as "number" keeps getting bigger and bigger as it is multiplied each time through the loop.
In this example, the code contained a logical error. It re-used the variable for which we wanted to find the factorial (5) as a variable in the loop condition, without considering that the number would be repeatedly increased within the loop. Changing the loop condition to the following would cause the script to work:
while multiplier < 5:
Even better than hard-coding the value 5 in this line would be to initialize a variable early and set it equal to the number whose factorial we want to find. The number could then get multiplied independent of the loop condition variable.
- Click the Stop button in the Debug toolbar to end the debugging session. We're now going to step through a corrected version of the factorial script, but you may notice that the Variable window still displays a list of the variables and their values from the point at which you stopped executing. That's not necessarily a problem, but it is good to keep in mind.
Open a new script, paste in the code below, and save the script as debugger_walkthrough2.py
# This script calculates the factorial of a given # integer, which is the product of the integer and # all positive integers below it. number = 5 loopStop = number multiplier = 1 while multiplier < loopStop: number *= multiplier multiplier += 1 print (number)- Step through the loop a few times as you did above. Watch the values of the "number" and "multiplier" variables, but also the new "loopStop" variable. This variable allows the loop condition to remain constant while "number" is multiplied. Indeed, you should see "loopStop" remain fixed at 5 while "number" increases to 120.
- Keep stepping until you've finished the entire script. Note that the usual >>> prompt returns to indicate you've left debugging mode.
In the above example, you used the Debug toolbar to find a logical error that had caused an endless loop in your code. Debugging tools are often your best resource for hunting down subtle errors in your code.
You can and should practice using the Debug toolbar in the script-writing assignments that you receive in this course. You may save a lot of time this way. As a teaching assistant in a university programming lab years ago, the author of this course saw many students wait a long time to get one-on-one help, when a simple walk through their code using the debugger would have revealed the problem.
Readings
Read Zandbergen 7.1 - 7.5to get his tips for debugging. Then read 7.11 and dog-ear the section on debugging as a checklist for you to review any time you hit a problem in your code during the next few weeks. The text doesn't focus solely on PyScripter's debugging tools, but you should be able to follow along and compare the tools you're reading about to what you encountered in PyScripter during the short exercise above. It will also be good for you to see how this important aspect of script development is handled in other IDEs.
2.2.3 Printing messages from the Esri geoprocessing framework
2.2.3 Printing messages from the Esri geoprocessing framework jed124When you work with geoprocessing tools in Python, sometimes a script will fail because something went wrong with the tool. It could be that you wrote flawless Python syntax, but your script doesn't work as expected because Esri's geoprocessing tools cannot find a dataset or otherwise digest a tool parameter. You won't be able to catch these errors with the debugger, but you can get a view into them by printing the messages returned from the Esri geoprocessing framework.
Esri has configured its geoprocessing tools to frequently report what they're doing. When you run a geoprocessing tool from ArcGIS Pro, you see a box with these messages, sometimes accompanied by a progress bar. You learned in Lesson 1 that you can use arcpy.GetMessages() to access these messages from your script. If you only want to view the messages when something goes wrong, you can include them in an except block of code, like this.
try:
. . .
except:
print (arcpy.GetMessages())Remember that when using try/except, in the normal case, Python will execute everything in the try-block (= everything following the "try:" that is indented relative to it) and then continue after the except-block (= everything following the "except:" that is indented relative to it). However, if some command in the try-block fails, the program execution directly jumps to the beginning of the except-block and, in this case, prints out the messages we get from arcpy.GetMessages(). After the except-block has been executed, Python will continue with the next statement after the except-block.
Geoprocessing messages have three levels of severity: Message, Warning, and Error. You can pass an index to the arcpy.GetMessages() method to filter through only the messages that reach a certain level of severity. For example, arcpy.GetMessages(2) would return only the messages with a severity of "Error". Error and warning messages sometimes include a unique code that you can use to look up more information about the message. The ArcGIS Pro Help contains topics that list the message codes and provide details on each. Some of the entries have tips for fixing the problem.
Try/except can be used in many ways and at different indentation levels in a script. For instance, you can have a single try/except construct as in the example above, where the try-block contains the entire functionality of your program. If something goes wrong, the script will output some error message and then terminate. In other situations, you may want to split the main code into different parts, each contained by its own try/except construct to deal with the particular problems that may occur in this part of the code. For instance, the following structure may be used in a batch script that performs two different geoprocessing operations on a set of shapefiles when an attempt should be made to perform the second operation, even if the first one fails.
for featureClass in fcList:
try:
. . . # perform geoprocessing operation 1
except:
. . . # deal with failure of geoprocessing operation 1
try:
. . . # perform geoprocessing operation 2
except:
. . . # deal with failure of geoprocessing operation 2
Let us assume, the first geoprocessing operation fails for the first shapefile in fcList. As a result, the program execution will jump to the first except-block which contains the code for dealing with this error situation. In the simplest case, it will just produce some output messages. After the except-block has been executed, the program execution will continue as normal by moving on to the second try/except statement and attempt to perform the second geoprocessing operation. Either this one succeeds or it fails too, in which case the second except-block will be executed. The last thing to note is that since both try/except statements are contained in the body of the for-loop going through the different shapefiles, even if both of the operations fail for one of the files, the script will always jump back to the beginning of the loop body and continue with the next shapefile in the list which is often the desired behavior of a batch script.
It is important to note that arcpy.GetMessages() will only get messages from the arcpy geoprocessing processes and not exceptions raised by other packages such as os or pandas. A general catchall construct is to use the general Exception, which will also get the exceptions raised in arcpy.
try:
. . .
except Exception as ex:
print (ex)Further reading
Please take a look at the official ArcGIS Pro documentation for more detail about geoprocessing messages. Be sure to read these topics:
- Understanding message types and severity
- Understanding geoprocessing tool errors and warnings - This is the gateway into the error and warning reference section of the help that explains all the error codes. Sometimes you'll see these codes in the messages you get back, and the specific help topic for the code can help you understand what went wrong. The article also talks about how you can trap for certain conditions in your own scripts and cause specific error codes to appear. This type of thing is optional, for over and above credit, in this course.
2.2.4 Other sources of help
2.2.4 Other sources of help jed124Besides the above approaches, there are many other places you can get help. A few of them are described below. If you're new to programming, just knowing that these resources exist and how to use them can help you feel more confident. Find the ones that you prefer and return to them often. This habit will help you become a self-sufficient programmer and will improve your potential to learn any new programming language or technology.
Drawing on the resources below takes time and effort. Many people don't like combing through computer documentation, and this is understandable. However, you may ultimately save time if you look up the answer for yourself instead of waiting for someone to help you. Even better, you will have learned something new from your own experience, and things you learn this way are much easier to remember in the future.
Sources of help
Search engines
Search engines are useful for both quick answers and obscure problems. Did you forget the syntax for a loop? The quickest remedy may be to Google "for loop python" or "while loop python" and examine one of the many code examples returned. Search engines are extremely useful for diagnosing error messages. Google the error message in quotes, and you can read experiences from others who have had the same issue. If you don't get enough hits, remove the quotes to broaden the search.
One risk you run from online searches is finding irrelevant information. Even more dangerous is using irrelevant information. Research any sample code to make sure it is applicable to the version of Python you're using. Some syntax in Python 3.x, used for scripting in ArcGIS Pro, is different from the Python 2.x used for scripting in ArcMap, for example.
Esri online help
Esri maintains their entire help system online, and you'll find most of their scripting topics in the arcpy section.
Another section, which you should visit repeatedly, is the Tool Reference, which describes every tool in the toolbox and contains Python scripting examples for each. If you're having trouble understanding what parameters go in or out of a tool, or if you're getting an error back from the geoprocessing framework itself, try the Tool Reference before you do a random Internet search. You will have to visit the Tool Reference in order to be successful in some of the course projects and quizzes.
Python online help
The official Python documentation is available online. Some of it gets very detailed and takes the tone of being written by programmers for programmers. The part you'll probably find most helpful is the Python Standard Library reference, which is a good place to learn about Python's modules such as "os", "csv", "math," or "random."
Generative AI
Caution should be used when using Generative AI tools. AI responses may be sourced from earlier versions of documentation and you may get stuck in an unhelpful loop because it often lacks context. If you do not fully understand the code generated by the tool, you could be introducing malware or cause irreparable damage to your company's data and hardware.
Printed books, including your textbook
Programming books can be very hit or miss. Many books are written for people who have already programmed in other languages. Others proclaim they're aimed at beginners, but the writing or design of the book may not be intuitive or is difficult to digest. Before you drop $40 on a book, try to skim through it yourself to see if the writing generally makes sense to you (don't worry about not understanding the code--that will come along as you work through the book).
The course text Python Scripting for ArcGIS is a generally well-written introduction to just what the title says: working with ArcGIS using Python. There are a few other Python+ArcGIS books as well. If you've struggled with the material, or if you want to do a lot of scripting in the future, I may recommend picking up one of these. Your textbook can come in handy if you need to look at a very basic code example, or if you're going to use a certain type of code construct for the first time, and you want to review the basics before you write anything.
A good general Python reference is Learning Python by Mark Lutz. We previously used this text in Geog 485 before there was a book about scripting with ArcGIS. It covers beginning to advanced topics, so don't worry if some parts of it look intimidating.
Esri forums and other online forums
The Esri forums are a place where you can pose your question to other Esri software users, or read about issues other users have encountered that may be similar to yours. There is a Python Esri forum that relates to scripting with ArcGIS, and also a more general Geoprocessing Esri forum you might find useful.
Before you post a question on the Esri forums, do a little research to make sure the question hasn't been answered already, at least recently. I also suggest that you post the question to our class forums first, since your peers are working on the same problems, and you are more likely to find someone who's familiar with your situation and has found a solution.
There are many other online forums that address GIS or programming questions. You'll see them all over the Internet if you perform a Google search on how to do something in Python. Some of these sites are laden with annoying banner ads or require logins, while others are more immediately helpful. Stack Exchange is an example of a well-traveled technical forum, light on ads, that allows readers to promote or demote answers depending on their helpfulness. One of its child sites, GIS Stack Exchange, specifically addresses GIS and cartography issues.
If you do post to online forums, be sure to provide detailed information on the problem and list what you've tried already. Avoid posts such as "Here's some code that's broken, and I don't know why" followed by dozens of lines of pasted-in code. State the problem in a general sense and focus on the problem code. Include exact error messages when possible.
People on online forums are generally helpful, but expect a hostile reception if you make them feel like they are doing your academic homework for you. Also, be aware that posting or copying extensive sections of Geog 485 assignment code on the internet is a violation of academic integrity and may result in a penalty applied to your grade (see section on Academic Integrity in the course syllabus).
Class forums
Our course has discussion boards that we recommend you use to consult your peers and instructor about any Python problem that you encounter. I encourage you to check them often and to participate by both asking and answering questions. I request that you make your questions focused and avoid pasting large blocks of code that would rob someone of the benefit of completing the assignment on their own. Short, focused blocks of code that solve a specific question are definitely okay. Code blocks that are not copied directly from your assignment are also okay.
I monitor all discussion boards closely; however, sometimes I may not respond immediately because I want to give you a chance to help each other and work through problems together. If you post a question and wind up solving your own problem, please post again to let us know and include how you managed to solve the problem in case other students run into the same issue.
Lesson 2 Practice Exercises
Lesson 2 Practice Exercises jed124Before trying to tackle Project 2, you may want to try some simple practice exercises, particularly if the concepts in this lesson are new to you. Remember to choose File > New in PyScripter to create a new script (or click the empty page icon). You can name the scripts something like Practice1, Practice2, etc.
Find the spaces in a list of names
Python String objects have an index method that enables you to find a substring within the larger string. For example, if I had a variable defined as name = "James Franklin" and followed that up with the expression name.index("Fr"), it would return the value 6 because the substring "Fr" begins at character 6 in the string held in name. (The first character in a string is at position 0.)
For this practice exercise, start by creating a list of names like the following:
beatles = ["John Lennon", "Paul McCartney", "Ringo Starr", "George Harrison"]
Then write code that will loop through all the items in the list, printing a message like the following:
"There is a space in blank's name at character (blank character)" where the first blank is filled in with the name currently being processed by the loop, and the second blank is filled in with the position of the first space in the name as returned by the index method. (You should obtain values of 4, 4, 5 and 6, respectively, for the items in the list above.)
This is a good example in which it is smart to write and test versions of the script that incrementally build toward the desired result rather than trying to write the final version in one fell swoop. For example, you might start by setting up a loop and simply printing each name. If you get that to work, give yourself a little pat on the back and then see if you can simply print the positions of the space. Once you get that working, then try plugging the name and space positions into the larger message.
- Practice 1 Solution
Solution:
beatles = ["John Lennon", "Paul McCartney", "Ringo Starr", "George Harrison"] for member in beatles: space = member.index(" ") print ("There is a space in " + member + "'s name at character " + str(space) + ".")Explanation:
Given the info on the index method in the instructions, the trick to this exercise is applying the method to each string in the list. To loop through all the items in a list, you use a for statement that ends with 'in <list variable>', which in this case was called beatles. Between the words 'for' and 'in', you insert a variable with a name of your choosing. That variable will store a different list item on each iteration through the loop.
In my solution, I called this loop variable member. The member variable takes on the value of "John Lennon" on the first pass through the loop, "Paul McCartney" on the second pass through the loop, etc. The position of the first space in the member variable is returned by the index method and stored in a variable called space.
The last step is to plug the group member's name and the space position into the output message. This is done using the string concatenation character (+). Note that the space variable must be converted to a string before it can be concatenated with the literal text surrounding it.
Convert the names to a "Last, First" format
Build on Exercise 1 by printing each name in the list in the following format:
Last, First
To do this, you'll need to find the position of the space just as before. To extract part of a string, you can specify the start character and the end character in brackets after the string's name, as in the following:
name = "James Franklin" print (name[6:14]) # prints Franklin
One quirky thing about this syntax is that you need to specify the end character as 1 beyond the one you really want. The final "n" in "Franklin" is really at position 13, but I needed to specify a value of 14.
One handy feature of the syntax is that you may omit the end character index if you want everything after the start character. Thus, name[6:] will return the same string as name[6:14] in this example. Likewise, the start character may be omitted to obtain everything from the beginning of the string to the specified end character.
- Practice 2 Solution
Solution:
beatles = ["John Lennon", "Paul McCartney", "Ringo Starr", "George Harrison"] for member in beatles: space = member.index(" ") last = member[space+1:] first = member[:space] print (last + ", " + first) # or doing away with the last and first variables # print (member[space+1:] + ", " + member[:space])Explanation:
Building on the first exercise, this script also uses a for loop to process each item in the beatles list. And the index method is again used to determine the position of spaces in the names. The script then obtains all characters after the space (member[space+1:]) and stores them in a variable called last. Likewise, it obtains all characters before the space (member[:space]) and stores them in a variable called first. Again, string concatenation is used to append the first name to the last name with a comma in between.
Convert scores to letter grades
Write a script that accepts a score from 1-100 as an input parameter, then reports the letter grade for that score. Assign letter grades as follows:
A: 90-100
B: 80-89
C: 70-79
D: 60-69
F: <60
- Practice 3 Solution
Solution:
import arcpy input = arcpy.GetParameterAsText(0) score = int(input) if score > 89: print ("A") elif score > 79: print ("B") elif score > 69: print ("C") elif score > 59: print ("D") else: print ("F")Explanation:
This exercise requires accepting an input parameter, so in my solution, I imported arcpy so that I could make use of the GetParameterAsText method. If you run this script directly in PyScripter, you can provide the input value for GetParameterAsText by going to Run > Configuration per file, checking the Command line options box, and entering a score in the Command line options text box. An alternative to using GetParameterAsText would be to use the Python function input() to read from the keyboard. Because the input parameter is being stored as a text string, I include an additional step that converts the value to an integer. While not critical in this instance, it's generally a good idea to avoid treating numeric values as strings when possible. For example, the number 10 treated as a string ("10") is considered less than 2 treated as a string ("2") because the first character in "10" is less than the first character in "2".
An 'if' block is used to print the appropriate grade for the inputted score. You may be wondering why all of the expressions handle only the lower end of the grade range and not the upper end. While it would certainly work to specify both ends of the range (e.g., 'elif score > 79 and score < 90:'), it's not really necessary if you evaluate the code as the interpreter would. If the user enters a value greater than 89, then the 'print "A"' statement will be executed and all of the other conditions associated with the if block will be ignored. Code execution would pick up with the first line after the if block. (In this case, there are no lines after the if block, so the script ends.)
What this means is that the 'elif score > 79' condition will only ever be reached if the score is not greater than 89. That makes it unnecessary to specify the upper end of the grade range. The same logic applies to the rest of the conditions in the block.
Create copies of a template shapefile
Imagine that you're again working with the Nebraska precipitation data from Lesson 1 and that you want to create copies of the Precip2008Readings shapefile for the next 4 years after 2008 (e.g., Precip2009Readings, Precip2010Readings, etc.). Essentially, you want to copy the attribute schema of the 2008 shapefile, but not the data points themselves. Those will be added later. The tool for automating this kind of operation is the Create Feature Class tool in the Data Management toolbox. Look up this tool in the Help system and examine its syntax and the example script. Note the optional template parameter, which allows you to specify a feature class whose attribute schema you want to copy. Also note that Esri uses some inconsistent casing with this tool, and you will have to call arcpy.CreateFeatureclass_management() using a lower-case "c" on "class." If you follow the examples in the Geoprocessing Tool Reference help, you will be fine.
To complete this exercise, you should invoke the Create Feature Class tool inside a loop that will cause the tool to be run once for each desired year. The range(...) function can be used to produce the list of years for your loop.
- Practice 4 Solution
Solution:
import arcpy try: arcpy.env.workspace = "C:\\Data\\" template = "Precip2008Readings.shp" for year in range(2009,2013): newfile = "Precip" + str(year) + "Readings.shp" arcpy.CreateFeatureclass_management(arcpy.env.workspace, newfile, "POINT", template, "DISABLED", "DISABLED", template) except: print (arcpy.GetMessages())Explanation:
This script begins by initializing the geoprocessing object, setting the workspace, and selecting the toolbox that holds the desired tool. A reference to the shapefile to be copied is then stored in a variable. Because the idea is to create a copy of that shapefile for each year from 2009-2012, a logical approach is to set up a for loop defined with bounds of 'in range(2009,2013)'. (Recall that the upper end of the range should be specified as 1 higher than the actual desired end point.)
Within the loop, the name of the new shapefile is put together by concatenating the number held in the loop variable with the parts before and after that are always the same. The CreateFeatureClass tool can then be called upon, supplying the desired workspace for the output feature class, its name, its geometry type, the feature class to use as the schema template, whether the feature class should be configured to store m and z values, and finally a spatial reference argument. Note from the documentation that the output feature class's spatial reference can be specified in a number of different ways and will be undefined if a spatial reference argument is not supplied. (It does not automatically take on the spatial reference of the schema template feature class.)Note also that the inconsistent casing CreateFeatureclass looks like a mistake on the part of Esri and is unfortunately required in this script.
Clip all feature classes in a geodatabase
The data for this practice exercise consists of two file geodatabases: one for the USA and one for just the state of Iowa. The USA dataset contains miscellaneous feature classes. The Iowa file geodatabase is empty except for an Iowa state boundary feature class.
Your task is to write a script that programmatically clips all the feature classes in the USA geodatabase to the Iowa state boundary. The clipped feature classes should be written to the Iowa geodatabase. Append "Iowa" to the beginning of all the clipped feature class names.
Your script should be flexible enough that it could handle any number of feature classes in the USA geodatabase. For example, if there were 15 feature classes in the USA geodatabase instead of three, your final code should not need to change in any way.
- Practice 5 Solution
Here's one way you could approach Lesson 2 Practice Exercise 5.
If you're new to programming, I encourage you to try figuring out a solution yourself before studying this solution. Then once you've given it your best shot, study the answer carefully. The video below (6:10) explains the script in more depth. Please note that the video uses the old Python 2 syntax with no parentheses (...) for the print statements. That is the only real difference to the Python 3 solution code you see below though.
#This script clips all feature classes in a file geodatabase import arcpy # Create path variables sourceWorkspace = "C:\\Data\\Lesson2PracticeExercise\\USA.gdb" targetWorkspace = "C:\\Data\\Lesson2PracticeExercise\\Iowa.gdb" clipFeature = "C:\\Data\\Lesson2PracticeExercise\\Iowa.gdb\\Iowa" # Get a list of all feature classes in the USA folder arcpy.env.workspace = sourceWorkspace featureClassList = arcpy.ListFeatureClasses() try: # Loop through all USA feature classes for featureClass in featureClassList: # Construct the output path outClipFeatureClass = targetWorkspace + "\\Iowa" + featureClass # Perform the clip and report what happened arcpy.Clip_analysis(featureClass, clipFeature, outClipFeatureClass) arcpy.AddMessage("Wrote clipped file " + outClipFeatureClass + ". ") print ("Wrote clipped file " + outClipFeatureClass + ". ") except: # Report if there was an error arcpy.AddError("Could not clip feature classes") print ("Could not clip feature classes") print (arcpy.GetMessages())Click for a transcript of "Explanation of Lesson 2 practice exercise" video. (6:10)PRESENTER: This video explains the solution to the Lesson 2 practice exercise in which we clip a bunch of feature classes from the USA geo database to the Iowa State boundary.
In line 3, we import the arcpy site package. This is required whenever we work with Esri tools or datasets.
In line 6, 7, & 8, we set up a series of string variables representing the locations on disk that we will work with during this script. Now, if you were going to go ahead and make a script tool out of this, you would eventually want to change these to arcpy.GetParameterAsText as described in the lesson text. However, while you're working on your script in PyScripter, you'll want to actually write out the full path so you can test and make sure that your script works. Once you're absolutely certain that your script works, then you can go ahead and insert arcpy.GetParameterAsText and then you can start making the script tool and test out the tool. So I recommend you keep those two parts separate, testing in PyScripter first and then making the tool.
In line 6 & 7, we set up the paths to some of the geodatabases will be working with. Line 6 is the USA geodatabase that contains the feature classes that we will clip. In line 7, is the Iowa geodatabase where we will put the output feature classes. Line 8 is the actual click feature, this is our cookie cutter feature that is the state boundary of Iowa.
Now, we've used the term workspace in the line six and seven variable names just to mean a folder or a geodatabase in which we're working. But there is an official workspace for arcpy which is the current folder or geodatabase in which arcpy is looking for items; and so, in line 11, we actually set the arcpy workspace to be the same path that we set up in line 6 which is USA, and when we start listing feature classes, that's going to be the workspace that we look in.
Line 12 is where we called the method to list feature classes and the question is, list feature classes where? And the answer is the workspace that we just set up in line 11, so it's going to default to look in arcpy's workspace for the feature classes it should list. And so, what we get out of this is a variable that we call featureClassList and it is a Python list of all the feature classes in the USA geodatabase. This is great now, because we know how to loop through a list, and we're going to do that here in just a minute.
In line 14, we begin a try block, so we'll try to run all this code from line 14 to 25. If for some reason there's a crash, the code will go down to line 27 and run the except block.
In line 17, we begin a for loop to try to go through each feature class in the list. And we create a new variable in line 17 called featureClass. So we say for featureClass in featureClassList. featureClass is just a name that we come up with for that variable. We could name it anything. In this case, it's pretty intuitive to call it featureClass.
Line 20 is setting up the output path. Now, one of our requirements was to append Iowa at the beginning of the output feature class name, and so we're doing a little bit of string manipulation using the plus sign to make a path. This particular string outClipFeatureClass is going to be created by concatenating the target workspace so— that's what you created up in line 7. So that will always be part of the output path. Then, we're going to explicitly add slash Iowa and then, on to that, we'll tack the name of the feature class that we're currently working with. This type of string concatenation to make a path is something that you will also need to do during your Lesson 2 project.
In line 23, we actually perform the clip by calling arcpy.Clip, and we need to put underscore analysis because we want to specify that we're using the Clip tool that's in the Analysis toolbox. Now, Clip, in this case, has three required parameters: the input feature class to be clipped so that's the current one that we're looping on right now—it was defined up in line 17. So that's the first parameter featureClass. The second parameter there is the clipFeature, and we defined that up in line 8. That's our cookie cutter feature what we'll be clipping all the rest of the feature classes to. And then, the third is the output path, and that's the path we just created back in line 20, and once we have all three of those things, we can run the Clip tool.
In lines 24 and 25, we report what happened, and we use a little bit of string concatenation there as well to make a custom message saying that exactly the path of the clipped file that we created. This will be useful in your project, as well. Now, this is an either/or scenario you would need to call either AddMessage or do a print statement in line 25. I just put both of them in here so you can see how they were both used. If you're going to go ahead and make a script tool out of this, that would go in your toolbox in ArcGIS, then you would use the approach in line 24, AddMessage. If you're just going to run this inside of PyScripter, then you would use line 25. If for some reason there were to be a crash in the above code, it would jump down to line 27 and run the except statement, and again we're using the approach of doing either an AddError, this is what you would use if you were making a toolbox, so that's line 30; or if you're just running in PyScripter, you can do line 31. And then, line 32 is another print statement you could do out of PyScripter. It actually gets the message from the Clip tool. The Esri geoprocessing tools report their own error messages, so if you were to have a problem that happened inside the Clip, you might be able to get it back this way by doing print arcpy.GetMessages.
Source: Sterling QuinnNotes
- The code above both prints messages and uses arcpy.AddMessage and arcpy.AddError to add geoprocessing messages. Normally, you would only put one or the other in your script. When you make a script tool, take out the print statements and add the arcpy.AddMessage and arcpy.AddError functions, so that your messages will appear in the ArcGIS tool results window.
Project 2: Batch reprojection tool for vector datasets
Project 2: Batch reprojection tool for vector datasets jed124Some GIS departments have determined a single, standard projection in which to maintain their source data. The raw datasets, however, can be obtained from third parties in other projections. These datasets then need to be reprojected into the department's standard projection. Batch reprojection, or the reprojection of many datasets at once, is a task well suited to scripting.
In this project, you'll practice Python fundamentals by writing a script that re-projects the vector datasets in a folder. From this script, you will then create a script tool that can easily be shared with others.
The tool you will write should look like the image below. It has two input parameters and no output parameters. The two input parameters are:
- A folder on disk containing vector datasets to be re-projected.
- The path to a vector dataset whose spatial reference will be used in the re-projection. For example, if you want to re-project into NAD 1983 UTM Zone 10, you would browse to some vector dataset already in NAD 1983 UTM Zone 10. This could be one of the datasets in the folder you supplied in the first parameter, or it could exist elsewhere on disk.
Figure 2.1 The Project 2 tool with two input parameters and no output parameters.
Running the tool causes re-projected datasets to be placed on disk in the target folder.
Requirements
To receive full credit, your script:
- must re-project shapefile vector datasets in the folder to match the target dataset's projection;
- must append "_projected" to the end of each projected dataset name. For example: CityBoundaries_projected.shp;
- must skip projecting any datasets that are already in the target projection;
- must report a geoprocessing message telling which datasets were projected. In this message, the dataset names can be separated by spaces. In the message, do not include datasets that were skipped because they were already in the target projection. This must be a single message, not one message per projected dataset. Notice an example of this type of custom message below in the line "Projected . . . :"
Figure 2.2 Your script must report a geoprocessing message telling which datasets were projected. - Must not contain any hard-coded values such as dataset names, path names, or projection names.
- Must be made available as a script tool that can be easily run from ArcGIS Pro by someone with no knowledge of scripting.
Successful completion of the above requirements is sufficient to earn 95% of the credit on this project. The remaining 5% is reserved for "over and above" efforts which could include, but are not limited to, the following:
- Your geoprocessing message of projected datasets contains commas between the dataset names, with no extra "trailing" comma at the end.
- User help is provided for your script tool. This means that when you open the tool dialog and hover the mouse over the "i" icon next to each parameter, help appears in a popup box. The ArcGIS Pro Help can teach you how to do this.
You are not required to handle datum transformations in this script. It is assumed that each dataset in the folder uses the same datum, although the datasets may be in different projections. Handling transformations would cause you to have to add an additional parameter in the Project tool and would make your script more complicated than you would probably like for this assignment.
Sample data
The Lesson 2 data folder contains a set of vector shapefiles for you to work with when completing this project (delete any subfolders in your Lesson 2 data folder—you may have one called PracticeData—before beginning this project). These shapefiles were obtained from the Washington State Department of Transportation GeoData Distribution Catalog, and they represent various geographic features around Washington state. For the purpose of this project, I have put these datasets in various projections. These projections share the same datum (NAD 83) so that you do not have to deal with datum transformations.
The datasets and their original projections are:
- CityBoundaries and StateRoutes - NAD_1983_StatePlane_Washington_South_FIPS_4602
- CountyLines - NAD_1983_UTM_Zone_10N
- Ferries - USA_Contiguous_Lambert_Conformal_Conic
- PopulatedPlaces - GCS_NorthAmerican_1983
Deliverables
Deliverables for this project are as follows:
- the source .py file containing your script;
- a detailed written narrative that explains in plain English what the various lines of your script do. In this document, we will be looking for you to demonstrate that you understand how the code you're submitting works. You may find it useful to emulate the practice exercise solution explanations. Alternatively, you may record a video in which you walk the grader through your solution beginning to end.
- the .atbx file containing your script tool;
- a short writeup (about 300 words) describing how you approached the project, how you successfully dealt with any roadblocks, and what you learned along the way. You should include which requirements you met, or failed to meet. If you added some of the "over and above" efforts, please point these out, so the grader can look for them.
Tips
The following tips can help improve your possibility of success with this project:
- Do not use the Esri Batch Project tool in this project. In essence, you're required to make your own variation of a batch project tool in this project by running the Project tool inside a loop. Your tool will be easier to use because it's customized to the task at hand.
-
There are a lot of ways to insert "_projected" in the name of a dataset, but you might find it useful to start by temporarily removing ".shp" and adding it back on later. To make your code work for both a shapefile (which has the extension .shp) and a feature class in a geodatabase (which does not have the extension .shp), you can use the following:
rootName = fc if rootName.endswith(".shp"): rootName = rootName.replace(".shp","")In the above code, fc is your feature class name. If it is the name of a shapefile it will include the .shp . The replace function searches for any string ".shp" (the first parameter) in the file name and replaces it with nothing (symbolized in the second parameter by empty quotes ""). So after running this code, variable rootName will contain the name of the feature class name without the ".shp" . Since replace(...) does not change anything if the string given as the first parameter does not occur in fc, the code above can be replaced by just a single line:
rootName = fc.replace(".shp","")You could also potentially chop off the last four characters using something likerootName = fc[:-4]
but hard-coding numbers other than 0 or 1 in your script can make the code less readable for someone else. Seeing a function like replace is a lot easier for someone to interpret than seeing -4 and trying to figure out why that number was chosen. You should therefore use replace(...) in your solution instead.
- To check if a dataset is already in the target projection, you will need to obtain a Spatial Reference object for each dataset (the dataset to be projected and the target dataset). You will then need to compare the spatial reference names of these two datasets. Be sure to compare the Name property of the spatial references; do not compare the spatial reference objects themselves. This is because you can have two spatial reference objects that are different entities (and are thus "not equal"), but have the same name property.
You should end up with a line similar to this:if fcSR.Name != targetSR.Name:
where fcSR is the spatial reference of the feature class to be projected and targetSR is the target spatial reference obtained from the target projection shapefile. - If you want to show all the messages from each run of the Project tool, add the line: arcpy.AddMessage(arcpy.GetMessages()) immediately after the line where you run the Project tool. Each time the loop runs, it will add the messages from the current run of the Project tool into the results window. It's been my experience that if you wait to add this line until the end of your script, you only get the messages from the last run of the tool, so it's important to put the line inside the loop. Remember that while you are first writing your script, you can use print statements to debug, then switch to arcpy.AddMessage() when you have verified that your script works, and you are ready to make a script tool.
- If, after all your best efforts, you ran out of time and could not meet one of the requirements, comment out the code that is not working (using a # sign at the beginning of each line) and send the code anyway. Then explain in your brief write-up which section is not working and what troubles you encountered. If your commented code shows that you were heading down the right track, you may be awarded partial credit.
- Remember to remove any debugging code from your script once you're satisfied that it's working properly.