010 Getting Started with Python#
COM6018
Copyright © 2023, 2024 Jon Barker, University of Sheffield. All rights reserved.
1. Introduction#
What is Python ?#
A modern, object-orientated, high-level programming language
Easy-to-learn: simple syntax and intuitive code
Expressive: can do a lot with a few lines of code
Interpreted: no need to compile
Dynamically typed: no need to define the types of variables
Memory managed: no c-style ‘memory leak’ bugs
Note
Notes require no arguments, so content can start here.
Why Python ?#
Why are we using Python in COM6018?
Python is widely used in the scientific computing community
Extensive ecosystem of rapidly maturing libraries
Good performance - closely integrated with time-tested C and Fortran code
blas, atlas, lapack etc
No license costs (i.e., free!) and easy to install
It’s a useful language to know, i.e. useful outside of COM6018
2. Running Python code#
There are many ways to run Python code including running scripts from the command line, using Jupyter notebooks (which we will be using in the labs), or working interactively in a Python shell (e.g. ipython
).
2.1 Using the iPython interactive shell#
If you are reading these notes at a computer then the easiest way to try out the examples is to cut and paste them into a Python shell.
To start a Python shell, open a terminal and type python
or ipython
(if installed).
%%bash
ipython
The IPython shell is an enhanced version of the standard Python shell with many useful features including tab completion, command history, and object introspection.
If you are viewing these notes in a web-browser there will be a button in each code block that copies the contents to the clipboard when clicked. You can then paste the code into your Python or IPython shell. Try this with the Python code block below.
print('Hello world!')
Hello world!
If you are reading these notes in the VSCode editor (the free editor that we are recommending for the module) then you can install the Markdown Preview Enhanced editor extension and run the Open Preview
command. Now, if you hover over a cell, a button will appear that allows you to run the contents directly with the output appearing below the code block.
2.2 Running Python scripts#
For longer programs, Python code is usually stored in a file and run from the command line. Programs are stored in files with a .py extension.
For example, a program to print Hello world!
could be stored in a file hello.py
with the following contents
# A program to print 'Hello world!'
print('Hello world!')
Hello world!
This program could then be run from the command line using
%%bash
python code/hello_world.py
2.3 Loading Modules#
When you write a Python program you rarely start from scratch. Instead you build on library code that others have already written. This code is organised into modules. Many of these modules come with the language and are part of the so-called Python Standard Library. Other modules are available from third-parties and can be installed using the pip
package manager. In this course we will be using some very popular third-party modules which are industry-standard for data science, including numpy
, scipy
, matplotlib
, and pandas
.
When using code from a module, the module first needs to be imported. Once a module is imported it will define a namespace which contains all the module’s functions and variables. To access these functions and variables you need to prefix them with the name of the module.
For example, the sqrt
(square-root) function is defined in the math
module which
is part of the Python Standard Library.
In the example below, we use the keyword import
to import the math
module. We can then access the sqrt
function using the syntax math.sqrt
. We then use the Python builtin print
function to print some result to the screen.
import math
print(math.sqrt(12.0))
print(math.sqrt(12.0))
3.4641016151377544
3.4641016151377544
Once a module is imported you can get help about it by using the help
function.
import math
# help(math)
You can also get help on a specific module function. For example to see help for the sqrt
function we can use,
import math
help(math.sqrt)
Help on built-in function sqrt in module math:
sqrt(x, /)
Return the square root of x.
2.4 Importing specific functions#
Sometimes you only want to import a specific function from a module. This can be done as follows,
from math import cos # import cos into the namespace
x = cos(0.4) # no need to write math.cos
print(x)
0.9210609940028851
Note, how in the example above we no longer need to prefix the function with the module name when we use it. This is because the from X import Y
syntax imports the function Y
directly into the current namespace.
It is possible to import all the functions from a module into the current namespace by
using the wildcard *
syntax.
from math import * # import all functions
x = sqrt(4.0)
print(x)
2.0
:fire: Importing like this may seem convenient, but it is not recommended. It can lead to namespace clashes, i.e., different modules might have used the same name for their own version of a function. If both are imported it can be difficult for the reader to know which function is the one being used.
3 Variables and Types#
Variables are dynamically typed, i.e., in contrast to languages like Java or C there is no need to explicitly declare the type before the variable is used. For example, we can assign an integer value to a variable simply by writing,
a = 1
print(type(a))
<class 'int'>
In the code above, we use the type
function to return and print the type of the variable a
and we see that it is an int
(i.e., integer).
Alternatively, if we write,
b = 1.0
print(type(b))
<class 'float'>
then we will see that the returned type is float
(i.e., floating point). The type has been inferred from the type of the literal value that we assigned to the variable: 1.0
is a float because it has a decimal point whereas 1
is an integer because it does not.
Now let us see what happens if we assign a new value to a variable.
b = 1.0
print('b is initially of type', type(b))
a = 1
b = a
print('b is now of type', type(b))
b is initially of type <class 'float'>
b is now of type <class 'int'>
Note how the type of the variable b
has changed from float
to int
after we assigned it the value of a
. This is what we mean by dynamic typing. A variable can change type during the execution of a program.
3.1 Numeric types#
A variable is of type int
(i.e., integer) if it is assigned a value without a decimal point
x = 1
print(type(x))
<class 'int'>
It will be of type float
(i.e., floating point) if it is assigned a value with a decimal point
x = 1.0
y = 5.
print(type(x), type(y))
<class 'float'> <class 'float'>
Variables are of type bool
(i.e., Boolean value) if assigned the value True
or False
x = True
y = False
print(type(y), type(x))
<class 'bool'> <class 'bool'>
There is a builtin type for complex
numbers which have a real and an imaginary part.
x = 1.0 + 4.0j
print(type(x))
<class 'complex'>
Note that Python uses j
to represent the imaginary part of a complex number. (This is the normal convention in engineering and physics, but in mathematics i
is used instead.)
4 Operators#
An operator is a symbol that tells the interpreter to perform a specific mathematical or logical manipulation. Python has a rich set of built-in operators.
4.1 Arithmetic Operators#
The basic arithmetic operators are +
(addition), -
(subtraction), *
(multiplication), /
(division), //
(integer division), %
(modulus), and **
(exponentiation).
In the examples below we are passing the expressions to the print
function which prints the result to the screen.
print(20 + 3, 20 - 3)
23 17
(Note, the print function can take multiple arguments (separated by commas) and these are printed separated by spaces. We are making use of this to demonstrate multiple expressions on a single line for compactness.)
print(20 * 3, 20 / 3, 20.0 // 3) # Note, *integer* division
60 6.666666666666667 6.0
In the example above we are using the integer division operator //
which returns the integer part of the result of the division.
print(20 % 3, 20.5 % 3.1) # Modulus, i.e. remainder
2 1.8999999999999995
There are also a set of operators that act on a variable in-place. For example, the +=
operator adds a value to a variable and stores the result back in the same variable.
a = 1
a += 1
print(a)
2
This would be equivalent to
a = a + 1
It is recommended to use the in-place operators where possible as they are more efficient and make the code more compact.
Other in-place operators include -=
(subtraction), *=
(multiplication), /=
(division), //=
(integer division), %=
(modulus), and **=
(exponentiation).
For example, to multiply a variably by 5 we could write,
a = 2.0
a *= 5
print(a)
10.0
4.2 Comparison Operators#
Comparison operators take a pair of values or expressions and return a Boolean value (True
or False
).
The Python comparison operators include ==
(equal), !=
(not equal), >
(greater than), <
(less than), >=
(greater than or equal), and <=
(less than or equal).
print(4 > 7, 4 < 7, 4 >= 7, 4 <= 7)
False True False True
print(4 == 7, 4 != 7)
False True
4.3 Logical Operators#
Logical operators take Boolean values and return Boolean values.
The Python logical operators include and
, or
, and not
.
print(4 > 7 and 4 < 7)
False
print(4 > 7 or 4 < 7)
True
print(not 4 > 7)
True
5 Compound Types#
A compound type is a type that can contain other types. Python has the following built-in compound types:
Strings
Lists
Tuples
Sets
Dictionaries
5.1 Strings#
Strings are used to represent text. Strings can be defined using either single or double quotes.
city = "Sheffield"
print(type(city))
<class 'str'>
city = 'Sheffi"eld' # can use " or '
print(type(city))
<class 'str'>
A string can be thought of as a list
of characters (we will see more about lists in the next section). A common operation for lists is to retrieve a single entry from the list. This can be done using the indexing operator []
. So for example, to retrieve the first character of a string we would write,
city = "Sheffield"
print(city[0]) # strings are like lists characters
S
To retrieve the second character we would write,
city = "Sheffield"
print(city[1])
h
Note that Python uses zero-based indexing. This means that the first element of a list or string is at index 0, the second element is at index 1, and so on.
Strings can be joined together using the +
operator.
city = 'Sheffi"eld' # can use " or '
print('Jon' + ' ' + 'Barker') # string concatenation
Jon Barker
Note here how the meaning of the +
operator depends on the types of things it is being applied to. When applied to numbers it performs addition, but when applied to strings it performs concatenation.
A note on Classes and Methods#
The type str
, like all types in Python, is represented by a class
. If you have not studied object-orientated programming before don’t worry about what this means for now. All you need to know at this point is that a class is a type that has associated functions (called methods) that can be applied to objects of that type.
There are many methods that can be applied to string objects. To apply a method to a variable we use the following syntax, variable.method()
.
For example, the string class has a method called upper
that returns a new string with all the characters in uppercase, and a method called lower
that returns a new string with all the characters in lowercase. To apply these methods to a string we would write,
city = "sheffield"
print(city.upper())
print(city.lower())
SHEFFIELD
sheffield
We can print out the complete list of methods that can be applied to a string by using the help
function. In the cell below, remove the #
from the start of the line and then run the code.
(The #
is a comment character in Python and is used to indicate that the line is a comment and should not be executed. It is being used here to prevent the code from running when the notebook is loaded. I have done this as I did not want to clutter the notes with the help text.)
# help(str)
5.2 Lists#
Lists are like strings except the elements can be any type. Lists are defined using square brackets with list elements separated by commas, e.g., as follows,
# A list of integers
primes = [2, 3, 5, 7, 11]
print(type(primes))
<class 'list'>
# A list of characters
vowels = ['a', 'e', 'i', 'o', 'u']
print(type(vowels))
<class 'list'>
# A list of strings
days = ['Mon', 'Tues', 'Weds', 'Thur', 'Fri', 'Sat', 'Sun']
print(days)
['Mon', 'Tues', 'Weds', 'Thur', 'Fri', 'Sat', 'Sun']
# A list of mixed types
weird = [1, 'Monday', 1.4, False, 4+5j]
print(weird)
[1, 'Monday', 1.4, False, (4+5j)]
List can even contain other lists!
# A list of lists of integers
list_of_lists = [[1, 2, 3, 4], [2, 3, 4], [3, 4]]
print(list_of_lists)
# Print out just the second list
print(list_of_lists[1])
[[1, 2, 3, 4], [2, 3, 4], [3, 4]]
[2, 3, 4]
5.2.1 List indexing#
Index is used to recover a single items or a range of items from a list. The syntax is list[start:end:step]
where start
is the index of the first item to be included and end
is the index of the first item to be excluded, and step
is the step size.
If the ‘step’ is omitted it defaults to 1. If the ‘start’ is omitted it defaults to the beginning of the list. If the ‘end’ is omitted it defaults to the end of the list.
This can be made clearer with some examples.
days = ['Mon', 'Tues', 'Weds', 'Thur', 'Fri', 'Sat', 'Sun']
Print just the first element of the list
print(days[0])
Mon
Print the 2nd and 3rd elements of the list
print(days[1:3])
['Tues', 'Weds']
Print from the 3rd element to the end of the list
print(days[3:])
['Thur', 'Fri', 'Sat', 'Sun']
Print from the 1st to the 3rd element in the list
print(days[:3])
['Mon', 'Tues', 'Weds']
Print every second element of the list starting from the 2nd element
print(days[1::2])
['Tues', 'Thur', 'Sat']
Print every second element of the list stating from the 1st element
print(days[::2]) # start and end are omitted
['Mon', 'Weds', 'Fri', 'Sun']
5.2.2 List operations#
The list class has a large number of methods that can be applied to list objects. The most common of this are append
, count
, extend
, index
, insert
, pop
, remove
, reverse
, and sort
. You can read the documentation for the list class to see exactly how these methods work, but most are self-explanatory and the examples below should give you a good idea of how they are used.
# Reverse the order of the elements in a list
days = ['Mon', 'Tues', 'Weds', 'Thur', 'Fri', 'Sat', 'Sun']
days.reverse()
print(days)
['Sun', 'Sat', 'Fri', 'Thur', 'Weds', 'Tues', 'Mon']
# Sort the elements in a list into order (numeric or alphabetical)
days = ['Mon', 'Tues', 'Weds', 'Thur', 'Fri', 'Sat', 'Sun']
days.sort()
print(days)
['Fri', 'Mon', 'Sat', 'Sun', 'Thur', 'Tues', 'Weds']
# Extend a list by adding items from another list
x = [1, 2, 3, 4]
x.extend([5, 6, 7])
print(x)
[1, 2, 3, 4, 5, 6, 7]
# Count the occurrences of an element in a list
x = list('Let us go then, you and I')
print(x.count('e'))
print(x.count(' '))
2
6
# Report the index of the first occurrence of an item
x = [1, 5, 3, 3, 4 ]
print(x.index(5))
1
# Append a single value to the end of a list
x = [1, 2, 3, 4, 5]
x.append(6)
print(x)
[1, 2, 3, 4, 5, 6]
# Remove and return the last value from a list
x = [1, 2, 3, 4, 5]
last_value = x.pop()
print(x)
print(last_value)
[1, 2, 3, 4]
5
# Insert a value into a list at a given position
x = [1, 2, 3, 4, 5]
x.insert(3, 999) # insert 999 after the 3rd item
print(x)
[1, 2, 3, 999, 4, 5]
5.3 Tuples#
Tuples are immutable lists. ‘Immutable’ means something whose value cannot be modified once it has been created.
They are created using a similar syntax to lists, except using round brackets ‘()’ rather than square ones ‘[]’, i.e.,
x = (1, 2) # note () for tuple and [] for lists
print(type(x))
<class 'tuple'>
x = 1, 2 # the brackets are not strictly necessary
print(type(x))
<class 'tuple'>
Values can be retrieved using the same indexing notation, i.e. using square brackets. e.g., to construct a tuple with two values and then to retrieve the first value, we would write,
x = (1, 2)
print(x[0])
1
If you try to change a tuple after is has been constructed, you will receive an error, e.g.,
x = (1, 2)
x[0] = 5 # Remember, tuples are immutable!
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[58], line 2
1 x = (1, 2)
----> 2 x[0] = 5 # Remember, tuples are immutable!
TypeError: 'tuple' object does not support item assignment
5.3.1 Working with tuples#
Tuples can be compared to see if they are identical,
pos1 = (10, 20, 30)
pos2 = (10, 25, 30)
pos1 == pos2 # true iff all elements equal
False
A tuple that has been stored in a variable can be `unpacked’ into separate variables as follows
pos = (10, 20, 30) # make a tuple called 'pos'
(x, y, z) = pos # 'unpack' the tuple into separate variables
print(x)
10
Tuple unpacking gives us a compact way to swap the value of two variables.
x, y = 10, 15
x, y = y, x # Can swap variables with a single line!
print(x, y)
15 10
This isn’t needed very often but it is a useful trick to know.
5.4 Sets#
A set is like a list in which every item is unique.
A set can be defined by listing items inside curly braces, e.g.
x = {10, 20, 30, 40}
print(type(x))
<class 'set'>
What happens if we try to store duplicated items in a set?
x = {1, 2, 3, 4, 3, 2, 1, 2}
print(x)
{1, 2, 3, 4}
You will see above that even though multiple copies of several of the integers were present between the curly braces, the set only stored a single copy of each.
Set supports all the usual set operations.
print({1,2,3,7,8, 9}.intersection({1,3,6,7,10}))
print({1,2,3,7,8, 9}.union({1,3,6,7,10}))
print({1,2,3,7,8, 9}.difference({1,3,6,7,10}))
{1, 3, 7}
{1, 2, 3, 6, 7, 8, 9, 10}
{8, 9, 2}
These operations can also be performed using arithmetic operator symbols, i.e.
print({1,2,3,7,8, 9} & {1,3,6,7,10}) # intersection
print({1,2,3,7,8, 9} | {1,3,6,7,10}) # union
print({1,2,3,7,8, 9} - {1,3,6,7,10}) # difference
{1, 3, 7}
{1, 2, 3, 6, 7, 8, 9, 10}
{8, 9, 2}
# Full documentation -- remove the '#' in the line below before running this cell.
# help(set)
5.5 Dictionaries#
A dictionary maps from a unique key to a value
office = {'jon': 123, 'jane':146, 'fred':245}
office = {'jon': 123, 'jane':146, 'fred':245}
print(type(office))
<class 'dict'>
office = {'jon': 123, 'jane':146, 'fred':245}
print(office['jon']) # look up a value for a given key
123
office = {'jon': 123, 'jane':146, 'fred':245}
print(office['jose']) # throws exception if key doesn't exist
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
Cell In[70], line 2
1 office = {'jon': 123, 'jane':146, 'fred':245}
----> 2 print(office['jose']) # throws exception if key doesn't exist
KeyError: 'jose'
We need to make sure that our keys are unique. e.g. a problem if we have two people called ‘jon’!
office = {'jon': 123, 'jane':146, 'fred':245, 'jon': 354}
print(office)
{'jon': 354, 'jane': 146, 'fred': 245}
The later entry has overwritten the earlier one (You might have expected it to raise a run-time error).
A better key would be a unique employee ID, e.g. the payroll number.
5.5.1 Operations with dictionaries#
office = {'jon': 123, 'jane':146, 'fred':245, 'jon': 354}
office['jose'] = 282 # add a new key-value pair
print(office)
{'jon': 354, 'jane': 146, 'fred': 245, 'jose': 282}
print(office.keys()) # return the list of keys
dict_keys(['jon', 'jane', 'fred', 'jose'])
print(office.values()) # return the list of values
dict_values([354, 146, 245, 282])
print('jon' in office) # check if a key exists
True
6 Control Flow#
To write programs with Python (as in any language), we have to use logic to control the flow of instructions. Python has several syntax elements for controlling flow, including,
if-elif-else
for loops
while loops
6.1 if-elif-else#
x = 20
if x > 10:
print(str(x) + ' is larger than 10') # Must be indented!
else:
print(str(x) + ' is smaller than 10')
20 is larger than 10
grade = 75
if grade >= 70:
print('Distinction') # Note, lines share the same indentation
print('well done!')
elif grade >= 50:
print('you have passed')
else:
print('FAIL!')
Distinction
well done!
6.2 For loops#
For-loops iterate over ‘iterables’ (lists, dictionaries,…)
sum = 0
for x in [1, 2, 3, 4, 5]:
sum += x
print(sum)
15
week = ['mon', 'tuesday', 'weds', 'thur', 'fri']
for day in week:
print(day.center(20,' '))
mon
tuesday
weds
thur
fri
To loop over integer values we can generate a list using range()
my_range = range(10)
print(list(my_range)) # range() generates a list of numbers
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(range(5, 25, 2))) # range(start, end, skip)
[5, 7, 9, 11, 13, 15, 17, 19, 21, 23]
for x in range(0, 50, 5):
print(x)
0
5
10
15
20
25
30
35
40
45
6.3 While loops#
While loops are much as you would expect. But note, no ‘while - until loop’
x = 127
output = []
while x != 1:
if x % 2 == 0:
x /= 2 # if x is even
else:
x = x * 3 + 1 # if x is odd
output.append(x)
print(output)
[382, 191.0, 574.0, 287.0, 862.0, 431.0, 1294.0, 647.0, 1942.0, 971.0, 2914.0, 1457.0, 4372.0, 2186.0, 1093.0, 3280.0, 1640.0, 820.0, 410.0, 205.0, 616.0, 308.0, 154.0, 77.0, 232.0, 116.0, 58.0, 29.0, 88.0, 44.0, 22.0, 11.0, 34.0, 17.0, 52.0, 26.0, 13.0, 40.0, 20.0, 10.0, 5.0, 16.0, 8.0, 4.0, 2.0, 1.0]
7 Functions#
Functions are introduced with the keyword def
Neither parameters nor return values need type declarations.
Function body must be indented.
def sum_list(data):
sum = 0
for x in data:
sum += x
return sum
sum = sum_list([1,2,3,4,5])
print(sum)
15
7.1 Function docstring#
A string can be added directly after the function definition to act as documentation. You should always do this!
def sum_list(data):
""" Returns the sum of a list of numbers."""
sum = 0
# sum the numbers
for x in data:
sum += x
return sum
help(sum_list)
Help on function sum_list in module __main__:
sum_list(data)
Returns the sum of a list of numbers.
Note, triple-quoted strings can contain newlines and quotes.
7.2 Multiple return values#
If a function needs to return multiple values they can simply be packed into a tuple
def find_min_max(data):
"""Return min and max value in a list"""
min, max = data[0], data[0]
for x in data:
if x < min:
min = x
elif x > max:
max = x
return min, max
min, max = find_min_max([1,4,3,6,-5,6])
print('smallest is', min, 'and largest is', max)
smallest is -5 and largest is 6
7.3 Functions: Default parameter values#
Parameters can be given default values
def sum_list(data, debug=False):
"""Returns the sum of a list of numbers"""
if debug:
print('Calling sum_list with', data)
sum = 0
for x in data:
sum += x
return sum
print(sum_list(range(5)))
10
print(sum_list(range(5), True))
Calling sum_list with range(0, 5)
10
7.4 Functions: Named parameters#
Parameter can be explicitly named in the function call
def compute_ratio(num, denom):
return num/denom
ratio = compute_ratio(10.0, 2.0)
print(ratio)
5.0
ratio = compute_ratio(num=10.0, denom=2.0)
print(ratio)
5.0
ratio = compute_ratio(denom=2, num=10)
print(ratio)
5.0
7.5 Function: named and default parameters#
Default and named parameters can be conveniently combined.
This is useful when there are a large number of parameters but most have default values. e.g.,
def dummy(p1=0.0, p2=0.0, p3=0.0, p4=0.0):
print(p1, p2, p3, p4)
dummy(p3=1.0)
0.0 0.0 1.0 0.0
dummy(p1=10.0, p4=2.0)
10.0 0.0 0.0 2.0
8 Summary#
Python is a dynamically typed language
It has extensive libraries loaded as modules
It has builtin compound types: str, list, tuple, dict
Supports standard flow control mechanisms: if-else, for, while
We can define functions with default and named parameters
There are many important features that we have not yet covered which will be introduced in later lectures:
classes
list comprehensions
lambda functions and functions as objects
numpy library