3. Statements and Packaging#
A statement is an instruction that your program can execute. In Python, you can compute, bind, or save values in different ways.
Some of the most common ways are:
Statements, for combining, printing or calculating values.
Assignments, for binding values to variables (names).
Lists and generators to store values.
Compound statements, control structures for running code conditionally or repeatedly.
Packaging, for reusing chunks of code.
1. Simple statement#
# statement 1
print(4)
# statement 2
print((5 + 6) / 2)
# statement 3
print('glucose')
# statement 4
2 == 2
4
5.5
glucose
True
Fun fact: Python does not have a rule to add three numbers together, but complex problems through binary operations (operations involving two statements).
(5 + 6 - 3) / 2
4.0
# How Python solves : (5 + 6 - 3) / 2
x0 = 5 + 6
x1 = x0 - 3
x2 = x1 / 2
x2
4.0
2. Assignment statement#
Assigning values to variables allows reusing a variable.
We use =
to bind a value to a variable.
Assigning a value to a variable is useful to write more clear and concise code since we can now use our variable everywhere the value (or simple statement) would go.
Before defining variables, the environment (or namespace) is empty. You can use %who
to check the status of your environment.
# assignment 1
au = 'Gold'
# assignment 2
total = (2 + 5 - 6) / 5
# assignment 3
elements = ['C', 'N', 'O']
elements[1]
'N'
total
0.2
%whos
Variable Type Data/Info
-----------------------------
au str Gold
elements list n=3
total float 0.2
x0 int 11
x1 int 8
x2 float 4.0
But be careful, variables \(\neq\) values!
Data objects (values) exist in memory, while variables are a reference to the value, or best a reference to the place in memory where the object is stored. This also means that we can have more than one variable referencing the same value.
# Pay attention to variables
# In the following example, temperatures and grams have the same values at the beginning,
# so we assume they will be the same in the future as well
temperatures = [20, 25]
grams = [20, 25]
temperatures = grams
# The setting of your experiment changes, and you have to use a higher quantity of grams
grams.append(100) # append is used to add items to a list
# Note that now we have a big problem in our temperatures list!
print(temperatures)
[20, 25, 100]
The built-in function id
can be helpful to check if two variables reference exactly the same value
id(temperatures) == id(grams)
True
# Use ? to get documentation of any function
id?
Be careful, variables can be overwritten!
# Define temperatures and grams
temperatures = [20, 25]
grams = [20, 25, 100]
# Print values in temperatures and grams
print(temperatures)
print(grams)
[20, 25]
[20, 25, 100]
As you can see, now temperatures
contains different values than in cell 7.
3. Lists and generators#
We will deep dive into lists and other types of data structures next week, for now, the only notion that you need to acquire is that items can be assigned to lists in multiple ways, e.g., manually (which we want to avoid since programming is all about automation), using a loop or through a list comprehension.
In the following examples, we will use different techniques to create list_gallons
.
# To avoid: create a list manually
# create original list - we will reuse this in the next cells
list_liters = [6, 9, 15.7]
# create list_gallons manually
list_gallons = [1.585, 2.3775, 4.1475]
You can index items in a list in this way - and remember, you start counting from 0 in python, so index 0 is the first item!
print(f"The first item in the list is {list_gallons[0]}")
print(f"The second item in the list is {list_gallons[1]}")
print(f"...or {list_gallons[-2]}")
print(f"The last item in the list is {list_gallons[-1]}")
The first item in the list is 1.585
The second item in the list is 2.3775
...or 2.3775
The last item in the list is 4.1475
# Creating a list in a loop
# create empty list_gallons
list_gallons = []
# loop through the elements of the original list
for item in list_liters:
# convert given value from liters to gallons
gallons = round(item * 0.2641720524, 4)
# assign resulting values in gallons to list_gallons
list_gallons.append(gallons)
print("The list contains the following elements: ", list_gallons)
The list contains the following elements: [1.585, 2.3775, 4.1475]
Another way to create a list is through list comprehension. This means that we will not use the traditional for loop syntax, but condense it into only one line.
# Creating the list through a list comprehension
# use a list comprehension to create list_gallons
list_gallons = [round(item * 0.2641720524, 4) for item in list_liters]
list_gallons
[1.585, 2.3775, 4.1475]
Generally, list comprehensions are faster than for loops, so keep this in mind if speed is a concern when writing your program. Read this tutorial if you want to know more about for loops and list comprehensions, and when to use them.
Extra: List comprehensions vs generator expressions
Note that you can also decide to create a generator instead of a list. The disadvantage of this approach is that you can only use a generator created in this way once.
You can read more about the differences between list comprehensions and generator expressions here.
# Create a generator
# create a generator instead of a list by substituting brackets with parenthesis
generator_gallons = (round(item * 0.2641720524, 4) for item in list_liters)
generator_gallons
<generator object <genexpr> at 0x000001CAE6EE33E0>
# The first time we run this loop the generator is going to return the values
for item in generator_gallons:
print(item)
1.585
2.3775
4.1475
# the second time it won't
for item in generator_gallons:
print(item)
You may be wondering, if the generator expression can’t be reused more than once, why would anyone use this instead of a normal list?
The answer is memory and time! Generators don’t construct a list object, so instead of storing the whole list in memory, they generate one item at a time and generate the next only when in demand. On the other hand, in a list comprehension, memory is reserved for the whole list. Additionally, generators are also more time efficient than list comprehensions.
4. Compound statement#
Compound statements are more complex statements that allow code in Python to be run according to a logic or order. They allow code to be run conditionally, i.e., expressions are evaluated one by one until one is found to be true; or repeatedly, i.e., code runs until a condition is satisfied (as long as the expression is true or iteration over given data structure is over). The two most widely used compound statements are if statements and loops.
Here, we will go through the basics of compound statements, which will be explored in more detail in the next Notebook 5-Control Flow.
For more resources, check out the documentation here.
if statement#
if statement
is used for conditional execution.
In plain language, an if-else
clause tells Python to run the code in order.
if
the first condition is met, then do something,if the second (
elif
) condition is true, then do something else,and so on
until reaching (if present) the last clause (
else
), if everything else before is false, then do this.
In Python syntax:
if <condition>:
⇥ <suite>
elif <condition>:
⇥ <suite>
elif <condition>:
⇥ <suite>
...
else:
⇥ <suite>
Let’s see an actual example of an if statement
.
Imagine having a barrel of water and wanting to reach steady state. In this case, the inflow has to be equal to the outflow.
# define list containing the inflow (process[0]) value and the outflow (process[1]) value
process = [10, 10]
# first clause, if this is true...
if process[0] == process[1]:
# ...then do this
print('Steady state')
# if the clause before is not true...
else:
# ...then do this
print('No steady state')
Steady state
Now try to change one element in the process
list so that the first condition is false
# Your code here
loops#
There are two types of loops used in Python: for
and while
loops.
Before looking at the cells below, can you guess the difference between a for
and a while
loop?
# Write your answer here
# (either use an # for commenting the line out or transform the cell to Markdown)
for loop#
Python syntax for the for
loop:
for <target_list> in <expression_list>:
⇥ <suite>
Now let’s look at some examples.
# Example of for loop
# define list of experiments
experiments = [
'Bubble column', 'Fluid bed', 'Crystalization',
'Ion exchange', 'Solid-liquid extraction',
]
# loop through the list of experiments we want to perform
for experiment in experiments:
# print the name of the experiments in lowercase
print(experiment.lower())
bubble column
fluid bed
crystalization
ion exchange
solid-liquid extraction
while loop#
Python syntax for the while
loop:
while <condition>:
⇥ <suite>
# Example of while loop
# loop through the list of experiments as long as
# the list contains more than 2 elements
while len(experiments) > 2:
# print the list of experiments
print('Experiments to perform: ', experiments)
# remove the last element in the list
experiments.pop()
# print the modified list
print('Only 2 experiments left: ', experiments)
Experiments to perform: ['Bubble column', 'Fluid bed', 'Crystalization', 'Ion exchange', 'Solid-liquid extraction']
Experiments to perform: ['Bubble column', 'Fluid bed', 'Crystalization', 'Ion exchange']
Experiments to perform: ['Bubble column', 'Fluid bed', 'Crystalization']
Only 2 experiments left: ['Bubble column', 'Fluid bed']
In summary:
for
loops are used to iterate over elements of a sequence.while
loops are used for repeated execution as long as the condition is true
5. Packaging#
“Packaging” your code into functions will enable you to organize and reuse code, and it makes it easy to maintain and update.
A function is defined using the syntax below. It needs to be preceded by ‘def’, it needs to be assigned a name (e.g., bar_to_pascal) and it allows you to provide some arguments (bar). “Calling the function” executes the wrapped code using the input arguments.
Functions#
# Define a function that can be reused in the future
# use the comand 'def' to define the function
# bar_to_pascal is the name that you assign to the function
# bar is the argument that you pass to your function
def bar_to_pascal(bar):
one_bar_in_pascal = 100000
return bar * one_bar_in_pascal
# call the function
bar_to_pascal(0.025)
2500.0
Note: variables defined inside a function cannot be used outside.
one_bar_in_pascal
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[25], line 1
----> 1 one_bar_in_pascal
NameError: name 'one_bar_in_pascal' is not defined
Why is it useful to create functions?
Functions allow you to reuse code. This means that your code will probably be:
Cleaner
Shorter (since you don’t have to specify long operations multiple times)
More robust (copying and pasting lines of code multiple times could introduce some errors, for example not changing one of the variables correctly.
Let’s see some examples.
The function that we defined above is quite simple and short, which means that we don’t save too many lines of code if we wanted to call it multiple times.
print(bar_to_pascal(0.25))
print(bar_to_pascal(0.05))
print(bar_to_pascal(0.02))
print(bar_to_pascal(1))
25000.0
5000.0
2000.0
100000
Compared to…
print(0.25 * 100000)
print(0.05 * 100000)
print(0.02 * 100000)
print(1 * 100000)
25000.0
5000.0
2000.0
100000
The result is the same, but you can maybe already notice that, without any context or comments, it would be very difficult to understand what operation you are trying to perform.
But what if your function was much longer?
Let’s compute the rate constant k. The initial concentration of A and B is: \(20.18 mol/m^3\). The gas can be considered ideal. The gas constant is \(R = 8.3144 Pa\cdot m^3/(mol\cdot K)\)
# define the function and arguments to be provided
def rate_constant(C0, C_ratio, t):
# constant k in hours
k_h = (1/(C0*t))*(1/C_ratio-1)
# transform it in seconds
k_s = k_h/(60*60)
return k_s
t = 1 # time in h
CA_CA0 = 0.15 # ratio of CA/CA0
C0 = 20.18 # mol/m^3
# call the function and print the rate constant found
k = rate_constant(C0, CA_CA0, t)
print(f'The constant rate k is: {k} m^3/mol*s')
The constant rate k is: 7.800168850713945e-05 m^3/mol*s
What if we now need to calculate the rate constant for an initial concentration of \(19.18 mol/m^3\), with a concentration of CA/CA0 of 0.17 in 2 hours? Than we would have to copy all the code and change the initial concentration variable!
It would look like this:
# Initial concentration of 20.18 mol/m^3
t = 1 # time in h
CA_CA0 = 0.15 # ratio of CA/CA0
C0 = 20.18 # mol/m^3
k_h = (1/(C0*t))*(1/CA_CA0-1) # constant k in hours
k_s = k_h/(60*60) # transform it in seconds
print(f'The constant rate k is: {k_s} m^3/mol*s')
# Initial concentration of 19.18 mol/m^3
t = 2 # time in h
CA_CA0 = 0.17 # ratio of CA/CA0
C0 = 19.18 # mol/m^3
k_h = (1/(C0*t))*(1/CA_CA0-1) # constant k in hours
k_s = k_h/(60*60) # transform it in seconds
print(f'The constant rate k is: {k_s} m^3/mol*s')
The constant rate k is: 7.800168850713945e-05 m^3/mol*s
The constant rate k is: 3.535477451321161e-05 m^3/mol*s
If we define a function, we can just do this:
k = rate_constant(19.18, 0.17, 2)
print(f'The constant rate k is: {k} m^3/mol*s')
The constant rate k is: 3.535477451321161e-05 m^3/mol*s