CMSI 3630: Getting Started With Python:
Python Workshop

Python is one of the most popular programming languages in the world. This page will show you a bit about what python programs look like, and will lead you through writing some basic simple code to run on your laptop or desktop computer.

An old tradition which dates back to the Pleistocene Epoch holds that any time you learn a new language you must write a 'hello world' program to start with. There are TONS of languages, of course, and thus there are TONS of ways to write this program. Python makes it dead simple:

   print( 'hello world' )
            

You can dress it up a little, using the 'newline' character which is a backslash followed by an 'n', like this:

   print( '\n Hello, world!' )
            

Installing Python

Python is easy. It's easy to learn, and it's easy to install. It's also free, which is nice. Navigate your browser to https://www.python.org/downloads/ and select the appropriate link for the computer platform you have, either Windows, MacOS, Linux/UNIX, or something else. Follow the normal way you use to do any application installation for your platform. For example, on Windows, double-click the '.exe' file.

The 'Idle' Integrated Development Environment [IDE]

Python ships with a quasi-IDE called IDLE which stands for Integrated Development and Learning Environment. It is a GUI-driven Python environment that includes a colored-syntax editor and file handling capabilities, along with a couple of different ways to run programs. You can find out all about it and try it out from here.

Running Python Online

One really good resource for learning Python [or just about any programming language you can think of, actually] is using the TIO web pages. TIO stands for Try It Online and you can select the language from a ginormous list. Go to the home page, click on the big arrow, and then select from the list to find Python 3. Once you open that page, you'll be able to input your source code and click the run button at the top of the page next to the TIO logo to run it.

Running Python from the Command Line

Once Python is installed on your computer [and added to your program search path, of course] you can simply type python at the command prompt to start the interpreter. If you want to run a program right away, change to the directory in which the source code is located and type python <filename>.py and the program will run.

About This Workshop

Do you know a programming language other than Python fairly well, but would like to learn Python? Join this Workshop to gain proficiency with the basics!

These notes are meant to be used in an interactive, classroom setting. The notes by themselves do not give you a sufficient explanation for each activity, so you'll need to be in class in person to get the full benefit. There is little to nothing in the way of explanation of any of the technical aspects of the language — that is left for your programming language class if you take one. Explanations come during the structured in-person lessons!

How This Works

This Workshop is designed to be run under the I DO, WE DO, YOU DO model.

For each activity, the professor [I DO] will first demo the finished product, explain the learning objectives, and highlight the code structure. Next, the students and professor [WE DO] will do a code along; students can copy-paste or type the code for each activity and make sure they can run the code. The professor will then ask students to predict what certain small changes [or extensions or modifications] to the code will do, then professor and students will try these changes together. A number of language-specific questions from students can show up in this stage as well, and that's great! Finally, several exercises to extend the activity code further are presented: the professor can initiate one or more of these activities with the students [keeping the WE DO phase going a little longer], but ultimately the professor should release the students into the YOU DO phase to complete the suggested extensions for independent practice when it makes sense to do so. If the independent practice takes place in a classroom or lab, TAs and professors should be available, and students, when confident, should perform peer teaching and learning as well.

Learning Objectives

By the end of this Workshop, all twelve activities, you should have a good understanding of:

This is a programming Workshop, not a Developer Workshop.

The activities here are designed to expose you to elements of the Python programming language. They do not cover enterprise programming techniques such as organizing applications with Maven or other package tools, using frameworks such as Django, or building networked or web applications. All the activities are very small command line programs, with some external libraries for purposes of learning and illustration.

Prerequisites

This Workshop assumes that you:

  1. …have already done some programming, even just a little bit, in some programming language, so that you know how to use variables, 'if' statements, loops, and functions
  2. …have python installed on your computer, and know how to start it either from the command line, from the IDLE IDE, or both
  3. …have a text editor of some flavor installed on your computer, such as VSCode, SublimeText, TextPad, Notepad++, Atom, or something similar, and know how to edit a source code file using it

Activities

These simple sample programs are designed to be worked through in order. They start easy and then they increase in difficulty. We've already done the zero-eth one, but we'll re-visit it and then expand on it some to get us going.

Activity 00 — Hello world and f2c

Open your editor, and make a new file called sayHi.py. Type in the following line:

   print( 'Hello, world!' )
                      

Save the file, then open a command or terminal window to the same directory where the file is stored, and type the command:

   python sayHi.py
                      

Congratulations! Your first Python program!

Be friendly and sayHi in Python
OK, now for something a little harder…

Now let's make a program that calculates the Celcius temperature from a Fahrenheit temperature. This is a really easy one to do using the formula
C = 5/9 * (F - 32)
or in Python parlance it looks like
degreesC = (((degreesF - 32) * 5) / 9).

However, there are a couple of new things here:

OK, let's see how this goes. First open a new editor file so you can type in the code. Name the file something easy to type, like f2c.py. Copy and paste the following code into the file:

   degreesF = float( input( '\n  Enter a temperature in degrees F: ' ) )
   degreesC = float(((degreesF - 32) * 5 ) / 9)
   print( "\n  You entered ", degreesF, " degrees which is ", degreesC, " degrees C.\n" )
            

Save the file, then open a command or terminal window to the same directory where the file is stored, and type the command:

   python f2c.py
            

When the program runs, you should see a prompt asking for a temperature, just like the first line is asking. Enter the value 212 and press Enter. You should see:

     Enter a temperature in degrees F: 212
     You entered   212.0  degrees which is  100.0  degrees C.
            

HUZZAH!! It works! Now try some other known Fahrenheit temperatures like 32°F, the freezing point of water or -40°F, the temperature at which the two scales have the same value. Then try something screwy like abc as the entry and see what happens.

Activity 01 — Riding the Waves

OK, next let's do a bit of 'console art' and use some basic math to create a wave pattern of stars on the console window. There's a little bit of arithmetic here, but how we came up with the formula is not important to this sample program. If you can figure it out, excellent! But the important part of this first activity is on getting a feel for Python command line applications, not showing off your knowledge of trigonometry! Here is the code:

SURFING!!
   import math

   for row in range( 64 ):
      waveHeight = math.fabs( math.sin( row * math.pi / 16.0 ) + 1 )
      numberOfStars = int( round( 12 * waveHeight, 0 ) )
      for col in range( numberOfStars ):
         print( "*", end = "" )
      print()
            

The output should look like this [note that everything above and including the RESTART: thing is from running the program using the IDLE GUI]:

   Python 3.11.1 (tags/v3.11.1:a7a450f, Dec  6 2022, 19:58:39) [MSC v.1934 64 bit (AMD64)] on win32
   Type "help", "copyright", "credits" or "license()" for more information.

   = RESTART: D:\bjohnson\PythonCode\classwork\classwork01\waveprinter.py
   ************
   **************
   *****************
   *******************
   ********************
   **********************
   ***********************
   ************************
   ************************
   ************************
   ***********************
   **********************
   ********************
   *******************
   *****************
   **************
   ************
   **********
   *******
   *****
   ****
   **
   *



   *
   **
   ****
   *****
   *******
   **********
   ************
   **************
   *****************
   *******************
   ********************
   **********************
   ***********************
   ************************
   ************************
   ************************
   ***********************
   **********************
   ********************
   *******************
   *****************
   **************
   ************
   **********
   *******
   *****
   ****
   **
   *



   *
   **
   ****
   *****
   *******
   **********
            

Quite a few things to talk about in this code [above the output, I mean]…

Experiment
with different expressions for generating lines of stars. Get creative and see what kind of cool things you can do!


There are hard-coded numbers in the program. These are often called magic numbers and we usually try not to use them that way. Instead, we define constants so that they are better defined in the code. The values 64 [number of rows], 12 [half the maximum number of stars per row], and 16.0 [half the number of rows per cycle] are just sitting in the middle of the code, making it look kind of mystical. Fix the code to use well-named variables for these values, so a reader can figure out more about what is going on.

Test yourself to see if you can make the program create a 'sawtooth' wave instead of a 'sinusoidal' wave.


Activity 02 — Flipping Coins

What are the odds?

What are your odds of tossing a certain number of heads in a row? We can figure out that you have a 1 out of 2 chance of tossing heads, then 1 out of 4 to get two heads in a row, 1 out of 8 for three. But how about in general? Or at least for anything up to 10 tosses?

Let's print the odds for the first few, both in the one out of N form and as percentages, too:

   #
   # A command line application that reports the odds of tossing a fair coin 1-10
   # times and getting heads each time.
   #

   # We'll show odds both in "1 in n" format and in percentage format
   message = "Odds of throwing {} heads in a row is 1 in {} ({}%)"

   possibilities = 2
   for tosses in range( 1, 11 ):
       percentage = 100.0 / possibilities
       line = message.format( tosses, possibilities, percentage )
       print( line )

       # Ready for the next iteration
       possibilities *= 2
            

The output should look like this:

   Python 3.11.1 (tags/v3.11.1:a7a450f, Dec  6 2022, 19:58:39) [MSC v.1934 64 bit (AMD64)] on win32
   Type "help", "copyright", "credits" or "license()" for more information.

   = RESTART: D:\bjohnson\PythonCode\classwork\classwork01\waveprinter.py
   Odds of throwing 1 heads in a row is 1 in 2 (50.0%)
   Odds of throwing 2 heads in a row is 1 in 4 (25.0%)
   Odds of throwing 3 heads in a row is 1 in 8 (12.5%)
   Odds of throwing 4 heads in a row is 1 in 16 (6.25%)
   Odds of throwing 5 heads in a row is 1 in 32 (3.125%)
   Odds of throwing 6 heads in a row is 1 in 64 (1.5625%)
   Odds of throwing 7 heads in a row is 1 in 128 (0.78125%)
   Odds of throwing 8 heads in a row is 1 in 256 (0.390625%)
   Odds of throwing 9 heads in a row is 1 in 512 (0.1953125%)
   Odds of throwing 10 heads in a row is 1 in 1024 (0.09765625%)
            

Experiment
to make the program output up to 100 coin tosses.


Research
how to make the str.format() function line up the number so they display in neat columns. Right-justify the integers and align the decimal points for the percents.


Activity 03 — Count Your Chickens Before They Hatch

OK, we've seen a lot of basic 'stuff' now, so it's time to get a bit more sophisticated. Remember in the f2c.py exercise you were asked to test what happens when the user enters 'abc' instead of a number? You got an error message, right? This is the kind of situation that users hate and programmers spend a LOT of time fixing. The best way to watch out for that is with error handling.

Python provides a nice facility called try … except which is very similar to other languages that have try … catch. This example illustrates it [along with plenty of other things, too…]. In this example, the program will prompt you to enter a number of eggs, then will show you how the pros count them. Let's hope the hens that laid them are cage free hens!. You shouldn't buy eggs from factory farms with caged chickens. That would be terrible.

I cannot count that high!

We'll also see how to define functions here!

Here's the code:

   """
    An application that prompts the user for a number of eggs, and reports this
    number in grosses and dozens.
   """

   import math

   def instructions():
      print( "\n Welcome to the Egg Counter! " )
      print( "  If you have less than a billion eggs," )
      print( "  we'll help you count them, like the pros do." )
      print( "  You must enter the number of eggs as an integer." )

   def main():
      try:
         eggs = int( input( "How many eggs? " ) )
      except:
         instructions()
         return
      if( eggs < 0 ):
         instructions()
         return
      elif( eggs >= 1000000000 ):
         instructions()
         return
      gross = eggs / 144
      excessOverGross = eggs % 144
      dozens = excessOverGross / 12
      leftOver = excessOverGross % 12
      print( "You have {} gross, {} dozen, and {} eggs.\n".format( gross, dozens, leftOver ) )

   main()
            

The output should look like this:

   Python 3.11.1 (tags/v3.11.1:a7a450f, Dec  6 2022, 19:58:39) [MSC v.1934 64 bit (AMD64)] on win32
   Type "help", "copyright", "credits" or "license()" for more information.

   = RESTART: D:\bjohnson\PythonCode\classwork\classwork01\waveprinter.py
   How many eggs? 123457
   You have 857.3402777777778 gross, 4.083333333333333 dozen, and 1 eggs.
            

Experiment
Clean up the magic numbers. Consider defining new variables for eggs per dozen, dozens per gross, and so on.


Experiment
A great gross is 12 grosses (1728 items). Make the program handle this unit.


Experiment
Make the program only show units for which the count is nonzero.


Experiment
Figure out how to get the program to only show integer values for all of the calculation results.


Activity 04 — Trip Down Memory Lane: Times Tables

YYAAAAAAAAAAYYYY

You're gonna need your multiplication tables. Since they don't print those out any more [paper shortage] you better either know them 'by heart' or be able to generate your own. In this activity we'll print a multiplication table to standard output whose size will be a command line argument. We'll see that in Python, the command line arguments are passed in an array to the main method of the app. The array has a special name: sys.argv.

As in the previous activity, this program has user input, so checking is required. That's one of the major principles of software security: validating input. In this program, we do not want non-integers, and we do not want values which are too big or too small.

One thing to note is that Python doesn't have the notion of defined constants the way other languages like Java and C-sharp do. Python relies on an accepted standard convention of using all uppercase letters with underscores between the words to identify a constant. You'll see examples in the upcoming program.

Here's the code:

   import sys

   MIN_SIZE = 1
   MAX_SIZE = 20

   ARGUMENT_COUNT_ERROR = "Exactly one command line argument required"
   ARGUMENT_FORMAT_ERROR = "Argument must be an integer"
   SIZE_ERROR = str.format( "Size must be between {} and {}", MIN_SIZE, MAX_SIZE )

   def printTable( size ):
      for i in range( 1, (size + 1) ):
         for j in range( 1, (size + 1) ):
            cell = "{:4d}".format( i * j )
            print( cell, end = "" )
         print()
      return

   def main():

      size = 0
      try:
         count = len( sys.argv )
         print( "count is: ", count )
         size = int( sys.argv[1] )
         print( "size is: ", size )
         if( count == 1 ):
            print( ARGUMENT_COUNT_ERROR )
            return
         elif( (size < MIN_SIZE) or (size > MAX_SIZE) ):
            print( SIZE_ERROR )
            return
      except:
         print( ARGUMENT_FORMAT_ERROR )

      printTable( size );

   main()
            

The output should look like this:

= RESTART: D:\bjohnson\PythonCode\classwork\classwork01\timestables.py
Argument must be an integer

= RESTART: D:\bjohnson\PythonCode\classwork\classwork01\timestables.py 12
size is:  12
   1   2   3   4   5   6   7   8   9  10  11  12
   2   4   6   8  10  12  14  16  18  20  22  24
   3   6   9  12  15  18  21  24  27  30  33  36
   4   8  12  16  20  24  28  32  36  40  44  48
   5  10  15  20  25  30  35  40  45  50  55  60
   6  12  18  24  30  36  42  48  54  60  66  72
   7  14  21  28  35  42  49  56  63  70  77  84
   8  16  24  32  40  48  56  64  72  80  88  96
   9  18  27  36  45  54  63  72  81  90  99 108
  10  20  30  40  50  60  70  80  90 100 110 120
  11  22  33  44  55  66  77  88  99 110 121 132
  12  24  36  48  60  72  84  96 108 120 132 144

= RESTART: D:\bjohnson\PythonCode\classwork\classwork01\timestables.py aaa
Argument must be an integer

= RESTART: D:\bjohnson\PythonCode\classwork\classwork01\timestables.py 30
Size must be between 1 and 20
            

Experiment
See if you can change this code to output a power table rather than the times table.


Research
to see if you can find out how to 'throw' an exception when an except occurs. In other words, find out what exceptions there are, and when there is an exception, throw a NEW exception with a specific message. There is a complete hierarchical list of exceptions in Python at this link.


Activity 05 — Your Birthday Day of the Week

Python has lots of really good libraries of functionality, which are known as modules. We've seen a couple of them already. Now lets take a look at how Python handles time.

The Python datetime module consists of five main classes

  • date
  • time
  • tzinfo
  • datetime
  • timedelta

Let's look at how to use these to find the day of the week on which you were born. We'll also re-visit the command line arguments again, since that is a simple way of getting input into our programs.

stopwatch picture

To run this program, you enter a date as a command line argument. The date [for this example program] should be in the form yyyy-mm-dd. Any date will work, since this program just calculates the day of the week that date occurs.

We're going to need the two modules sys and time for this one. We'll also use a list to hold the days of the week as strings, so we can index into it. [There are other ways to do this using the datetime functions — those are left as an exercise for the student.]

Here's the code:

   import sys
   import time

   dayOfWeek = [ 'Monday', 'Tuesday', 'Wednesday', \
                 'Thursday' 'Friday', 'Saturday', 'Sunday' ]

   if( len( sys.argv ) < 1 ):
      print( "\n   You must supply a date of form yyyy-mm-dd.\n" )
   else:
      theDate = sys.argv[1]
      print( "\n     Finding day of week for date:", theDate )
      day_variable = time.strptime( theDate, "%Y-%m-%d" )
      print( "   That date's day of the week is:", dayOfWeek[day_variable.tm_wday] )
            

…and here's the output of a typical run:

= RESTART: D:/bjohnson/PythonCode/classwork/classwork03/dayOfWeek.py

     Finding day of week for date: 1951-06-05
   That date's day of the week is: Tuesday
            

Experiment
Try running the code with bogus dates like 2022-09-37 or like 1900-13-00 and see what happens. Then see if you can fix your code to output a message telling the user how to run the code properly.

Experiment
Modify your program to take in the year, month and day as separate arguments on the command line.


Experiment
Research how to display the input date string itself, in various text styles. Make your program respond with an entire phrase, such as, in English, August 1, 1987, was a Saturday. To get really deep into the world of dates, try to distinguish past dates from future dates in your output. Research will be required, but familiarity with the documentation is a necessary skill for programmers!

Activity 06 — Making Nonsense for Fun and Profit

BWWWAAAAHAHAHAHAHHAAAAA

Time to get silly and generate nonsense sentences. We are starting simply, with our program featuring only a single, fixed, story template, and a built-in set of possible words.

This little program features lists and dictionaries. A list is what Python calls an array of objects — in this case type str. A dictionary is what Python calls a map. If you've done Python before, you know that you can modify your lists and dictionaries, but in Java, using List.of and Map.of produce unmodifiable objects! This is nice and secure for fixed data like we have in statically-typed languages, but not so much for a dynamilcally-typed language like python.

For this activity, we have a dictionary with word types as keys, and the value for each key is a list of random words. We also have a list of other random words, some of which are the keys in the dictionary. The list acts as a template for making a sentence, which ends up being nonsensical.


Here's the code:

   import random

   words = {"noun": ["dog", "carrot", "chair", "toy", "rice cake"],
            "verb": ["ran", "barked", "squeaked", "flew", "fell", "whistled"],
            "adjective": ["small", "great", "fuzzy", "funny", "light"],
            "preposition": ["through", "over", "under", "beyond", "across"],
            "adverb": ["barely", "mostly", "easily", "already", "just"],
            "color": ["pink", "blue", "mauve", "red", "transparent"] };
   tokens = ["Yesterday", "the", "color", "noun",
             "verb", "preposition", "the", "coach's",
             "adjective", "color", "noun", "that", "was",
             "adverb", "adjective", "before"];

   firstWord = True
   for token in tokens:
      if( not firstWord ):
         print( " ", end = "" )
      if( token in words ):
         choices = words.get( token )
         print( choices[random.randint( 0, (len(choices) - 1) )], end = "" )
      else:
         print( token, end = "" )
      firstWord = False
   print( "." )
            

…and here's the output of a typical run, running the program seven times:

Python 3.11.1 (tags/v3.11.1:a7a450f, Dec  6 2022, 19:58:39) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.

= RESTART: D:\bjohnson\PythonCode\classwork\classwork03\nonsense.py
Yesterday the red toy barked under the coach's fuzzy transparent chair that was just funny before.

= RESTART: D:\bjohnson\PythonCode\classwork\classwork03\nonsense.py
Yesterday the pink chair barked beyond the coach's small pink dog that was just great before.

= RESTART: D:\bjohnson\PythonCode\classwork\classwork03\nonsense.py
Yesterday the blue carrot barked across the coach's great mauve rice cake that was already great before.

= RESTART: D:\bjohnson\PythonCode\classwork\classwork03\nonsense.py
Yesterday the blue rice cake fell through the coach's funny mauve carrot that was just light before.

= RESTART: D:\bjohnson\PythonCode\classwork\classwork03\nonsense.py
Yesterday the red chair flew beyond the coach's small pink toy that was already funny before.

= RESTART: D:\bjohnson\PythonCode\classwork\classwork03\nonsense.py
Yesterday the transparent rice cake fell across the coach's light mauve dog that was already light before.

= RESTART: D:\bjohnson\PythonCode\classwork\classwork03\nonsense.py
Yesterday the mauve chair whistled beyond the coach's funny red rice cake that was mostly funny before.
            

Experiment
add more words to the word list dictionarys. Add bigger words.


Experiment
Add multiple new sentence template listss. A run of the program should generate a random nonsense short story by generating five random sentences, each of which comes from a randomly selected template list.

Activity 07 — Take a Break: Python Online Examples

So far we've been concentrating exclusively on fully-functioning command line apps, that we run on the command line. We've only done enough Python to make our applications work, without worrying about all the details of, and all the possible operations on, our numbers, strings, booleans, arrays, lists, and maps.

Let's shift our focus from applications, just for a short time, and use TIO to practice with some language features that are really good to know about. The session below contains a number of Python features, some of which we will talk about some during class. If something isn't mentioned in class, you're encouraged to read the documentation to learn about it. If you're still stuck, ask!

In this class we'll be doing quite a bit of work in the terminal window. That's what it's called on the Mac [or in Linux, if you are running that O/S]. On Windows it's technically called the "Command Line", but I'll just use "terminal" to cover for both so I don't have to keep saying it both ways.

So, open a terminal and type the command python to bring up the interactive python shell. This will give you a prompt from the command line interpreter so that you can enter python code. If you want, you can open the IDLE GUI on your computer instead, or even go to the TIO page.

Warm ups

Try these out just to see if you get it...

Experiment
Can you explain the results you get from these commands?


Numbers

Try these number-oriented expressions on your own. [Notice that Python distinguishes between integers and floating point values.

Experiment
Make sure you understand what happens when regular integer arithmetic overflows, and what happens when floating-point arithmetic overflows.

Strings

Explore Python strings to see what these produce:

Activity 08 — World Traveler

whatever happened to Amelia Earhart?

Can you guess the 10 smallest countries in the world by population?

Let's make this question into a quiz game!

We're going to take this opportunity to introduce a new collection type: the set. We'll start with the 10 countries in a set. When you guess a country correctly, we'll remove it. If you manage to empty out the set, you will WIN. HUZZAH!!


Here's the code:

   ###
   # filename: GuessingGame.py
   # purpose:  demonstrate sets, while loops,
   #           break, split, and replace
   # author:   Dr. Johnson
   # date:     2023-01-05
   ###


   class GuessingGame:
      # note that the currly braces make this a set, not a list
      countries = {'vaticancity', 'nauru', 'tuvalu', \
                   'palau', 'sanmarino', \
                   'liechtenstein', 'monaco', 'st kitts', \
                   'marshall islands', 'dominica'}

      def main( self ):
         print( "\n   Welcome to the Country Guessing Game. Guess the 10 smallest" )
         print( "   countries by population.  If at any time you want to give up," )
         print( "   just enter the single letter 'q' at the prompt.\n" )

         message = "Guess a country: "
         while( len( self.countries ) != 0 ):
            response = input( message )
            response = self.cleanupGuess(response)
            if( response == 'q' ):
               break
            elif( response in self.countries ):
               self.countries.remove( response )
               message = "Great, {} more to go!  Next guess: ".format( len( self.countries ) )
            else:
               message = "Try again: "
         if( len( self.countries ) == 0 ):
            print( "\n\n   YOU WIN!  Wow amazing job!" )
         else:
         # Be nice, and show the ones still in the set
            print( "\n\n   That's okay, you only missed ", self.countries )

       #
       # We want to be liberal with what we receive from the user, trying our best to
       # "normalize" it. We lowercase then remove all non-letter characters for ease
       # of comparison. But there are some special cases to consider also.
      def cleanupGuess( self, guess ):
         guess = guess.lower()
         return guess

   gg = GuessingGame()
   gg.main()
            

…and here's the output of a typical run, running the program seven times:

   Welcome to the Country Guessing Game. Guess the 10 smallest
      countries by population.  If at any time you want to give up,
      just enter the single letter 'q' at the prompt.

   Guess a country: tuvalu
   Great, 9 more to go!  Next guess: Palau
   Great, 8 more to go!  Next guess: russia
   Try again: SAO Tome
   Try again: saotome
   Try again: VATICAN CITY
   Try again: VATICANCITY
   Great, 7 more to go!  Next guess: san marino
   Try again: doctor marino
   Try again: sanmarino
   Great, 6 more to go!  Next guess: marshall
   Try again: marshall islands
   Great, 5 more to go!  Next guess: AnTiGuA
   Try again: antigua
   Try again: DoMiNiCa
   Great, 4 more to go!  Next guess: Monaco
   Great, 3 more to go!  Next guess: saint kitts
   Try again: st kitts
   Great, 2 more to go!  Next guess: q


      That's okay, you only missed  {'nauru', 'liechtenstein'}
            

Experiment
Add more countries to the list, then modify the code to take the number of countries into account when displaying the remaining country count.


Experiment
Add a 'guess counter' into the program that will only allow the player to guess so many times before ending the game. The counter must AT LEAST allow the number of guesses that matches the number of countries in the list.

Experiment
Modify the code to base the number of allowed guesses on the number of countries in the list; add a factor to provide a few 'bad guesses' without making the player lose the game.


Experiment
Modify the program to accept different versions of the country names like Saint Kitts for st kitts or vatican city for vaticancity.


Experiment
Modify the program to output a message if the user enters a country that is obviously not a small population, like Russia or China or USA.

Activity 09 — Have a Little More Class

Our first few activities were all short command line apps that performed some little task, perhaps with input in the form of command line arguments and responses to prompts, then printed a result of some sort. All of these apps used entities of built-in types [numbers, booleans, strings, lists, sets], or types from a standard Python library [math, sys, random, etc.].

Now we'd like to create a type of our own, which in Python we do with the keyword class. Wait, haven't we made classes before? Didn't we create a class called GuessingGame? Well, yes, but the intent was never to make a type for new entities. It's just that, weirdly, sometimes Python requires us to make a class to house something about the program. And actually, even for the GuessingGame.py program, we could probably find a way to NOT use a class to do the same thing.

Now, finally, we're going to build a REAL class whose purpose is to allow the creation of many instances of that class. So, here we go.

call me the tumbling dice

A die is an n-sided entity [with n >= 4] whose value is between 1 and n, inclusive. Rolling the die changes its value, but the number of sides cannot be changed.

We must provide fields in this class to reflect the number of sides on the die, as well as the current value of the die. We must also provide functions to roll the die, get its value, and print out what it currently looks like [imagine a die sitting on a table with the side that has five spots on it on top – that is what I mean by what it looks like.]

Here's the code:

   ###
   # filename: Die.py
   # purpose:  Dice game class example
   # author:   Dr. Johnson
   # date:     2023-01-06
   ###

   import random
   import math

   class Die:
      sides = 4      # default for minimum number of sides
      value = 0      # default starting value

      def __init__( self, numberOfSides ):
         if( numberOfSides < 4 ):
            raise IllegalArgumentException( " must have at least 4 sides." )
         else:
            self.sides = numberOfSides

      def roll( self ):
         self.value = random.randint( 1, self.sides )
         return self.value

      def getValue( self ):
         return self.value

      def toString( self ):
         rep = "[{}]".format( self.value )
         return rep
            

I added a bunch of test code to the bottom of the source code to instantiate two different copies of the Die class and call their functions several times, displaying the results. The testing verifies four things:

…and here's the output of a typical run:

   Die 1 created ~ sides:  4

   Die 1 created ~ value:  0

   Die 1 rolled  ~ value:  4
   Die 1 rolled  ~ value:  3
   Die 1 rolled  ~ value:  2
   Die 1 rolled  ~ value:  3
   Die 1 rolled  ~ value:  2
   Die 1 rolled  ~ value:  1
   Die 1 getValue() says:  1
   Die 1 representation :  [1]

   Die 2 created ~ sides:  17

   Die 2 created ~ value:  0

   Die 1 rolled  ~ value:  17
   Die 2 rolled  ~ value:  8
   Die 2 rolled  ~ value:  3
   Die 2 rolled  ~ value:  8
   Die 2 rolled  ~ value:  14
   Die 2 rolled  ~ value:  1
   Die 2 getValue() says:  2
   Die 2 representation :  [2]

   Die 1 value:  1

   Die 2 value:  2
   Die 1 getValue() says:  1
   Die 1 representation :  [1]
            

Now, as simple example of a program that uses our Die objects, let's build a small program to roll a pair of six-sided dice thousands of times, keeping track of how often we roll a 2, or a 3, or a 4, and so on, up to a 12:

   ###
   # filename: DiceSet.py
   # purpose:  Dice game class example
   # author:   Dr. Johnson
   # date:     2023-01-06
   ###

   import Die

   class DiceSet:
      myDice = []
      counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

     # constructor
      def __init__( self ):
         d1 = Die.Die( 6 )
         d2 = Die.Die( 6 )
         self.myDice.append( d1 )
         self.myDice.append( d2 )

     # track the count
      def count( self ):
         index = self.myDice[0].getValue() + self.myDice[1].getValue()
         self.counts[index] += 1
         return

     # roll all the dice one at a time
      def rollAll( self ):
         for i in range( 0, len(self.myDice) ):
            self.myDice[i].roll()
         return


   def main():
     # a constant to define how many rolls
      MAX_ROLLS = 100000

     # instantiate the dice set
      dSet = DiceSet()

     # roll the dice
      for x in range( 1, MAX_ROLLS + 1 ):
         dSet.rollAll()
         dSet.count()

     # print the results
      print()
      for x in range( 2, len(dSet.counts) ):
         print( "{:2d} : {:8d}".format( x, dSet.counts[x - 1] ) )

   main()
            

…and here's the output of a typical run:

    2 :        0
    3 :     2827
    4 :     5548
    5 :     8480
    6 :    11178
    7 :    13770
    8 :    16630
    9 :    13873
   10 :    11129
   11 :     8280
   12 :     5466
            

Things to note and discuss:

Experiment
Modify the program to use three dice instead of two


Experiment
Modify the program to use a number of dice given from the command line by the user


Experiment
Modify the program to use dice with more than six sides


Activity 10 — Testing... Testing.... Is This Thing On??

whatever happened to Amelia Earhart?

So how do you know if your program even works? TEST IT!! Tests can increase confidence that your program is working, and the act of testing can help us find bugs before we ship our code. Many people would say that programmers even have a moral obligation to write tests.

Perhaps you've already been told about, or better, have experienced, how testing is essential and can save your life [both metaphorically AND literally].

For our testing, we can use a testing framework which is a set of code in a module that helps us set up tests for our code. The framework we'll use is called PyUnit. It is modeled after a mature and well-known framework for Java called JUnit.

Here is a test program for the Python string class which is one of the examples from the PyUnit documentation pages. As usual, the details of the activity, the fine points of testing in general, and testing in Python specifically, will be covered in class.


Here's the code:

   import unittest

   class TestStringMethods(unittest.TestCase):

       def test_upper(self):
           self.assertEqual('foo'.upper(), 'FOO')

       def test_isupper(self):
           self.assertTrue('FOO'.isupper())
           self.assertFalse('Foo'.isupper())

       def test_split(self):
           s = 'hello world'
           self.assertEqual(s.split(), ['hello', 'world'])
           # check that s.split fails when the separator is not a string
           with self.assertRaises(TypeError):
               s.split(2)

   if __name__ == '__main__':
       unittest.main()
            

THings to note here:

…and here's the output of a typical run, running the program seven times:


  D:\bjohnson\PythonCode\classwork\classwork04
  [Yo Beej: Need Input] <== python TestStringMethods.py
   ...
   ----------------------------------------------------------------------
   Ran 3 tests in 0.001s

   OK

  D:\bjohnson\PythonCode\classwork\classwork04
  [Yo Beej: Need Input] <== python TestStringMethods.py
   ...
   ----------------------------------------------------------------------
   Ran 3 tests in 0.001s

   OK

  D:\bjohnson\PythonCode\classwork\classwork04
  [Yo Beej: Need Input] <== python TestStringMethods.py
   ...
   ----------------------------------------------------------------------
   Ran 3 tests in 0.001s

   OK
            

Experiment
add tests for the rest of the string class.


Activity 11 — A Trip to Las Vegas [or 'Lost Wages' as it ends up for me…]

In the previous activity we saw how to write classes and create instances of classes. Classes can have constructors, fields, and methods [and a couple other things, but that is for later].

Die objects in the previous activity were mutable, which means they could be changed; the roll method changed the state of the object it was called on by changing the value of one of its fields. Normally in programming we strive to make objects that are immutable. This is a matter of safety, and is the premise behind things like encapsulation, getters and setters, the different visibility categories, and such like. The Java record, for one example, is a special kind of class for making immutable objects with a lot less code. We don't have that in Python, of course, but we can still sort of simulate that. Here is an example class for a single playing card:

WHOA, DUDE!  A ROYAL STRAIGHT FLUSH! OMG...

   ###
   # filename: Card.py
   # purpose:  simulation of a deck of playing cards
   # author:   Dr. Johnson
   # date:     2023-01-07
   ###

   class Card:

      cardSuit = None
      cardRank = None

      def __init__( self, suit, rank ):
   #      thisSet = { "?", "?", "?", "?" }      # can also do it with ASCII art
         thisSet = { "S", "H", "D", "C" }       # spades, hearts, diamonds, clubs
         myRank = [0, 'A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K']
         if( thisSet.isdisjoint( set( suit ) ) ):
            print( "   Illegal suit value sent to initializer." )
         elif( 1 > rank or 13 < rank ):
            print( "   Illegal card rank sent to initilaizer>" )
         else:
            self.cardSuit = suit
            self.cardRank = myRank[rank]

      def toString( self ):
         rep = "[{},{}]".format(str(self.cardSuit), str(self.cardRank))
         return rep

   # this is for testing, and will be removed later....
   spades = []
   for i in range( 0, 13 ):
      c = Card( "S", i+1 )
      spades.append( c )
      print( "card: ", c.toString() )
            

…and here's the output of a typical run:

     [Yo Beej: Need Input] <== python Card.py
   card:  [S,A]
   card:  [S,2]
   card:  [S,3]
   card:  [S,4]
   card:  [S,5]
   card:  [S,6]
   card:  [S,7]
   card:  [S,8]
   card:  [S,9]
   card:  [S,10]
   card:  [S,J]
   card:  [S,Q]
   card:  [S,K]
            

A lot of card games use a deck of all 52 cards. Let's make a class for decks. We want to be security conscious: every deck must have all 52 cards. You are allowed to shuffle the deck and ask about the positions and values of cards in the deck, but you can not add and remove cards. So what kind of a structure would be best here? Well we need a list rather than a set, because the elements are ordered.

In practice, Python applications will almost always use lists; Python arrays are much more rarely used. Arrays are generally only used in system-level or low-level code where either performance is crucial, or you are mapping to hardware, or you are implementing the internals of some higher-level data type; for example, there are arrays underneath things like lists, sets, tuples, and even dictionaries, but we can't see them ~ they are hidden from view by the language. It makes things lots easier for the programmer, but on the other hand we don't get to see the nuts and bolts of the things that are going on…

Arrays are really underpowered compared to lists in Python, which is why you don't even see them.

Here's the code for a deck of Card objects. We start with a normal deck of 52 cards, 13 of each of the four suits:

   ###
   # filename: Deck.py
   # purpose:  simulation of a deck of playing cards
   # author:   Dr. Johnson
   # date:     2023-01-11
   ###

   import Card
   import random

   class Deck:
      suits   = [ "S", "H", "D", "C" ]
      theDeck = []

      def __init__( self ):
         for s in self.suits:
            for i in range( 0, 13 ):
               c = Card.Card( s, i+1 )
               self.theDeck.append( c )

      def shuffle( self ):
         for i in range( len(self.theDeck) ):
            j = random.randint(0, 51)
            c1 = self.theDeck[i]
            c2 = self.theDeck[j]
            self.theDeck[i] = c2
            self.theDeck[j] = c1

      def cardAt( self, index ):
         try:
            return self.theDeck[index]
         except:
            print( "   Bad index in 'cardAt()' method.\n" )


      def deal( self, cardCount ):
         hand = []
         for i in range( cardCount ):
            hand.append( self.cardAt( i ).toString() )
            self.theDeck.pop(i)
         return hand
            

…and here's some code to test the Deck class:

   d = Deck()
   for i in range( len(d.theDeck) ):
      print( d.theDeck[i].toString(), end = " " )
   print()
   d.shuffle()
   print("\n\n    AFTER SHUFFLING:" )
   for i in range( len(d.theDeck) ):
      print( d.theDeck[i].toString(), end = " " )
   print()
   myHand = []
   myHand = d.deal( 5 )
   print( "\n  my hand: ", myHand )
   print("\n\n    AFTER DEALING ONE HAND:" )
   for i in range( len(d.theDeck) ):
      print( d.theDeck[i].toString(), end = " " )
   print()
            

…and here's the output of a typical run [I added the line breaks]:

  [Yo Beej: Need Input] <== python Deck.py
   [S,A] [S,2] [S,3] [S,4] [S,5] [S,6] [S,7] [S,8] [S,9] [S,10] [S,J] [S,Q] [S,K]
   [H,A] [H,2] [H,3] [H,4] [H,5] [H,6] [H,7] [H,8] [H,9] [H,10] [H,J] [H,Q] [H,K]
   [D,A] [D,2] [D,3] [D,4] [D,5] [D,6] [D,7] [D,8] [D,9] [D,10] [D,J] [D,Q] [D,K]
   [C,A] [C,2] [C,3] [C,4] [C,5] [C,6] [C,7] [C,8] [C,9] [C,10] [C,J] [C,Q] [C,K]

    AFTER SHUFFLING:
   [S,J] [H,Q] [H,8] [S,Q] [S,2] [D,9] [S,9] [S,A] [C,A] [H,10] [S,5] [S,10] [C,J]
   [C,5] [C,Q] [C,2] [S,7] [D,K] [S,3] [H,6] [C,7] [D,A] [C,4] [D,2] [D,Q] [H,2]
   [D,6] [S,8] [C,3] [H,K] [C,8] [S,6] [H,A] [C,9] [S,4] [S,K] [H,J] [D,3] [H,9]
   [D,10] [C,10] [D,8] [D,7] [H,3] [H,4] [C,K] [D,4] [D,5] [C,6] [H,5] [D,J] [H,7]

  my hand:  ['[S,J]', '[H,8]', '[S,2]', '[S,9]', '[C,A]']

    AFTER DEALING ONE HAND:
   [H,Q] [S,Q] [D,9] [S,A] [H,10] [S,5] [S,10] [C,J] [C,5] [C,Q] [C,2] [S,7] [D,K]
   [S,3] [H,6] [C,7] [D,A] [C,4] [D,2] [D,Q] [H,2] [D,6] [S,8] [C,3] [H,K] [C,8]
   [S,6] [H,A] [C,9] [S,4] [S,K] [H,J] [D,3] [H,9] [D,10] [C,10] [D,8] [D,7] [H,3]
   [H,4] [C,K] [D,4] [D,5] [C,6] [H,5] [D,J] [H,7]
            

Experiment
make a test that can deal more than one hand.


Experiment
Write a utility method that can determine from a given hand what type of poker hand you have.


Experiment
Write a program to play the game war by dividing the deck into two equal parts and playing one card from each half at a time such that the highest value card wins that round; in case of a tie, you should draw another card from each half to use for the comparison.

Activity 12 — Calling Dr. Doolittle! Talk with the Animals

A short drive out to the country will take you to a place where there are quite a few horses, cows, sheep, and other animals. Each of these animals has a name, and they all make their own species-specific sounds. But when asked to speak, the all speak exactly the same way: they say their name, then the word says then their individual sound.

Talk to the animals!

Python captures this idea of related types that share some behaviors but differ in other behaviors by a process called subclassing. In this operation, one Python class describes the common attributes and activities to all the classes, then the other subclasses inherit those attributes and activites and can add more that make them unique. The philosophical ideas behind how all this set up, including explanations of the meanings of the technical terms inheritance and polymorphism and the role they play in this application, will be covered during the code-along.


The basic idea is that the top level class will be what's known as an abstract class. This is a class which is kind of a blueprint for other classes. It lets you create methods called abstract methods which define the methods and their calling signatures but don't actually define implementations of the methods. They are left empty and must be implemented by the subclasses.

By default, python doesn't provide a built-in mechanism for abstract classes. However, there is a module that provides the base for defining what are called Abstract Base Classes, the ABC module. When a function is defined with the decoration keyword @abstractmethod, it is an abstract method and must be implemented by the subclasses.

Here's the code. First the main class, also called the super class. We'll define an Animal class:

   ###
   # filename: Animal.py
   # purpose:  demonstrate subclassing
   # author:   Dr. Johnson
   # date:     2023-01-15
   ###

   from abc import ABC, abstractmethod

   class Animal( ABC ):             # this is a subclass of ABC
      animalName = None

      def __init__( self, name ):
         self.animalName = name

      def speak( self ):
         phrase = f"\n   {self.animalName}{' says '}{self.sound()}"
         return phrase

      @abstractmethod
      def sound( self ):
         pass                       # this indicates an empty method

            

Now we'll define several more unique classes for each type of animal, also called the sub classes:

   ###
   # filename: Animal.py
   # purpose:  demonstrate subclassing
   # author:   Dr. Johnson
   # date:     2023-01-15
   ###

   from abc import ABC, abstractmethod

   class Animal( ABC ):             # this is a subclass of ABC
      animalName = None

      def __init__( self, name ):
         self.animalName = name

      def speak( self ):
         phrase = f"\n   {self.animalName}{' says '}{self.sound()}"
         return phrase

      @abstractmethod
      def sound( self ):
         pass                       # this indicates an empty method


   class Cow( Animal ):             # this is a subclass of Animal
      # overriding the abstract method for Cows
      def sound( self ):
         return "MOO!"

   class Horse( Animal ):             # this is a subclass of Animal
      # overriding the abstract method for Horses
      def sound( self ):
         return "NEIGH!"

   class Goat( Animal ):             # this is a subclass of Animal
      # overriding the abstract method for Goats
      def sound( self ):
         return "MAA-AA-AA!"

   class Sheep( Animal ):             # this is a subclass of Animal
      # overriding the abstract method for Sheep
      def sound( self ):
         return "BAA BAA BAA!"


   ## test code
   groucho = Cow( "Groucho")
   harpo = Horse( "Harpo")
   chico = Goat( "Chico")
   zeppo = Sheep( "Zeppo")
   print( groucho.speak() )
   print( harpo.speak() )
   print( chico.speak() )
   print( zeppo.speak() )
            

…and here's the output of a typical run:

  [Yo Beej: Need Input] <== python Animal.py

   Groucho says MOO!

   Harpo says NEIGH!

   Chico says MAA-AA-AA!

   Zeppo says BAA BAA BAA!
            

Experiment
Add two more types of animals, meaning create two more subclasses of the Animal superclass and write tests for each one.

Experiment
Break your program up into modules, with the Animal class in one file and the other animals in a second file.

Experiment
Do some research and give each of your animals a method to output its genus and species. Write tests to cover the new methods. Modify your code so the output says something like "Groucho says, 'I am a Bos Taurus and I say MOO!'