Python for Designers

by Roberto Arista

Fork me on GitHub

How to Make Choices

Operators, Expressions and Statements

An expression is a combination of identifiers, values and operators which computes a value. It is quite similar to an arithmetic expression, with the only difference that the scope of programming languages expressions is way bigger since they can compute any kind of data, be they numerical or otherwise. We have already delved into identifiers and values; what we miss to write proper Python expressions are now the operators. They are special symbols and keywords that instruct a computation between two values. The semantics of an operator depends on the type of its operands.

Logical Operators (Yield Boolean)

Logical operators allow to manipulate and chain boolean values. Non-boolean values can also be operands of a logical operator, but they will be implicitly converted to boolean values with the bool() data type constructor before evaluation.

not (unary negation) is used to change the state of a boolean type, from False to True, from True to False. It is like pressing a switch. "Unary" means that the operator only affects the following operand.

or is used to build a logical expression that will evaluate True if any of the operands is True. Look at the diagram: the pipe has a fork with a tap on each side. The fork is the "or" operator, and the taps are the operands; if any of the valves are open (True), the water will flow on the other side.

and (conditional and) is used to build a logical expression that will evaluate True if every operand is True. Look at the diagram: a straight pipe represents the boolean expression with two taps following each other. The only way to let the water through is to open both valves. Meaning that every operand has to be True.

and and or are also referred to as "short-circuit" operators, because the interpreter will not evaluate the entire expression if unnecessary.

Here is a table showing their behaviours:

ExpressionOutput
not TrueFalse
not FalseTrue
True and TrueTrue
False and TrueFalse
False and FalseFalse
True or TrueTrue
True or FalseTrue
False or FalseFalse

Take into account that logical operators accept any type as operands, not only Boolean. If the operand type is non-boolean, the interpreter will convert the expression to a Boolean value and only after that evaluate the logical expression. For example:

First stepSecond stepThird stepResult
0 and 5bool(0) and bool(5)False and TrueFalse
1.0 or 2.5bool(1.0) or bool(2.5)True or TrueTrue
not 0not bool(0)not FalseTrue
not 2not bool(2)not TrueFalse

and so on. Check the Boolean Type section if you have any doubt concerning the way bool() works.

Equality Operators (Yield Boolean)

To make reasonable choices, evaluating if values are the same is very important. Python can test two different notions of “sameness”, equality and identity:

OperatorsMeaning
issame identity
is notdifferent identity
==equivalent
!=not equivalent

It is essential to understand the difference between the two different notions in order to write reliable and semantic code.

Equality operators investigate the values of the objects referred; they are usually integers, strings, containers:

valueOne = 12
valueTwo = 25
valueOne == valueTwo
# False
valueOne != valueTwo
# True

someChars = 'Lemon'
moreChars = 'lemon'
someChars == moreChars
# False, Python is case-sensitive

myFirstList = [1, 'a', False]
mySecondList = [1, 'a', False]
myFirstList == mySecondList
# True

myThirdList =  [2, 'b', None]
myFirstList == myThirdList
# False

Identity operators instead, check if two identifiers refer or not to the same object. So, in different words, they check if two identifiers are pointing to the same place in memory. Let’s write a little variation of the code above:

myFirstList = [1, 'a', False]
mySecondList = myFirstList
myThirdList = [1, 'a', False]

# two aliases referring to the same object in memory
myFirstList is mySecondList
# True

# extra check
id(myFirstList) == id(mySecondList)
# True

# two aliases referring to two different objects
# with the same content
myFirstList is myThirdList
# False

# indeed they have the same content
myFirstList == myThirdList
# True

# but different identity
id(myFirstList) == id(myThirdList)
# False

So, when should you use what? When comparing numbers, text, containers content is good practice to use == and !=. Avoid is and is not, they are ambiguous and often not reliable because of caching. Instead, when comparing identifiers to singletons (True, False, None) you should always use is and is not. Why? Because there is only one instance of each of these type (therefore their identity is reliable) and makes your code more readable.

if isDark is True:
    turnLight()

Comparison Operators (Yield Boolean)

Sorting things is quite a big deal in programming. Python provides a few operators able to detect the size of two expressions:

OperatorsMeaning
<less than
<=less than or equal to
>greater than
>=greater than or equal to
x = 6
y = 9
x <  y  # True
x <= y  # True
x == y  # False
x >= y  # False
x >  y  # False
x = y = 6
x > y  # False
x < y  # False
x == y # True
x <= y # True
x >= y # True

When comparing non-integer instances, they are translated in a numerical value and then evaluated. For example, strings are converted to sequences of integers according to the position of each character into the Unicode standard.

Arithmetic Operators (Yield Floating Point or Integer)

Computers have the ability to compute arithmetical expression very quickly. Python, of course, supports the standard arithmetic operators as follows:

OperatorOperation
+addition
-subtraction
*multiplication
/division
//integer division
%modulo operator
**power of

Addition, subtraction and multiplication have a very straightforward behaviour: if both operands are integer, they yield an integer. If one of the operands is a floating point, they will return a floating point value. For example:

2 + 3.5  # 5.5
2-1      # 1
10*3     # 30

Division has two different operators which behave slightly differently. The standard division is what you would expect from it:

6 / 4  # 1.5

If you are allowed to slice units, you can proceed as follows:

The integer division instead yields the mathematical floor of the quotient:

6//4 # 1

if you can’t slice units you group 4 units only once from a collection of 6

The modulo operator yields the remainder of such division:

6 % 4 # 2

In combination with the equivalent (==) or not equivalent (!=) operator, the modulo can be really helpful in checking whether a number is even or odd, whether a number is decimal or not and so on

3 % 2   != 0 # True # 3 is odd
2.5 % 1 == 0 # False # 2.5 is not integer

Compound Expressions

Python provides the opportunity to combine more expressions at once. The result of these compound expressions relies on the evaluation of each single expression within it. The order of evaluation affects the final result, so it is very important to get a grip on precedence standard of the interpreter. For example:

5+2*8 # 21

Because of the higher precedence of multiplication over addition, 2*8 is evaluated first, then 16 is added to 5, giving 21 as result. In order to manipulate the execution order of evaluation, parenthesis can be used. For example:

(5+2)*8 # 56

Here is a concise table of the expressions evaluation order sorted by higher precedence

ExpressionsOperators
function call, slicing sequencefunc(), sequence[]
exponentiation**
multiplication, division*, /, //, %
addition, subtraction+, -
comparisonsis, is not, ==, !=, , >=
logical notnot
logical andand
logical oror
assignments=, +=, -=, /=, *=

Colon Syntax

As we already mentioned, whitespace is semantic in Python. This means that in order to group lines of code it is necessary to compose the statements which form the block accordingly. Consider the following list of words:

potatoes
groceries
eggplants
tomatoes

Here we are taking no advantage of visual variables in order to make the hierarchy more accessible to readers. We can only rely on the meaning of these words and try to detect a possible structure. So, at first sight, we detect no hierarchy, but after reading, we notice that groceries can group all the other items.

There are a few contrivances we can implement to express this structure at best using only typography. Let’s start to make some order moving groceries to the top:

groceries
potatoes
eggplants
tomatoes

Then we should find a way to associate unequivocally potatoes, eggplants and tomatoes as part of the same group. A very elegant and economic way is to move their alignment rightwards, like this:

groceries
    potatoes
    eggplants
    tomatoes

After grouping, hierarchy. A very common and shared way of tag the first line of a list as a title is to use the colon sign, like:

groceries:
    potatoes
    eggplants
    tomatoes

If we compare the final outcome with the original one, we will notice a sensible improvement. We do this all day without even noticing. We activate visual variables in order to build images with clear meanings that are easy to access to other people. It is a subtle kind of grammar, but an extremely powerful one.

The designer of Python, Guido van Rossum, decided to implement such behaviour into the Python programming language. This behaviour is mandatory, meaning that the Python interpreter expects the user to indent code semantically in order to organize code blocks. These statements need a colon as final delimiter:

if, elif, else
for
while
with
def
try, except, finally
class

The code related to the statement needs to be indented four spaces rightwards – tab is also accepted by the interpreter but not recommended – in order to be considered as the body of the construct. For example:

if index != 0:
    print('something')
print('something else')

‘something else’ is not part of the conditional construct because it falls out of the indented body. Consider also that Python allows nested structures like:

for eachElem in iterable:
    if eachElem == target:
        print 'found!'

In order to execute the conditional construct nested into the for-loop, this has to be indented four spaces more than its parent statement, which is already indented four spaces. Meaning, it has to be indented eight spaces rightwards. Just like a matryoshka.

Conditional Execution

Now that we have the tools, we need to instruct the interpreter to make reasonable choices. As we already said, it is critical while programming to be able to check whether a condition is true or not and then to be able to reroute the execution flow on different blocks of code.

Here’s a common standard diagram used to describe these patterns is the flowchart:

Python provides a control structure called “conditional construct” which allows to define whether a block of code should be executed or not. The minimal form of a conditional structure is:

if condition:
    body

Here’s a concrete example:

newPage(100, 100)
myVar = 10
if myVar < 20:
    rect(10, 10, 40, 40)

The condition is a Boolean expression. If this expression results in a True value, the indented block of code that follows will be executed. The body of the conditional construct can include other nested structures, they only need to be indented accordingly.

newPage(100, 100)
myVar = 8
if myVar < 20:
    if myVar < 10:
        fill(.5)
    rect(10, 10, 40, 40)

As soon as a statement falls out of the indented body, it is not considered part of the conditional structure anymore. For example:

newPage(100, 100)
myVar = 10
if myVar < 5:
    fill(1)
rect(10, 10, 40, 40)

In this case, whether the body is executed or not, a rectangle will be drawn on the canvas.

An else statement can be placed at the end of the conditional construct. The instructions grouped into this statement will be executed if any other statement (if, elif) of the conditional construct is not met. Here’s how it is used:

if condition:
    body1
else:
    body2

Here’s a concrete example:

newPage(100, 100)
myVar = 22
if myVar < 20:
    rect(10, 10, 40, 40)
else:
    oval(10, 10, 40, 40)

The else keyword is not followed by any condition, because it doesn’t need any evaluation. It is an interpreter parachute.

Moreover, Python allows to chain the evaluation of many conditions at once, using an indefinite number of elif (which means “else if”) statements between if (mandatory) and else (optional).

The structure is then:

if condition1:
    body1
elif condition2:
    body2
elif condition3:
    body3
else:       # all other possible cases
    body4

for example

newPage(100, 100)
myVar = 10

# chained comparison expressions!
# it is helpful when looking for
# a value included between values
if 20 < myVar:
    fill(0)
elif 20 >= myVar > 12:
    fill(.3)
elif 12 >= myVar > 8:
    fill(.6)
else:
    fill(.9)
rect(10, 10, 50, 50)

Take note of the use of indentation. if, elif and else are aligned to the left while their bodies are four spaces indented to the right.

Workbook

exercise 7.1

Write a program able to draw a circle positioned at the center of the canvas. Its size should react to a parameter "factor" between 0 and 1:

  • if 0, the diameter should be a quarter of the canvas height
  • if 1, the diameter should be three-quarters of the canvas height
  • anything in between should smoothly interpolate

Define also a variable "switch" pointing to a boolean value. A True value should create a black circle on a white background, otherwise the opposite.

>>> Solution (.py)

exercise 7.2

Draw four circles placed on the bottom left – top right canvas diagonal. Circles fill color should change from light to dark grey, following the diagonal direction. Then, assign a boolean value to an identifier called “firstLightThenDark”. This variable should allow the user to decide in which direction the circles should be drawn.

>>> Solution (.py)

exercise 7.3

Draw three dots vertically aligned in the middle of a squared canvas. Define a “distance” variable between 0 and 1. This value should push the dots away towards the canvas edges alternatively in opposite direction. Define also a “switch” variable with a boolean value assigned able to flip the dots direction.

>>> Solution (.py)