Tags

, ,

This series of posts is for those who have used a programming language before but are not familiar with Python. This post continues the introductory tour of the language.

In the last three posts [part 1, part 2, & part 3] we toured the key Python data types. In this post we start exploring Python syntax and language statements.

Selection — the ability to jump (aka branch) the thread of execution due to some runtime condition — is a key characteristic of programming languages. Because branching is due to a runtime condition, code statements implementing selection are also called conditionals.

The most common conditional, the one almost every programming language has, is the if statement and its cousins if-else and if-elseif-else.

Here’s a simple example in Python:

001| ### If statement…
002| 
003| x = 42
004| 
005| if 0 < x:
006|     
007|     
008| 

Remember three periods form the Python code object Ellipsis that does nothing but occupy space. We use Ellipsis to say “code goes here”. I use them here because the if statement needs a block of code (what Python calls a suite).

The most basic syntax is:

if condition :
    code-block

Both the colon and the indenting are crucial parts of Python. The indenting defines the code block. There can be as many indented lines as needed. The block ends when code returns to a previous indent level.

A common syntax for Python statements looks like this:

keyword some-stuff :
    indented-block-of-code

We’ve already encountered it with function and class examples in previous posts:

001| # Python user-defined functions and classes…
002| 
003| def function_name (parameters):
004|     ”’Define a function.”’
005|     
006|     
007| 
008| class class_name (base_class_name):
009|     ”’Define a class.”’
010|     
011| 
012|     def method_name (self, parameters):
013|         ”’Define a class method.”’
014|         
015|         
016| 
017|     
018|     
019| 

Though we haven’t explored either of these in detail yet. The first one defines a function named function_name; the second defines a class named class_name with one method named method_name. The Ellipsis indicate code that needs to be written to give these definitions functionality.

For now, the important part is the general form of functions and classes (and their methods). A keyword (def or class) followed by a user-defined name and parameters and ending with a colon.

The full Python if-elif-else statement looks like this:

001| ### If-Elif-Else statement…
002| 
003| x = 42
004| 
005| if x < 10:
006|     
007|     
008| elif x < 100:
009|     
010|     
011| elif x < 1000:
012|     
013|     
014| else:
015|     
016|     
017| 

Not too dissimilar from most programming languages. There can be as many elif clauses as needed.

Python has a nice wrinkle for conditional expressions:

001| # Range test…
002| 
003| x = 42
004| 
005| if 0 < x <= 99:
006|     
007|     
008| 

In many languages require: if (0 < x) and (x <= 99). (One of the attractions of Python is how expressive it is — how much it allows us to “say what we mean.”)

Python has an in operator that tests for membership in list-like objects:

001| ### The in operator…
002| 
003| x = 42
004| 
005| if x in [21, 42, 63, 84]:
006|     
007|     
008| 
009| words = “the words are foobar nazbot tonvat”
010| 
011| if ‘foo’ in words:
012|     
013|     
014| 
015| 

The general syntax is:

item [not] in list

Where item is any Python object to match, and list is any Python list-like object that might contain item. An optional not keyword reverses the logic. The operator returns True or False, depending on whether item is [or is not] in list.

(As we’ll discover below, the in keyword has another use.)

Lastly, we can write one-line conditionals:

001| # One-line if statements…
002| 
003| m = 50
004| 
005| x = 42
006| if x < m: print(f’{x} is less than {m}.)
007| else: print(f’{x} is greater than {m}.)
008| print()
009| 
010| x = 86
011| if m < x: x = xm; print(f’{x = })
012| print()
013| 

For code clarity, it’s best to use only single statements (as in lines #6 and #7), but semi-colons can join (short!) multiple statements into one (as in line #11).

When run, this prints:

42 is less than 50.

x = 36

Note the use of f-strings (see previous post for details).


Many languages have a selection statement that looks something like:

SELECT variable

    CASE value1
        code-block-1
    END CASE

    CASE value2:
        code-block-2
    END CASE

    :::

    DEFAULT
        code-block-X

END SELECT

It’s often called a switch-case statement. Here, variable is matched against the various case values. If one matches, that case’s code is executed. If none match, the optional default code is run.

It’s similar to an if-elseif-else construct like this:

IF variable=value1
    code-block-1

ELSEIF variable=value2
    code-block-2

:::

ELSE
    code-block-X

END IF

But variable only needs to be evaluated once (which can be convenient).

Python’s version of this is the match-case statement:

001| ### Match-Case statement…
002| 
003| def display_level (level):
004|     ”’Print level to screen.”’
005| 
006|     match level:
007|         case 0: print(‘Level Zero’)
008|         case 1: print(‘Level One’)
009|         case 2: print(‘Level Two’)
010|         case _: print(‘Level Unknown’)
011| 
012| print(“Levels:”)
013| print(“=======”)
014| display_level(1)
015| display_level(0)
016| display_level(1)
017| display_level(2)
018| display_level(3)
019| display_level(4)
020| print()
021| 

Python compares the match value (here from level in line #6) to each case statement in turn and executes the first one that matches. It ignores all others. If none match, none are executed.

The special built-in Python variable _ (a single underbar), when used in a case statement as in line #10, always matches (it provides a default case). If used, it must come at the end.

When run, this prints:

Levels:
=======
Level Unknown
Level Zero
Level One
Level Two
Level Unknown
Level Unknown

Python’s match-case statement is more powerful than first appears. It deserves a post on its own, so we’ll return to it another time.


iteration is another key characteristic of programming languages, either with loop statements or with recursive functions. Recursion is more a computer science topic than a programming language one, especially in a language tour, so we won’t get into it in this series.

This next section is about loop statements. Python offers two: the while statement and the for statement.


The while statement is like most while loops.

The basic syntax looks like this:

while condition :
    code-block

The code-block is executed while condition is true. Note an important point with while loops: the loop is infinite unless condition becomes false. So, the code-block (or some external influence) must change condition to make it false or the loop runs forever.

A common cause of lockup is forgetting code to update condition while looping!

Here’s a simple while loop in Python:

001| ### While loop…
002| 
003| ix = 10
004| 
005| # Print numbers 10 through 1…
006| while 0 < ix:
007|     print(ix)
008| 
009|     
010|     
011| 
012|     # Next…
013|     ix = ix  1
014| 
015| print()
016| 

When run, this prints the digits ten down to one. Note how line #13 ensures the loop terminates. The while clause is true so long as ix is greater than zero, and line #13 decreases ix each time, so it eventually must reach zero and end the loop.

If not for line #13, the loop would print the number 10 over and over until forcibly stopped. So, when making a while loop, the first code to add should be something that ensures the loop ends.

Here’s another way to write this:

001| ### While loop with Break…
002| 
003| ix = 10
004| 
005| # Print numbers 10 through 1…
006| while True:
007|     print(ix)
008| 
009|     
010|     
011| 
012|     # Next…
013|     ix -= 1
014|     if ix <= 0:
015|         # Exit loop…
016|         break
017| 
018| print()
019| 

Note line #13 now uses the augmented assignment form to subtract one from variable ix. It’s a shorthand way to write: ix = ix - 1. Many math operators can use this form (e.g. ix += 5 or ix *= 2 and so on).

Given the constantly True condition, this while loop runs forever, but a test in the code block combined with the break statement (lines #14 to #16) “breaks out” of the loop. After a break, the code resumes immediately after the loop’s code block.

Given we can break out of loops, sometimes we want to know when that happens (or whether the loop ran to the end). We can append the optional else clause, which executes only when the loop runs without a break:

001| ### While loop with Break and Else…
002| 
003| list_of_stuff = [21, 42, 63, 84, 99]
004| 
005| def find_item_in_stuff (item):
006|     ”’Search list for a match to item.”’
007| 
008|     ix = 0
009|     while ix < len(list_of_stuff):
010| 
011|         # Test each list item for a match…
012|         if list_of_stuff[ix] == item:
013| 
014|             # If we found it, exit loop…
015|             print(f’Found {item} at index {ix}.)
016|             break
017| 
018|         # Otherwise, next item…
019|         ix += 1
020| 
021|     else:
022|         # We ran through all the items…
023|         print(f’Item {item} not found.)
024| 
025| 
026| find_item_in_stuff(33)
027| find_item_in_stuff(42)
028| find_item_in_stuff(70)
029| find_item_in_stuff(99)
030| 

Line #3 defines a list of integer objects called list_of_stuff. Lines #5 through #23 define a function called find_item_in_stuff. Lines #8 to #23 are a while loop. Lines #21 to #23 implement its else clause.

Lines #26 through #29 call the function four times searching for different integers.

When run, this prints:

Item 33 not found.
Found 42 at index 1.
Item 70 not found.
Found 99 at index 4.

In most languages, we’d need a flag variable and a test to determine if we encountered a break. Python’s else is a handy alternative.


As an aside, there’s a more Python way to search for items in lists:

001| ### More Pythonish version of searching a list…
002| 
003| list_of_stuff = [21, 42, 63, 84, 99]
004| 
005| def find_item_in_stuff (item):
006|     ”’Search list for a match to item.”’
007| 
008|     nbr_found = list_of_stuff.count(item)
009|     if not nbr_found:
010|         # Item is not in the list…
011|         print(f’Item {item} not found.)
012|     else:
013|         # At least one, so get first index…
014|         item_index = list_of_stuff.index(item)
015|         print(f’Found {item} at index {item_index}.)
016| 
017| 
018| find_item_in_stuff(33)
019| find_item_in_stuff(42)
020| find_item_in_stuff(70)
021| find_item_in_stuff(99)
022| 

The list count method (line #8) returns the number of occurrences of an item in some list. The list index method (line #14) returns the index of the first occurrence of an item.

Note: the index method raises an Exception if the item is not on the list. This is why we use the count method to first determine whether there is at least one item.

Here’s an even better way to write it:

001| ### Even more Pythonish version of searching a list…
002| 
003| list_of_stuff = [21, 42, 63, 84, 99]
004| 
005| def find_item_in_stuff (item):
006|     ”’Search list for a match to item.”’
007| 
008|     if item in list_of_stuff:
009|         # Item exists, so get first index…
010|         item_index = list_of_stuff.index(item)
011|         print(f’Found {item} at index {item_index}.)
012|     else:
013|         # Item is not in the list…
014|         print(f’Item {item} not found.)
015| 
016| 
017| find_item_in_stuff(33)
018| find_item_in_stuff(42)
019| find_item_in_stuff(70)
020| find_item_in_stuff(99)
021| 

We use the in operator described above to determine whether it’s safe to use the index method. Note that (just for fun) the logic is reversed in the if statement.

Both these print the same thing as the while loop version above.


The for statement syntax may or may not be familiar depending on what languages you’ve used before. It does not resemble the BASIC or Fortran version:

FOR IX=1 TO 10 STEP 1
    PRINT IX
NEXT IX

[If you’re wondering why I use ix in loops rather than just i, it’s because ix is easier to see and search for, especially in lowercase. So, rather than i, j, k for nested loops, use ix, jx, kx. Or rx and cx for row and column loops.]

In Python, the basic syntax is:

for variable-name in list-like-object :
    code-block

This is the other use of the in keyword.

Here’s a simple for loop in Python:

001| ### For loop (introduction)…
002| 
003| for ix in [10,9,8,7,6,5,4,3,2,1]:
004|     print(ix)
005| 
006|     
007|     
008| 
009| print()
010| 

This is functionally identical to the simple while loop above. It prints the numbers ten down to one.

With for loops, we don’t need explicit code to ensure the loop ends. Assuming the list-like-object is finite in size (which is almost always the case), the loop terminates after iterating through the list members.

Note how the loop variable (the thing that changes each time through the loop) is part of the syntax. We saw above how while loops need a separate loop variable.

As in while loops, the break statement exits the loop early:

001| # For loop with Break…
002| 
003| for ix in [10,9,8,7,6,5,4,3,2,1]:
004|     print(ix)
005| 
006|     # Stop counting after 4…
007|     if ix == 4:
008|         break
009| 
010|     
011|     
012| 
013| print()
014| 

This example counts down from ten to four and stops.

As also in while loops, for loops have an optional else clause:

001| # For loop with Break and Else…
002| 
003| list_of_stuff = [21, 42, 63, 84, 99]
004| 
005| def find_item_in_stuff (item):
006|     ”’Search list for a match to item.”’
007| 
008|     for ix,list_item in enumerate(list_of_stuff):
009| 
010|         # Test each list item for a match…
011|         if list_item == item:
012| 
013|             # If we found it, exit loop…
014|             print(f’Found {item} at index {ix}.)
015|             break
016| 
017|     else:
018|         # We ran through all the items…
019|         print(f’Item {item} not found.)
020| 
021| 
022| find_item_in_stuff(33)
023| find_item_in_stuff(42)
024| find_item_in_stuff(70)
025| find_item_in_stuff(99)
026| 

This is functionally identical to the similar while loop earlier but cleaner and less error prone.

The continue statement jumps to the top of the loop for the next item, thus skipping the rest of the code block:

001| # For loop with Continue…
002| 
003| for ix in [10,9,8,7,6,5,4,3,2,1]:
004| 
005|     # Skip the numbers 4 & 8…
006|     if ix in [4, 8]:
007|         continue
008| 
009|     print(ix)
010| 
011|     
012|     
013| 
014| print()
015| 

This counts down from ten to one but skips eight and four.

One can use continue in a while loop, but (as always) must ensure the loop condition changes. This code runs forever:

001| ### While loop with Continue…
002| ### Note: DO NOT RUN! Code runs forever!
003| 
004| ix = 10
005| 
006| # Print numbers 10 through 1…
007| while 0 < ix:
008|     print(ix)
009| 
010|     # This makes loop infinite at 4…
011|     if ix == 4:
012|         continue
013| 
014|     
015|     
016| 
017|     # Next…
018|     ix = ix  1
019| 
020| print()
021| 

It starts by counting down from ten but goes into an infinite loop of printing four because the remainder of the code block is skipped once ix reaches four.


A for loop iterates over the items of any list:

001| # For loop iterates items in a list…
002| 
003| for item in [‘red’,‘yellow’,‘green’,‘cyan’,‘blue’,‘violet’]:
004|     print(f’{item}: {len(item)} chars)
005| print()
006| 

When run, this prints:

red: 3 chars
yellow: 6 chars
green: 5 chars
cyan: 4 chars
blue: 4 chars
violet: 6 chars

We use the built-in len function to get the length of each color name string.

Iterating through a list is nice, but what about the common case of iterating index numbers? For instance, the examples above counting down from ten to one. Must we write a list of numbers each time?

No, Python once again has our back. Let’s start with a simple example that gives us the integers zero through nine — numeric indexes for ten items:

001| # For loop with numeric indexes…
002| 
003| for ix in [0,1,2,3,4,5,6,7,8,9]:
004|     print(ix)
005|     
006|     
007| print()
008| 

This prints the numbers 0 up to 9 and presumably does something in the code block with those indexes.

But no need to write out the list. Instead:

001| # For loop with numeric indexes using Range…
002| 
003| for ix in range(10):
004|     print(ix)
005|     
006|     
007| print()
008| 

The built-in range function returns a range of consecutive integers. When used with a single (integer) argument, it returns the integers 0 to one less than the provided value. When run, this prints 0 to 9.

If we want indexes 1 through 10, we use the two-argument form:

001| # For loop with numeric indexes using Range…
002| 
003| for ix in range(1, 11):
004|     print(ix)
005|     
006|     
007| print()
008| 

In this case, the first argument is the start index and the second is (one plus) the stop index. Admittedly, this can take some getting used to — the stop value must always be one more than the last index desired.

The range function has a three-argument version:

001| # For loop with numeric indexes using Range…
002| 
003| for ix in range(10, 31, 2):
004|     print(ix)
005|     
006|     
007| print()
008| 
009| 

Where the third argument is the step size. This loop counts 10, 12, 14, … 30.

If step size is negative, the loop counts down rather than up. If we wanted to recreate the examples above that count down from 10 to 1:

001| # For loop with numeric indexes using Range…
002| 
003| for ix in range(10, 0, 1):
004|     print(ix)
005|     
006|     
007| print()
008| 

Here’s one that reverses the counting by twos example:

001| # For loop with numeric indexes using Range…
002| 
003| for ix in range(30, 9, 2):
004|     print(ix)
005|     
006|     
007| print()
008| 

This prints 30, 28, 26, … 10.


It turns out that for loops with numerical indexes aren’t common in Python. In most languages a common way to iterate over items in a list goes something like this:

001| # For loop indexing list items…
002| 
003| # A list of color names…
004| colors = [‘red’,‘yellow’,‘green’,‘blue’,‘violet’]
005| 
006| # Print all the color names…
007| for ix in range(5):
008| 
009|     # Use index to get next color in list…
010|     color = colors[ix]
011| 
012|     # Print index and color…
013|     print(f’{ix:2d}: {color})
014|     
015|     
016| print()
017| 

When run, this prints:

 0: red
 1: yellow
 2: green
 3: blue
 4: violet

The loop (lines #6 to #15) uses numerical indexes to access items in colors (line #10). This is a common pattern in many languages: a numerical index accesses items in a list or array.

The Python way iterates over the list itself and uses the built-in enumerate function to provide the indexes (if we need them; often we don’t):

001| # For loop with Enumerate…
002| 
003| colors = [‘red’,‘yellow’,‘green’,‘blue’,‘violet’]
004| 
005| for ix,color in enumerate(colors):
006|     # Print index and color…
007|     print(f’{ix:2d}: {color})
008|     
009|     
010| print()
011| 

This has the same output. Here we pass colors list to enumerate, and it returns a list of tuples in the form (index, item). The index by default starts from zero.

Note the two variable names in the for statement. Because enumerate returns two-item tuples, we can unpack each into two variables (see part 2 of this series for more on tuples). We could write this as:

001| # For loop with Enumerate…
002| 
003| colors = [‘red’,‘yellow’,‘green’,‘blue’,‘violet’]
004| 
005| for tup in enumerate(colors):
006|     # Unpack tuple into variables…
007|     ix, color = tup
008| 
009|     # Print index and color…
010|     print(f’{ix:2d}: {color})
011|     
012|     
013| print()
014| 

Where the for loop variable tup (line #5) takes the enumeration tuple and we unpack its two elements into variables ix and color (line #7). This is functionally identical to the first example (but can be handy if you need both the tuple and its members).

Just to make it clear what’s going on, this code is also functionally identical:

001| # For loop with Enumerate…
002| 
003| colors = [‘red’,‘yellow’,‘green’,‘blue’,‘violet’]
004| 
005| for tup in enumerate(colors):
006|     # Access members of tuple and assign to variables…
007|     ix = tup[0]
008|     color = tup[1]
009| 
010|     # Print index and color…
011|     print(f’{ix:2d}: {color})
012|     
013|     
014| print()
015| 

In this case, we index the tuple members directly.

Often, when printing a list of items, we want to number them from one rather than zero. The enumerate function takes an optional parameter, start, that specifies the starting index:

001| # For loop with Enumerate…
002| 
003| colors = [‘red’,‘yellow’,‘green’,‘blue’,‘violet’]
004| 
005| for ix,color in enumerate(colors, start=1):
006|     # Print index and color…
007|     print(f’{ix:2d}: {color})
008|     
009|     
010| print()
011| 

Now the list prints like this:

 1: red
 2: yellow
 3: green
 4: blue
 5: violet

Which is friendlier (plus, now the last item is the number of items).

Above we gave the range function negative step values to iterate in reverse. Python also has a built-in reversed function:

001| # For loop with Enumerate and Reversed…
002| 
003| colors = [‘red’,‘yellow’,‘green’,‘blue’,‘violet’]
004| 
005| for ix,color in enumerate(reversed(colors), start=1):
006|     # Print index and color…
007|     print(f’{ix:2d}: {color})
008|     
009|     
010| print()
011| 

When run, this prints:

 1: violet
 2: blue
 3: green
 4: yellow
 5: red

Note we reverse the colors list, then pass its output to enumerate.

If we wanted the indexes also reversed:

001| # For loop with Reversed and Enumerate…
002| 
003| colors = [‘red’,‘yellow’,‘green’,‘blue’,‘violet’]
004| 
005| for ix,color in reversed(list(enumerate(colors, start=1))):
006|     # Print index and color…
007|     print(f’{ix:2d}: {color})
008|     
009|     
010| print()
011| 

We enumerate the colors list first, then pass its output to reversed.

Now it prints:

 5: violet
 4: blue
 3: green
 2: yellow
 1: red

Note using the list class on line #5. It’s used as a constructor to create a list object from the output of enumerate — which is not exactly a list (but is list-like).

If we try the obvious method:

001| # For loop with Reversed and Enumerate…
002| # Note: THIS CODE RAISES AN EXCEPTION!
003| 
004| colors = [‘red’,‘yellow’,‘green’,‘blue’,‘violet’]
005| 
006| for ix,color in reversed(enumerate(colors, start=1)):
007|     # Print index and color…
008|     print(f’{ix:2d}: {color})
009|     
010|     
011| print()
012| 

Python raises an Exception:

TypeError: 'enumerate' object is not reversible

In modern Python, many of the built-in functions return list-like objects that are not actually lists but can be treated as lists in many — but not all — contexts. The enumerate function is one such — if we run the following code:

001| ### Enumerate objects…
002| 
003| e = enumerate([1,2,3])
004| print(e)
005| print()
006| 
007| for x in e: print(x)
008| print()
009| 
010| print(list(e))
011| print()
012| 
013| e = enumerate([4,5,6])
014| print(e)
015| print()
016| 
017| print(list(e))
018| 

It prints:

<enumerate object at 0x000001CD4503FC40>

(0, 1)
(1, 2)
(2, 3)

[]

<enumerate object at 0x000001CD4515BB00>

[(0, 4), (1, 5), (2, 6)]

When we print the returned value, we see it is a Python enumerate object that acts like a list in a list context — as in a for loop or list constructor.

Note the one-line for loop (line #7). This prints the returned tuples. As with the if statement, when the code-block is just one line, it’s often convenient to write the for loop as one line. The print on line #8 comes after the for loop.

Important: These special list-like objects are used up after providing all their elements. So, on line #10, where we pass the returned object to list, we get an empty one — the previous iteration through the object exhausted its members.

In line #13 we get a new enumerate object and convert it to a list (line #17). Now we get the entire list of tuples.

We’ll run into this often with built-in functions — they return something we can treat as a list, but which actually isn’t a list. The range function we explored above is one example:

001| ### Range objects…
002| 
003| r = range(10)
004| print(r)
005| print()
006| 
007| print(list(r))
008| print()
009| 

When run, this prints:

range(0, 10)

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Note the range object has a nicer printable string than the enumerate object (which used a Python default). When printed, it gives the range. (Remember: the stop index is one more than the last index.)

This makes it easy to produce almost any list of integers:

001| # Make a List of integers…
002| 
003| numbers = list(range(0, 100, 5))
004| 

This creates the integer list: 0, 5, 10, 15, … 90, 95.


That’s plenty for this time. Next time we start exploring Python functions.