1. Basics of Python Programming#
1.1. Objectives of the laboratory#
Use a programming language and libraries available to perform probability and statistics exercises
The choice of the programming language and statistics tools is instrumental
Every physicist should be ready to change tools when needed
learning new tools quickly
understanding how libraries work and whether they are suited for the problem to be tackled
1.2. The Python programming language#
high-level: it has a strong abstraction from the details of the computer. It uses natural language elements, is easy to use and automates significant areas of computing systems like memory management
interpreted: no program compilation action is needed by the user
object-oriented: organised around data, or objects, rather than functions and logic
dynamic semantics: its variables are dynamic and can change memory size and values during execution
1.2.1. Typical uses#
Rapid Application Development: a fast development and testing of ideas and prototype, with less emphasis on planning;
Warning
Python may not be the best programming language for a big and complex fail-proof application
scripting: writing small programs or scripts that control other programs
glue language: it is able to deal with libraries compiled with different languages and use them
1.2.2. Main advantages#
simple, easy to learn syntax, readable-friendly, with a steep learning curve It also provide a standard library of functions developed by the community that covers almost all the needs of a normal user.
support of modules and packages written by other users and available to the community
1.2.3. The Python Prompt#
Once the Python program is executed from the program shell, a command line is prompted on the terminal, the so-called “Python prompt”
Simple Python commands, sets of instructions or programs may be directly typed there and executed
$ python Python 3.11.4 (main, Jun 20 2023, 16:59:59) [Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>>
The Python prompt is recognised by the
>>>
symbol and is ready to receive commands:>>> 2+2 4 >>> 7*6/2 21.0
Note
The Python prompt may be useful for quick checks, but it does not replace actual programming.
1.2.4. Programming in Python#
A computer program is a set of instructions meant to guide the computer hardware in doing calculations, by using the computing processing unit (CPU) and the memory
A program is structured accoding to rules dictated by the programming language
In this brief introduction we will see the main elements to be used to build a Python program
# variables
write information into the computer memory and read it back
functions
groups of sequences of elementary instructions into a repeatable block
control sequences
fundamental structures to handle sequences of elementary instructions (choices, iterations)
modules
libraries of fully-developed tools to implement specific behaviours
1.3. Variables#
Information needs to be handled according to its nature, in terms of occupied memory space and elementary operations
# variable
use
integer
a = 1
floating point
a = 1.0
complex
a = 3 + 2j
boolean
a = True
string
a = 'filename.txt'
None
object is not defined
Note
For a detailed list and description of built-in Python types see the official Python documentation
Python is not statically typed: variables do not need to be declared before use, nor their type to be declared
Tip
Use the
type
built-in function to know what is the current type of the object
1.3.1. Numbers#
Integers can take up to any positive or negative number in an unlimited range (subject to the available virtual memory).
Floating point numbers are implemented as the same as
double
in C (\(1.7\times 10^{\pm308}\) with 15 digits).Complex numbers have a real and imaginary part, which are a floating point number each.
>>> a = 4 >>> type(a) <class 'int'> >>> b = 5. >>> type(b) <class 'float'> >>> c = 4 + 5j >>> type(c) <class 'complex'>
Note
A number is automatically interpreted as imaginary by adding
j
(orJ
) to its valueIf needed, the functions (called constructors)
int()
,float()
, andcomplex()
can be used to generate numbers of specific type.>>> a = complex (4) >>> type (a) <class 'complex'> >>> a (4+0j) >>> b = int (5.) # this call operates a type conversion (casting) >>> type (b) <class 'int'> >>> b 5
The real and imaginary part of a complex number
z
can be accessed by callingz.real
andz.imag
and they are returned asfloat
>>> z = complex(4,5) >>> z.real 4.0 >>> z.imag 5.0 >>> type(z.imag) <class 'float'>
1.3.2. Boolean#
Booleans are variables that can only take the value
True
orFalse
They support the logical operators
or
,and
andnot
# Operation
Result
x or y
if
x
is true, thenx
, elsey
x and y
if
x
is false, thenx
, elsey
not x
if
x
is false, thenTrue
, elseFalse
the result of a comparison is a boolean
>>> 5>3 True >>> 5*3>20 False
# Operation
Meaning
<
strictly less than
<=
less than or equal
>
strictly greater than
>=
greater than or equal
==
equal
!=
not equal
is
object identity
is not
negated object identity
For a full list of comparison operators, see Table 1.4
Different operations, when not ordered with parentheses, have different priority as determined by the programming language
The comparison has lower priority than mathematical operations, but higher than the logical ones
>>> 5*3>20 and 5>3 False
Finally,
not
has a lower priority than non-Boolean operators, sonot a == b
is interpreted asnot (a == b)
, anda == not b
is a syntax error.
1.3.3. Strings#
Textual data in Python is handled with
str
objects, or strings. Strings are written in a variety of ways:single quotes:
'allows embedded "double" quotes'
double quotes:
"allows embedded 'single' quotes"
triple quotes:
'''Three single quotes'''
,"""Three double quotes"""
Triple quote strings may span multiple lines - all associated whitespaces will be included in the string literal.
>>> 'my single quote string' 'my single quote string' >>> "my double quote string" 'my double quote string' >>> """my triple quote string""" 'my triple quote string' >>> """my triple quote string ... spanning multiple ... lines""" 'my triple quote string\nspanning multiple\nlines'
Note
A string is a list of characters, when spanning multiple lines the corresponding Unicode character
\n
for the new line is added to the list
1.3.4. None
#
It represents a null value, or no value at all
Different from all other types (e.g. it’s not
True
, norFalse
)It indicates that the object is not defined and therefore occupies no space in memory. Any operation between an object that is
None
and another one returns into an error.>>> a = None >>> type(a) <class 'NoneType'> >>> a*2 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
1.4. Object containers#
Single variables may be collected in containers
Containers are by themselves variable types
Therefore, containers of containers may be built
Different types of containers exist, depending on the behaviour needed when handling the collection of variables
# container
use
example
ordered, changeable, allows duplicates, items are indexed
[1, 2, 3]
ordered, unchangeable, allows duplicates, items are indexed
(1, 2, 3)
unordered, unchangeable, does not allow duplicate values
{1, 2, 3}
items are presented in
key:value
pairs, ordered (in thekey
), changeable, does not allow duplicates (of elements with the samekey
){1:'a', 2:'b', 3:'c'}
1.4.1. Lists#
Mutable sequences of objects, typically used to store collections of homogeneous objects, but Python allows also inhomogeneous lists.
Can be constructed in several ways:
Using a pair of square brackets to create an empty list:
[]
Using square brackets, separating items with commas:
[a]
,[a, b, c]
Using the type constructor:
list()
orlist(iterable)
>>> test_list = [1,2,3] >>> type(test_list) <class 'list'> >>> test_list = [1, float (2), complex (3)] >>> test_list [1, 2.0, (3+0j)] >>> type (test_list) <class 'list'>
Elements may be added with the
append
function:>>> test_list = [1,2,3] >>> print (test_list) [1, 2, 3] >>> test_list.append (7) >>> print (test_list) [1, 2, 3, 7]
or with the
+=
operator:>>> test_list += [4,5,6] >>> print (test_list) [1, 2, 3, 7, 4, 5, 6]
the dot (
.
) betweentest_list
and the functionappend
indicates that the function has to act on that specific list
The order of the items in the list is the same given in the assignation.
There are various operations possible on the lists, as detailed in Table 1.6
# Operation
Result
x in test_list
True
if an item oftest_list
is equal tox
, elseFalse
x not in test_list
False
if an item oftest_list
is equal tox
, elseTrue
test_list_1 + test_list_2
returns a new list which is the concatenation of
test_list_1
andtest_list_2
test_list * n
orn * test_list
equivalent to adding
test_list
to itselfn
timestest_list[i]
returns the i-th item of
test_list
, starting the counting from 0test_list[i:j]
returns a sub-list, called slice, containing the elements of
test_list
fromi
toj
test_list[i:j:k]
returns a sub-list containing the elements of
test_list
fromi
toj
with stepk
len(test_list)
returns number of elements contained in
test_list
min(test_list)
returns smallest item of
test_list
max(test_list)
returns the largest item of
s
test_list.index(x)
returns the index of the first occurrence of
x
intest_list
test_list.index(x, i, j)
returns the index of the first occurrence of
x
intest_list
, at or after indexi
and before indexj
test_list.count(x)
counts the total number of occurrences of
x
intest_list
>>> a = [1,2,3] >>> print (a[1]) 2 >>> print (a[1:3]) [2, 3] >>> b = [4,5,6] >>> c = a+b >>> print (c) [1, 2, 3, 4, 5, 6] >>> print (c[1:4:2]) [2, 4] >>> print (len(c)) 6 >>> print (min(c)) 1 >>> print (max(c)) 6 >>> c = c*4 >>> print (c) [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6] >>> print (len(c)) 24 >>> print (c.index(4)) 3 >>> d += [4,3,4] >>> print (d) [2, 1, 4, 3, 6, 5, 4, 3, 4] >>> print (d.count(4)) 3
Tip
A special list is given by the type
range
, which represents an immutable sequence of numbers commonly used for looping a specific number of times infor
loops>>> list(range(4)) [0, 1, 2, 3] >>> list(range(1,8,2)) [1, 3, 5, 7]
Note
Since strings are substantially lists of characters, all the list operations can be performed on them.
1.4.2. Tuples#
They are defined by a set of objects separated by commas
When printed they are shown within parentheses
>>> test_tuple = 1, 2, 3, 'this', 'is', 'a', 'tuple' >>> print (test_tuple) (1, 2, 3, 'this', 'is', 'a', 'tuple')
Their elements can be accessed via indexing
>>> test_tuple[4]
or via unpacking
>>> a1, a2, a3, b1, b2, b3, b4 = test_tuple >>> print( a1, a2, a3, b1, b2, b3, b4 ) 1 2 3 this is a tuple
Note
Unpacking can also be performed by means of the
*
operator>>> print( *test_tuple ) 1 2 3 this is a tuple
Cannot be modified after creation, since they are an immutable type
>>> t[4]= 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
1.4.3. Dictionaries#
Define an associative array object.
Contain a set of
key: value
pairs, with the requirement that the keys are unique within each dictionaryAre indexed by keys, which can be any immutable type (often strings or numbers)
A pair of braces creates an empty dictionary:
{}
.Placing a comma-separated list of
key: value
pairs within the braces adds initialkey: value
pairs to the dictionary; this is also the way dictionaries are written on output.>>> test_dict = { 'a': [1,2,3], 'b': 'my string', 1: 0} >>> print (test_dict) {'a': [1, 2, 3], 'b': 'my string', 1: 0} >>> print (test_dict['a']) [1, 2, 3] >>> test_dict['test'] = 1+3J >>> print (test_dict) {'a': [1, 2, 3], 'b': 'my string', 1: 0, 'test': (1+3j)}
Iterations on a dictionary may be done in a similar way to the lists:
>>> for key in test_dict: print (key, test_dict[key])
alternative ways do exist
>>> for key in test_dict.keys (): print (key) >>> for value in test_dict.values (): print (value) >>> for key, value in test_dict.items (): print (key, value)
1.5. Python memory handling#
When creating a new variable or assigning a value to it, three steps comceptually take place:
Create in memory an object to contain the value assigned
Create the Python variable (if not existing)
Link the variable to the new object
Threfore, all variables are references to the actual objects saved in memory
Variables are classified in two main categories:
immutable objects cannot changed in place
mutable objects may be changed in place
# mutable
immutable
lists, sets, user-defined classes, dictionaries
int, float, bool, string, tuple
Note
When a new value is assigned to an immutable variable, the variable itself changes the object it points to
When a mutable variable is passed to a function, any modifications done inside the function propagate outside the function, to the scope where the function is called from
When an immutable variable is passed to a function,
modifications do not affect the variable outside the function
1.6. Python scripts#
A Python script is a sequence of instructions written in a text file to be executed by the python interpreter
Let the script be saved in a file called
script_01.py
, and have the form:print ('hello world')
The following shell command will ask the Python interpreter to open the script, execute it and end the execution:
$ python3 script_01.py
1.6.1. Interactive running#
It is sometimes useful to run a script in interactive mode, meaning that Python will not exit after the execution of the program. For example, in the case you want to inspect the final values of the variables:
$ python3 -i my_first_script.py 25 >>>
1.7. Functions#
Functions are a set of instructions grouped which may be called together, that produce a given output or action
They are identified with a name and set of inputs
1.7.1. an example: the print
function#
The
print
function is used to print the value of a variable on the screen at a certain point during the execution of the programIt takes as input a variable to be printed and visualises its value on the screen
>>> a = 5 >>> print (a) 5
It can also be used to print messages or any other value, since it interprets its argument as an input variable
>>> print('this is a message') this is a message >>> print(42) 42
1.7.2. another example: user input#
The program can be instructed to accept an input from the user and store it into a variable by means of the
input ()
function.input
takes a string as argument, representing the message that will be printed on screen to instruct the user.>>> a = input('insert a number: ') insert a number: 2 >>> print (a) '2'
By default
input
returns a string, and casting should be used to interpret it as a different variable>>> a = int (input ('insert a number:'))
1.7.3. User-defined functions#
The keyword
def
introduces a function definition that is used to define new functions implemented by usersIt must be followed by the function name, its arguments within parentheses and a colon (
:
)def squared (x) : return x*x
The commands inside the function need to be written displaced towards the right by a fixed shift
This operation, called indentation, is the way chosen in Python programming to define a scope, which is a set of instructions at the same logical level
Warning
Indentation shall be generous (2 spaces at least)
Intentation shall be done always with the same character (TABs and spaces are not the same thing)
The function may or may not return something where it has been called
In case it does, it may return a single object or a sequence of them (interpreted as a tuple)
>>> def first_three_powers (x) : ... return x, x*x, x*x*x ... >>> first_three_powers (2) (2, 4, 8)
1.7.4. Functions with arbitrary number of arguments#
It is possible to define a function with an arbitrary number of arguments by using the
*args
and**kwargs
syntax.In this case, the
*
operator is used to unpack a list or a tuple into a function call, so that each element of the list is passed as a different argument to the function.Similarly, the
**
operator can be used to pass keyword arguments in the form of a dictionary.def my_function(*args, **kwargs): print(args) print(kwargs) my_function( (1,2,3), a = 1, b = 'wow', c= (4,5,6)) ((1, 2, 3),) {'a': 1, 'b': 'wow', 'c': (4, 5, 6)}
Warning
Defining functions with an arbitrary number of arguments is a powerful feature, but it should be used with care. All the operations that are performed on the arguments inside the function body should be valid for all the possible types of arguments that the function can receive.
1.7.5. Functions in python scripts#
When a program gets some structure, it’s wise to write it within a script, so that it can be carefully edited before execution
For example, several functions may be defined in the same script
To let the Python interpreter know what to do when a script is called, the main sequence of instructions follows the statement
if __name__ == "__main__":
def squared (x) : return x*x if __name__ == "__main__": number = float (input ('insert a number:')) print ('the square of ' + str (number) + ' is: ' + str (squared (number)))
Tip
When writing a script, it is advisable to define a
main ()
function that will be executed when the script is calleddef main () : <your code statements here> return #------------------------------- if __name__ == "__main__": main ()
1.7.6. Documenting function behaviour#
The first statement of the function body can be a string literal, in which case it will be interpreted as the documentation of the function that can be accessed by the built-in function
help
.>>> def squared (x): ... '''calculates the square of a number''' ... return x*x ... >>> help (squared)
The code above will open a new page with the documentation of the function:
Help on function squared in module __main__: squared(x) calculates the square of a number (END)
To exit, press
q
.
Documentation styles
Since in the Python language the information is implicit (as the type of the variables), special care has to be put in documenting the source code: explain what is the purpose of the function in a concise way and describe the arguments with their type, as well the expected result type.
def squared(x): """calculates the square of a number Args: x (float): a number Returns: (float): the square of x """ return x*x
The output of
help(squared)
is in this case much more helpful.Help on function squared in module __main__: squared(x) calculates the square of a number Args: x (float): a number Returns: (float): the square of x
1.7.7. Variables inside functions#
Variables defined in the scope of a function, called local, are not visible outside it unless they are declared as
global
.>>> def test(x): ... y = 4 ... return x ... >>> test(1) 1 >>> y Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'y' is not defined >>> def test(x): ... global y ... y = 4 ... return x ... >>> test(1) 1 >>> y >>> 4
On the other side, a function may access and manipulate objects defined outside its scope and not passed as arguments
>>> a = 8 >>> def test(x): ... return a*x ... >>> test(2) 16
Whenever a variable is defined within the scope of the function with the same name as a variable outside its scope, it behaves as a new local variable.
>>> a = 8 >>> def test(x): ... a = 4 ... return a*x ... >>> test(1) 4 >>> a >>> 8
1.8. Control Flow Tools#
1.8.1. Checking conditions: if
and else
statements#
It is quite common in programming to let the program execute a set of commands only when a condition is met. This is possible in Python with the
if
statement.>>> a = 3 >>> if a > 0 : ... print('a is positive') ... a is positive >>>
As in the case of functions, instructions following an
if
statement shall be indented
If different commands should be executed depending on the value the condition, the
else
statement comes to support.>>> a = -3 >>> if a > 0: ... print('a is positive') ... else: ... print('a is negative') ... a is negative
Multiple cases are covered by using the
elif
statement.>>> a = 0 >>> if a > 0: ... print('a is positive') ... elif a < 0: ... print('a is negative') ... else: ... print('a is zero') ... a is zero
1.8.2. The for
loop#
Repetitive operations can be executed by means of the
for
andwhile
loops.The
for
loop executes the same set of instructions for all the elements of a list of objects>>> for elem in [0,1,'a','abc',5.] : ... print (elem) ... 0 1 a abc 5.0
The variable
elem
can be used within the loop and assumes, one by one, the value of each element present in the listLoops over integer indices are usually performed using the
range
function:for i in range (3): print ('iteration ' + str (i))
As in the case of functions, instructions following a
for
statement shall be indentedThis allows, for example, to uniquely identify scopes in nested loops
for i in range (3): print ('before the internal loop in iteration ' + str (i)) for j in range (3): number = 3 * i + j print (number) print ('end of internal loop')
1.8.3. The while
loop#
The
while
loops execute a set of instructions as long as a condition is fulfilled>>> i = 3 >>> while i > 0: ... print(i) ... i -= 1 ... 3 2 1
1.8.4. Exceptional loop interruction instructions#
Sometimes it is useful to alter the behaviour of loops independently of the conditions present in the
for
orwhile
statementsThe
continue
instruction interrupts the execution of the instructions in the scope and jumps to the following iterationThe
break
instruction interrupts the execution of the iteration and exits the loop
1.9. Modules#
Collections of functions to be used in many programs may be collected in libraries or modules that can be imported in scripts
1.9.1. An example: reading the command line#
When a script is executed, it’s very practical to provide input information in the same command line:
$ python3 script_03.py 3 the square of 3.0 is: 9.0
The library that is used to read the input line is called
sys
import sys def squared (x) : return x*x if __name__ == "__main__": number = float (sys.argv[1]) print ('the square power of ' + str (number) + ' is: ' + str (squared (number)))
The instruction
import
allows to import thesys
library ready for useThe
sys.argv
list contains all the words written afterpython3
1.9.2. User-defined libraries#
A library is a text file containing a collection of functions, usually placed in the same directory of the scripts which use it
import sys from operations import squared if __name__ == "__main__": number = float (sys.argv[1]) print ('the square power of ' + str (number) + ' is: ' + str (squared (number)))
where in this case
operations
isdef squared (x) : """calculates the square of a number Args: x (float): a number Returns: (float): the square of x """ return x * x
import sys from operations import squared if __name__ == "__main__": number = float (sys.argv[1]) print ('the square power of ' + str (number) + ' is: ' + str (squared (number)))
Note
Adding the following term to the library allows to run the library as a script for testing purposes
if __name__ == "__main__": number = 3. print ('the square power of ' + str (number) + ' is: ' + str (squared (number)))