NGA Advanced Python Programming for GIS, GLGI 3001-1

Expressions, if/else & ternary operator, and match

PrintPrint

Expressions

You are already familiar with Python binary operators that can be used to define arbitrarily complex expressions. For instance, you can use arithmetic expressions that evaluate to a number, or boolean expressions that evaluate to either True or False. Here is an example of an arithmetic expression using the arithmetic operators – and *: 

x = 25 – 2 * 3

Each binary operator takes two operand values of a particular type (all numbers in this example) and replaces them by a new value calculated from the operands. All Python operators are organized into different precedence classes, determining in which order the operators are applied when the expression is evaluated unless parentheses are used to explicitly change the order of evaluation. This operator precedence table shows the classes from lowest to highest precedence. The operator * for multiplication has a higher precedence than the – operator for subtraction, so the multiplication will be performed first and the result of the overall expression assigned to variable x is 19. 

Here is an example for a boolean expression: 

x = y > 12 and z == 3 

The boolean expression on the right side of the assignment operator contains three binary operators: two comparison operators, > and ==, that take two numbers and return a boolean value, and the logical ‘and’ operator that takes two boolean values and returns a new boolean (True only if both input values are True, False otherwise). The precedence of ‘and’ is lower than that of the two comparison operators, so the ‘and’ will be evaluated last. So if y has the value 6 and z the value 3, the value assigned to variable x by this expression will be False because the comparison on the left side of the ‘and’ evaluates to False. 

if/else & ternary operator  

In addition to all these binary operators, Python has a ternary operator, so an operator that takes three operands as input. This operator has the format 

x if c else y 

x, y, and c here are the three operands while ‘if’ and ‘else’ are the keywords making up the operator and demarcating the operands. While x and y can be values or expressions of arbitrary type, the condition c needs to be a boolean value or expression. What the operator does is it looks at the condition c and if c is True it evaluates to x, else it evaluates to y. So for example, in the following line of code 

p = 1 if x > 12 else 0

variable p will be assigned the value 1 if x is larger than 12, else p will be assigned the value 0. Obviously what the ternary if-else operator does is very similar to what we can do with an if or if-else statement. For instance, we could have written the previous code as 

p = 1 
if x > 12:  
    p = 0

The “x if c else y” operator is an example of a language construct that does not add anything principally new to the language but enables writing things more compactly or more elegantly. That’s why such constructs are often called syntactic sugar. The nice thing about “x if c else y” is that in contrast to the if-else statement, it is an operator that evaluates to a value and, hence, can be embedded directly within more complex expressions as in the following example that uses the operator twice:

newValue = 25 + (10 if oldValue < 20 else 44) / 100 + (5 if useOffset else 0) 

Using an if-else statement for this expression would have required at least five lines of code. If you have more than two possibilities, you will need to utilize the if/elif/else structure or you can implement what is called a ‘object literal’ or ‘switch case’. 

Match

Other coding languages include a switch/case construct that executes or assigns values based on a condition.  Python introduced this as ‘match’ in 3.10 but it can also done with a dictionary and the built in dict.get() method.  This construct replaces multiple elifs in the if/elif/else structure and provides an explicit means of setting values. 

For example, what if we wanted to set a value based on another value?  The long way would be to create an if, elif, else like so: 

p = 0 
 
for x in [1, 13, 12, 6]: 
    if x == 1: 
        p = ‘One’ 
    elif x == 13: 
        p = ‘Two’ 
    elif x == 12: 
        p = ‘Three’ 
 
    print(p) 
Output
One
Two 
Three

The elifs can get long depending on the number of possibilities and difficult to read. Using match, you can control the flow of the program by explicitly setting cases and the desired code that should be executed if that case matches the condition.

An example is provided below:

command = 'Hello, Geog 485!' 
 
match command: 
    case ‘Hello, Geog 485!’: 
        print('Hello to you too!') 
    case 'Goodbye, World!': 
        print('See you later') 
    case other: 
        print('No match found') 
Output 
Hello to you too!

‘Hello, Geog 485’ is a string assigned to the variable command. The interpreter will compare the incoming variable against the cases. When there is a True result, a ‘match’ between the incoming object and one of the cases, the code within the case scope will execute. In the example, the first case equaled the command, resulting in the Hello to you too! printing.

With the dict.get(…) dictionary lookup mentioned earlier, you can also include a default value if one of the values does not match any of the keys in a much more concise way:

possible_values_dict = {1: 'One', 13: 'Two', 12: 'Three'} 
for x in [1, 13, 12, 6]: 
    print(possible_values_dict.get(x, 'No match found')) 
Output 
One 
Two 
Three 
No match found 

In the example above, 1, 13, and 12 are keys in the dictionary and their values were returned for the print statement. Since 6 is not present in the dictionary, the result is the set default value of ‘No match found’. This default value return is helpful when compared to the dict[‘key’] retrieval method by not throwing a KeyError Exception and stopping the script or requiring that added code is written to handle the KeyError as shown below.

possible_values_dict = {1: 'One', 13: 'Two', 12: 'Three'} 
for x in [1, 13, 12, 6]: 
    print(possible_values_dict[x]) 

As mentioned earlier in the lesson, dictionaries are a very powerful data structure in Python and can even be used to execute functions as values using the .get(…) construct above. For example, let’s say we have different tasks that we want to run depending on a string value. This construct will look like the code below:

task = ‘monthly’ 
getTask = {'daily': lambda: get_daily_tasks(), 
            'monthly': lambda: get_monthly_tasks(), 
           'taskSet': lambda: get_one_off()} 
 
getTask.get(task)() 

The .get will return the lambda (introduced in the next module) for the matching key passed in . The empty () after the .get(task) then executes the function that was returned in the .get(task) call. .get() takes a second parameter that is a default return if there is no key match.  You can set it to be a function, or a value.

getTask.get(task, get_hourly_tasks)()

Portions of this content developed by Jan Wallgrun and James O’Brien