Complex programs usually consist of the execution of a sequence of commands. To control which command will be executed at a certain point, we use control statements. In all programming languages, we can find two basic control statemets: one that creates loops and one that enables conditional choices.
if
statemet¶The if
statement is responsible for branching in most programming languages. With the help of if
, we can determine for our program which branch to follow if one or more conditions are fulfilled. Let us see an example:
if 2+2==4:
print('Mathematics still works.')
Attention, we've indented the second line! Indentation is Python's notation for grouping commands.
A common convention is to use four spaces as indentation for code that is at a lower syntactical level. It means that using two subsequentif
statements results in eight spaces as indentation for the code block under the second if
. This indentation is automatically done in the code cells of a Jupyter Notebook. Pressing Enter
after the :
mark, the next line will be indented by 4 spaces, pressing further Enter
keys keeps the level of this hierarchy. I we want to end the indented block, and go up one level, we have to delete the indentation.
In the C, C++, Java languages, we use curly brackets for grouping code blocks. An example:
if (i==10) {
print i
}
In Fortrain, the word END marks the end of a code block.
If we copy code from another code editor, we have to pay attention, because it may use TABs instead of four spaces for indentation. TABs are sometimes allowed, but are to avoid. Modern Python style guidelines suggest that every indentation should consist of 4 spaces.
today='Monday';
time='12:00';
if today=='Monday':
if time=='12:00':
print('Let\'s do Python!')
We can tell the program what to do in case the condition is False
with the else
and elif
commands.
x = 1
if x < 0:
x = 0
print('Negative, I\'ve changed it to zero.')
elif x == 0:
print('Zero.')
elif x == 1:
print('One.')
else:
print('More than one.')
else
and elif
are optional. There may be only one else
branch, and one or more elif
branches. elif
is an abbreviation for 'else if', and it is very useful for avoiding unnecessary indentation. An if
... elif
... elif
block subtitutes the switch
and case
commands of other programming languages.
for
and while
loops¶One of the best properties of computers is that they are very fast and 'indefatigable'. They are most eective in solving tasks where the problem is relatively easy, but its execution needs numerous repetition or iteration. Iteration can be or example when our program goes through every element of a list, and performs operations on every element. This is what Python's for
statement is used for.
days_of_the_week = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]
for day in days_of_the_week:
print(day)
This piece of code goes through every element of the days_of_the_week
list, and assigns the visied elements to the day
variable, that is also called the loop variable. After that, it executes every command that is in the indented codeblock, which for now is only the print
statement. For these commands, it may use the loop variable. After the end of the indented block, it terminated the loop.
Calling the loop variable day
has no significance whatsoever. The program doesn't know anything about human time, or that in a week, there are days, not kitties:
for kitty in days_of_the_week:
print(kitty)
The codeblock of a loop may consist of more than one commands:
for day in days_of_the_week:
statement = "Today is " + day
print(statement)
The range()
command may be used if we want to execute a given number of operations in a for
loop.
for i in range(20):
print(i," times ",i ,"equals to",i*i)
Our programs are becoming more and more interesting, if we combine loops with conditional branches from above:
for day in days_of_the_week:
statement = "Today is " + day
print(statement)
if day == "Sunday":
print (" Sleep in")
elif day == "Saturday":
print (" Do chores")
else:
print (" Go to work")
Note how for
and if
are embedded into each other!
Similarly to the for
statement, we can use the while
statement for creating loops. Let us observe an example:
i=0
while i<10:
print(i)
i+=1
Attention! I we are not careful enough, our while
loop might result in an infinite loop! For example, the cell below, if we do not interrupt it with the Interrupt command from the Kernel menu (or by pressing I
two times outside o editing mode), never stops!
while 1==1:
print('Allitsák meg a világot, ki akarok szállni!!')
If we complemet the while
loop with an else
branch, we can write a piece of code that executes if the condition of the while
statement is no longer True
.
i=0
while i<10:
print(i)
i+=1
else:
print('THE END')
Similarly to other languages, the break
interrupts the deepest for
or while
loop.
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(n, 'is factorizable, e.g.:', x, '*', n/x)
break
else:
print(n, 'is a prime.')
More on the break
command and other control statements in English.
n = 10 # number of elements we want to compute
sequence = [0,1] # the first two elements
for i in range(2,n): # numbers from 2 to n, n msut not be less than two!
sequence.append(sequence[i-1]+sequence[i-2])
print (sequence)
Let us go through the code step by step. First, we set the value of n to 10, which is the length of the sequence to compute. We call the list that is going to store the series sequence
, and initialize it with the first two values. After this job done 'by hand', we automate the process by iteration.
We begin the iteration with 2, which is the 3rd element due to the fact that indexing begins with 0. We are going to go until n, that has been previously given.
In the body of the loop we append the sum of the previous two elemets, that have already been calculated. After the end of the loop, we write the results.
Let us see the same example with a while
statement:
sequence=[0,1]
while len(sequence)<10:
sequence.append(sequence[-1]+sequence[-2])
print (sequence)
In many programming languages, it is common practice to group several commands into so-called functions. Well written functions make the usage of the programs easier, and the code will be much more readable.
If we want to create a Fibonacci series with different number of elemets than previously, we could copy our code into a new cell, and we could rewrite n=10
to n=100
, for example. But it is a much more effective method to define this piece of code as a new function with the def
command:
def fibonacci(sequence_length):
"The first *sequence_length* elements of the Fibonacci sequence." # this is a 'help', the so-called docstring
sequence = [0,1]
if 0 < sequence_length < 3:
return sequence[:sequence_length]
for i in range(2,sequence_length):
sequence.append(sequence[i-1]+sequence[i-2])
return sequence
Now we can call the function fibonacci()
for different lengths.
fibonacci(5)
Let us analyze the above piece of code. The colon mark and the indentation define which commands belong to the function definition. In the second line, there is a string between quotation marks as a comment. This is the so-called docstring, that explains the function briefly, and can be later evoked by the help
command.
help(fibonacci)
In Jupyter Notebooks, docstrings can be read by using a question mark:
?fibonacci
We can view docstrings in the Jupyter Notebook by pressing SHIFT+TAB while typing the parentheses of the function call. Try it in the next cell without running the code! If you press TAB several times while holding the SHIFT key, you can put the docstring out to the lower half of your browser.
fibonacci()
The output of a function is determined by the return
keyword. If we don't use a return
command, then our function returns with a None
value. After running a function that has not executed a return
, it also returns None
. The function fibonacci()
returns with a list.
x=fibonacci(10)
x
This function returns None
.
def empty_function(x):
print('I am an empty function, though I print stuff!')
y=x-2;
Therefore, there is no value in the variable z
.
z=empty_function(3)
z
print(z)
A function might have more input values, so-called arguments.
def summing(a,b):
return a+b
We can get more than one return values:
def plusminus(a,b):
return a+b,a-b
p,m=plusminus(2,3)
print (p)
print (m)
A function may have several input values, and the input values of a function may be arranged into a list by another function. A typical example is a least squares fit, where we can pass function parameters by unpacking a list with an * mark in a very compact way, as we will see later. Let us see an example for fitting a fifth-order polynomial:
$$f(x)=a_0+a_1x+a_2x^2+a_3x^3+a_4x^4+a_5x^5$$Let us define a function, that receives the variable x, and the six parameters $a_0,\dots,a_5$ if the above polynomial:
# this is the function to fit
def poly5(x,a0,a1,a2,a3,a4,a5):
return a0+a1*x+a2*x**2+a3*x**3+a4*x**4+a5*x**5
We have six parameters to fit, that are given by the fitting function as a list:
# these are the fitted parameters
# according to the following sequence
# params=[a0,a1,a2,a3,a4,a5]
params=[ 2.27171539, -1.1368942 , 0.65380304, -0.25005187, -0.1751268 , -0.48828309];
If we want to evaluate our polynomial at 0.3, we can do it in the following way:
poly5(0.3,params[0],params[1],params[2],params[3],params[4],params[5])
or more compactly:
poly5(0.3,*params)
This construction makes it possible to prepare the function for receving a varying number of input values. If we put an * mark before a parameter in the function definition, that parameter can be of any length! Let's see an example:
def i_give_what_i_receive(*argv): # we prepare the function to receive
# any number of parameters
print("I have ",len(argv),"input arguments.")
for arg in argv:
print ("This is an argument:", arg)
return argv[-1] # we refer to input arguments as the elements
# of a common list
The function in the above code cell can receive any number of parameters. It prints the number of parameters, their values, and returns the last input parameter.
i_give_what_i_receive('Caspar','Melchior','Balthazar')
Of course, we can use the 'unpacking' of an arbitrary list as parameter:
i_give_what_i_receive(*params) # unpacking
Apart from dicionaries being very useful data structures in themselves, we will see later, that it is common to use them as function parameters. These parameters are called keyword arguments. They can make the code much more readable to the human eye, and they give more flexibility while programming.
# setting default values
def students(time, state='paying attention to the teacher', activity='experimenting', lesson='physics'):
print("These students are "+state+" while "+activity+" during a "+lesson+" lesson!");
print("The current time is",time,"!");
It is compulsory to give the first parameter of the function. If we don't give any more, then it is going to use the default values for the keyword arguments.
students('5PM')
If it receives one keyword argument, it uses only that one as different from the default values.
students('8AM',state='pulling faces')
We don't have to pay attention to the order of the keyword arguments:
students('8AM',lesson='Latin',state='panicking',activity='writing an essay')
If we give a keyword that has not been declared in the function definition, we get an error.
students('5PM',teacher='Miss Smith')
Similarly, we get a problem if we use a keyword two times:
students('8AM',lesson='Latin',lesson='chemistry')
We can unpack the dictionary of keyword arguments with a double **.
student_dict={'lesson':'music','state':'whining'};
students('12AM',**student_dict)
Just as in the case of simple arguments, it may happen that we would like to process a dictionary of arbitrary length. or example, if we define a function that calls several other functions, for which we would like to pass some of the incoming parameters.
The function below expects an arbitrary dictionary as an input, inspects its length, and if there is a keyword argument 'hamburger', it returns its value.
def check_hamburger(**dictionary):
print('The length of the dictionary:',len(dictionary))
if 'hamburger' in dictionary:
print('There is hamburger!')
return dictionary['hamburger']
check_hamburger(macaroni=1,cake='delicious') # we can give whatever keywords, even if not predefined!
eat={'macaroni':1,'cake':'finom','hamburger':137,'salad':None}
check_hamburger(**eat)
We can give parameters to functions in many ways:
We may use all types in one single function definition. Use the following order:
def difficult_function(var1,var2,var3='ELZETT',*args,**kwargs):
if ((len(args)==0 and len(kwargs)==0)):
return var3+str(var2)+str(var1)
elif (len(args)!=0 and len(kwargs)==0):
return 'There is something in args!'
elif (len(args)==0 and len(kwargs)!=0):
return 'There is something in kwargs!'
else:
return 'We have all possible kinds of arguments: '+str(var1)+str(var2)+var3+str(args)+str(kwargs)
The first two arguments of the above function are 'simple' arguments, the third is a keyword argument with a default value of 'ELZETT', and then we can have an arbitrary number of 'simple' arguments (args
) and keyword arguments (kwargs
). Let us observe some simple behaviours of this function:
difficult_function(1,2)
difficult_function(1,2,var3='MULTLOCK')
difficult_function(1,2,*days_of_the_week)
difficult_function(1,2,**eat)
difficult_function(1,2,*days_of_the_week,**eat)
We can give not only variables, but other funtions as inputs for functions. For example, we may think of a function, that plots mathematical functions. In this case, when a function expects a function as an input, it is possible, that it would be a tedious task to define all possible input functions. Then, it is much more compact to use lambda
functions.
Let us see an example. Let us define a function that evaluated another function at a given place, outputs that place and returns the value at that place.
def funfun(g,x):
print('This is the place: ',x)
return g(x)
def fx(x):
return x**2-1/x;
funfun(fx,0.1)
Then, we can omit the definition of fx
by using the following abbreviation:
funfun(lambda x:x**2-1/x,0.1)
In the above expression, this piece of code
lambda x:x**2-1/x
defines an anonymous function, that assigns $x^2-1/x$ to the variable $x$.
We can also use multi-variable functions in lambda
functions:
This expression
lambda x,y:x**2-1/y
is equivalent to the following function:
def fxy(x,y): return x**2-1/y