This chapter contains general Python programming tips about Pythonic style and readability. It’s commonly believed that if a program is not beautiful, it is also incorrect (for some unrelated reason). Pythonic programs are beautiful and, therefore, less likely to be incorrect. By following these tips, we can make our programs beautiful.

Chain comparison operators

Try to recall these common algebraic statements: x<y<zx < y < z

In Python, we can use chain comparison operators.

x, y, z = 5, 10, 12
x < y < z

We can even write:

x < y <= z != w

An expression like this is evaluated by first evaluating each comparison operator separately and then taking the and of the results:

x < y and y <= z and z != w

We could even evaluate bizarre expressions like x < y > z (check if yy is greater than both xx and zz), but they are confusing and better avoided.

Try the exercise below yourself!

x=input('Enter value of x ')
y=input('Enter value of y ')
z=input('Enter value of w ')
w=input('Enter value of z ')
result = 0
# Write your code to evaluate the expression and print the result
# x < y and y<= z and z! = w
print(result)
Evaluating the chain comparison

Compact the code with conditional expression

The conditional expression, x if cond else y, with the conditional operator, if-else, is a replacement for the conditional statement. The value of the expression is x if the condition, cond, is true, otherwise it is y.

Note: The conditional statement doesn’t have a value as such because it isn’t an expression. Only expressions have values. The following two code fragments are equivalent:

# Conditional statement
if cond:
value = x
else:
value = y
# Conditional expression
value = x if cond else y

On the bright side, the conditional expression is much more compact (a one-liner). Unfortunately, the conditional statement allows for more than one line in each branch, if necessary. In a sense, conditional expressions to conditional statements are what lambda functions are to real functions

The conditional operator is best when we must use one expression, like in a list comprehension. The following list comprehension converts the words that start with a capital letter to the uppercase (assuming that they have at least one character):

WORDS = 'Mary had a little Lamb'.split()
[(print(word.upper()) if word and word[0].isupper() else print(word)) for word in WORDS ]

Note that moving the condition check to the list comprehensive would solve a different problem by converting the words to the uppercase and removing all other words:

[print(word.upper()) for word in WORDS if word and word[0].isupper()]

Most Python operators require two operands (binary operators). Similarly, some operators (-, +, ~, not, await) are unary, while the conditional operator is ternary.

Find the missing switch

Unlike C, C++ or Java, Python doesn’t have the switch statement. This statement lets us choose which statements to execute based on the value of a discrete variable. For example, the following C language statement prints some self-explanatory messages:

int choice = ...; // Initialize the variable
switch(choice)
{
case 0:
puts("You chose zero.");
break;
case 1:
puts("You chose one.");
break;
case 99:
puts("You chose ninety-nine.");
break;
default:
puts("You chose something else.");
}

There is no direct matching statement in Python. We can replace it with a cascaded conditional statement, however:

choice = 0 # Initialize the variable
if choice == 0:
print('You chose zero.')
elif choice == 1:
print('You chose one.')
elif choice == 99:
print('You chose ninety-nine.')
else:
print('You chose something else.')

As a bonus, we can now use other comparison operators, such as <= and !=, to describe the conditions that can’t be described in the original switch. They’ll make the switch and the cascaded statement incompatible, however.

A notable special case of the switch statement is when we call different functions in response to different values. For example, in a two-party game, we may call make_computer_move() if the value is 0, make_human_move() if the value is 1, or report_error() for all other values. Here’s the equivalent C code:

switch(choice) {
case 0:
make_computer_move();
break;
case 1:
make_human_move();
break;
default:
report_error();
}

Conversion of functions into dictionary

We can translate this fragment into Python by arranging the choices and functions into a dictionary:

actions = {0: make_computer_move, 1: make_human_move}
actions.get(choice, report_error)()

The dictionary contains the function references, not the function calls (note the absence of parentheses).

A function identifier is a reference to that function, a claim that the function has been defined and exists somewhere in the interpreter’s memory. When we mention an existing function’s identifier on the command line, the interpreter confirms that the function exists. It also tells us the address of the function in the RAM, but it’s not relevant.

The dict.get(key,fallback=None) method looks its first argument key up in the dictionary. If the key is found, the expression calls the matching function. Otherwise, it calls the fallback function. We can easily extend the dictionary to handle as many cases as necessary. This trick works best if all functions take the same number of parameters (including none). If they don’t, we can prepare a list of suitable positional parameters (possibly empty) and a dictionary of suitable keyword parameters (also possibly empty) and pass them to the chosen function:

actions.get(choice, report_error)(*positional, **keyword)

Let’s try Python’s dictionary instead of switch.

choice = int(input("Enter your choice in integer"))
actions= {0: 'you chose zero',1: 'you chose one', 99: 'you chose ninety-nine'}
print(actions.get(choice))
Using Python dictionary to perform working similar to switch statement

Pass it

Some compound statements in Python (for example, class, for, while, if, and try) require that their body contains at least one statement, and we must provide that statement. In C, C++, and Java, we’d use an empty statement, a block of two curly braces {}. Python doesn’t use curly braces (at least not for this purpose). Instead, it uses indentation. The problem with an empty statement defined through indentation is that it’s invisible:

class EmptyClass:
# Is there an empty statement here? Can you see it?

Python provides an equivalent of an empty statement called pass. We use pass when we must use a statement, but it’s not relevant:

class EmptyClass:
pass

We could use a string literal instead, something like “Do nothing here,” but that defies our rule that there should be one obvious way to do it.

Try it

There are two programming approaches to coding an operation that may fail: an optimistic and a pessimistic one.

  • The pessimistic approach endorses safety checks if an operation may cause a problem before engaging in the operation. We use this if the check is considerably less expensive than handling an exception, or if the failures are frequent and costly to repair. It is cheaper to avoid them by checking some preconditions.

  • The optimistic approach implies that failures are rare. It’s cheaper to try and then recover if anything goes wrong by using the exception handling mechanism (try/except). One of the most illustrative examples of an optimistic scenario is checking whether a string represents a number…

String conversion

Though not trivial, it is possible to describe a valid string representation of a floating-point number with an exponential part by using a regular expression. It is much easier to pretend that a string represents such a number and attempts to convert it, however. If the conversion is successful, the string is a number. Otherwise, it is not.

def string_to_float(s):
  try:
    return float(s) 
  except ValueError:
    return float('nan')

Let’s pass a string to the above function and observe the output.

print(string_to_float("3.2"))

Opening a file

Another common application of the optimistic method is by opening a file for reading. We can open a file for reading if it exists, is a file (and not a directory), belongs to us, and is readable. We can replace this sequence of checks with one painless attempt to open the file. If the file opens, it is openable. Otherwise, it is not:

try:
with open('somefile') as infile:
# Do something with infile
except (FileNotFoundError, IsADirectoryError, PermissionError):
# Do something else

In case we anticipate some other file-related errors, we add them to the tuple, but try to avoid the blanket exception handlers.

The choice to be an optimist and try or a pessimist and check depends on the complexity of the check, the penalty of trying, and our programming philosophy. The former two can be estimated. The latter depends on how it was taught.

Quiz on comparison and conditions

1

What would be the answer after evaluating the expression x==y and x!=z and x>w

where x=5, y=5, z=6, and w=4?

A)

false

B)

true

Question 1 of 40 attempted