CV4GS - Python tutorial

Table of Contents

  • 1  Introduction
  • 2  Python basics
    • 2.1  Basic data types
      • 2.1.1  numbers
      • 2.1.2  booleans
      • 2.1.3  strings
        • 2.1.3.1  creation
        • 2.1.3.2  formating
        • 2.1.3.3  concatenation
        • 2.1.3.4  class built-in methods
        • 2.1.3.5  bytes, unicode
    • 2.2  Operators
      • 2.2.1  arithmetic operators
      • 2.2.2  assignment operators (shortcut)
      • 2.2.3  comparison operators
      • 2.2.4  logical operators
      • 2.2.5  membership operators
    • 2.3  Containers
      • 2.3.1  lists
        • 2.3.1.1  creation
        • 2.3.1.2  indexing and slicing
        • 2.3.1.3  class built-in methods
        • 2.3.1.4  iterating over lists
  • 2  Python basics (continued)
    • 2.3  Containers (continued)
      • 2.3.2  dictionaries
        • 2.3.2.1  creation
        • 2.3.2.2  accessing
        • 2.3.2.3  iterating over dict
        • 2.3.2.4  class built-in methods
      • 2.3.3  tuples
        • 2.3.3.1  creation
        • 2.3.3.2  indexing
      • 2.3.4  sets
    • 2.4  Control flow statements
      • 2.4.1  if statement
      • 2.4.2  for loop
      • 2.4.3  while loop
    • 2.5  Functions
      • 2.5.1  defining functions
      • 2.5.2  arguments and parameters
      • 2.5.3  return values
      • 2.5.4  lambda functions
      • 2.5.5  scope

Introduction¶

  • fun facts
    ⇒ Python was first released in 1991 by Guido van Rossum; Python 2.0 was released on 16 October 2000, and Python 3.0 on 3 December 2008
    ⇒ Python's name is derived from the British comedy group Monty Python
  • Python versions
    On 1 Janurary 2020, Python officially dropped support for python2. Throughout this course we'll be using python3.
    Check your Python version from your terminal:
    $ python --version
    
In [1]:
!python --version
Python 3.9.12

**Note**: there are a number of differences between Python2 and Python3. Here are a few:

  • print function:
    print 'hello world'   # Python2 syntax
       print('hello world')  # Python3 syntax
    
  • integer division:
    3/2   # Python 2 => returns 1
       3/2   # Python 3 => returns 1.5
    
  • unicode: Python 2 has ASCII str() type, unicode() type, but no byte type. Python 3, has unicode UTF-8 str() type, and byte type
    print(Popocatépetl)   # Python2 => ERROR: non-ASCII characters not handled (e.g. accents)
       print(Popocatépetl)   # Python3 => SUCCESS: UTF-8 characters handled (e.g. accents)
    
  • xrange & range functions to create iterable objects:
    xrange(5)   # Python2 syntax
       range(5)    # Python3 syntax
    
  • interpretated language
    ⇒ an interpreter processes the source file at runtime
    ⇒ Python does not require that you compile your program before executing it (unlike C++, Pascal, Fortran, etc.)
  • high-level language
    ⇒ Python relies on easy-to-read structures that are later translated into a low-level language and run on the computer’s central processing unit (CPU)
  • object-oriented language
    ⇒ allows powerful code structuring
    ⇒ the object-oriented programming (OOP) allows thinking of problems in terms of classes and objects, thereby allowing the creation of reusable patterns of code
  • general-purpose
    ⇒ Python comes with a limited number of built-in libraries, making it very light weigth
    ⇒ a huge amount of third-party packages, libraries, and frameworks are available to handle various aspects
    ⇒ the installation of additional packages is managed through package managers (pip, conda):
    $ pip install <package_name>     #>> Python's package manager
    $ conda install <package_name>   #>> Anaconda's package manager
    
  • most common libraries for scientific purposes
    • plotting: matplotlib, bokeh, seaborn, plotly, mayavi
    • data manipulation: numpy, pandas
    • image: scikit-image, imageio, pillow, opencv
    • machine learning: scikit-learn, tensorflow/keras, pytorch

For those with experience with Matlab, see numpy for Matlab users

Python basics¶

  • print text using "print"
    print('Hello World')
    
  • comment using a hash character (#)
    # this text is commented
    
  • python is zero base: indexing starts at 0 (whereas Matlab starts at 1)
    a = [1, 2, 3]
    a[0] # access first element of list with index = 0
    
  • python requires proper indenting to understand which block it corresponds to (i.e., no explicit statement to close a code block).
    Without proper indenting the code will not compile.
    # EX: for-loop
    for k in range(10):
      print(k)
    # EX: if-statement
    if k > 0:
      print(k)
    
  • python makes extensive use of modules and packages, which are imported at the begining of the file.
    Theses can be imported if installed, or if in your PYTHONPATH.
    import sys
    import pandas as pd
    from matplotlib import pyplot as plt
    

Basic data types¶

Basic data types in Python include numbers, booleans and strings.

numbers¶

Types: integer, float, complex

In [56]:
a = 1            # int = signed integer (= positive or negative whole numbers with no decimal point)
print(type(a))

b = 1.0          # float = floating point real values (= real numbers with a decimal point)
print(type(b))

c = 2.0 + 3j     # complex = complex numbers (= a + bj, where a represents the real part, anb b the imaginary part)
print(type(c))
<class 'int'>
<class 'float'>
<class 'complex'>

booleans¶

In Python, boolean variables are defined by the True and False keywords.

In [47]:
t = True
f = False

print(type(t))
<class 'bool'>

strings¶

creation¶

In [1]:
hello = 'hello'                    # string with single quotes
world = "world"                    # string with double quotes (same as single quote)
helloworld = '''hello world '''    # string with triple quotes
hellocruelworld = """              # string with triple quotes (can span multiple lines, include single/double quotes)
hello 'cruel' world
"""  

print(type(hello))
print(type(world))
print(type(helloworld))
print(type(hellocruelworld))
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>

formating¶

  • using string method .format()
In [5]:
'Hello {}! We are in {}.'.format('world', 2024)
Out[5]:
'Hello world! We are in 2024.'
In [102]:
'{word1} {word2} {word3}'.format(word1='hello', word2='cruel', word3='world')
Out[102]:
'hello cruel world'
In [12]:
'{:.2f}'.format(3.141592) # keep only 2 decimal points
'{:.2f}'.format(3)        # add 2 decimal points
Out[12]:
'3.00'
  • using f-strings (in Python 3.6+)
In [6]:
word1 = 'world'
word2 = 2024
f'Hello {word1}! We are in {word2}'
Out[6]:
'Hello world! We are in 2024'
  • using placeholder % (older formatting syntax)
In [9]:
name = 'world'
year = 2024

formatted_string = 'Hello %s! We are in %d.' % (name, year)
print(formatted_string)
Hello world! We are in 2024.

concatenation¶

In [94]:
x = 'Hello'
y = 'World'
x + y
Out[94]:
'HelloWorld'

class built-in methods¶

List of all string methods in the documentation.

In [98]:
s = "hello"
print(s.capitalize())              # Capitalize a string
print(s.upper())                   # Convert a string to uppercase; prints "HELLO"
print(s.rjust(7))                  # Right-justify a string, padding with spaces
print(s.center(7))                 # Center a string, padding with spaces
print(s.replace('l', 'L'))         # Replace all instances of one substring with another
print('  world '.strip())          # Strip leading and trailing whitespace
print('hello world'.split(' '))    # Split string at specified character
Hello
HELLO
  hello
 hello 
heLLo
world
['hello', 'world']

bytes, unicode¶

In [14]:
# - unicode string
type('test')
type(u'test') # Note: u'strings' are unicode for backwards compatibility with python 2)
Out[14]:
str
In [17]:
# - byte string
type(b'test') # Note: bytes can only contain ASCII literal characters (no accents)
Out[17]:
bytes
In [22]:
# - convert unicode to bytes
# Note: UTF-8 is an encoding supporting accents, ASCII does not
print(type('Popocatépetl'))
'Popocatépetl'.encode('utf-8')  # unicode is .encoded(encoding) to bytes; 
<class 'str'>
Out[22]:
b'Popocat\xc3\xa9petl'
In [20]:
# - convert byte to unicode
mystring = b'test'.decode('utf-8')
type(mystring)
Out[20]:
str

Operators¶

arithmetic operators¶

In [26]:
x = 1
print(x + 1)   # Addition
print(x - 1)   # Subtraction
print(x * 2)   # Multiplication
print(x ** 2)  # Exponentiation
print(x % 2)   # Modulus
print(x // 2)  # Floor division (note: since python3 dividing an int always casts to float)
2
0
2
1
1
0

assignment operators (shortcut)¶

In [2]:
x = 1 
x += 2   # x = x + 2
x -= 2   # x = x - 2
x /= 2   # x = x / 2
x *= 2   # x = x * 2
In [3]:
# Example:
x = 1
x *= 2
print(x)
2

Multiple variable assignments:

In [41]:
x, y = 1, 'a'

print(x)
print(y)
1
a

comparison operators¶

In [ ]:
x = 1
y = 2

x == y    # Equal
x != y    # Not equal
x > y     # Greater than
x < y     # Less than
x >= y    # Greater than or equal to
x <= y    # Less than or equal to

logical operators¶

Logical operators are used to combine conditional statements.

Python implements all of the usual operators for Boolean logic, but uses English words rather than symbols (&&, ||, etc.):

In [ ]:
x = 1

x < 5 and  x < 10     # Returns True if both statements are true
x < 5 or x < 4        # Returns True if one of the statements is true
not(x < 5 and x < 10) # Reverse the result, returns False if the result is true

membership operators¶

Membership operators are used to test if a sequence is presented in an object:

In [ ]:
l = ['a', 'b']

'a' in l        # Returns True if a sequence with the specified value is present in the object
'c' not in l    # Returns True if a sequence with the specified value is not present in the object

Containers¶

Python includes several built-in container types: lists, dictionaries, sets, and tuples.

lists¶

A list is a mutable list of elements, which can be of multiple types.

creation¶

In [159]:
l = []                    # create empty list
l = list()                # create empty list

l = [1, 2, 3, 4, 5]       # create list by comma seperated values inside square brackets
l = [1, 'a', 'b', 4, 5]   # lists can hold several data types
l = list(range(5))        # create list of integers using the built-in function 'range'

print(l)
[0, 1, 2, 3, 4]
In [125]:
l1 = [1, 2, 3]
l2 = [4, 5, 6]
l = [l1, l2]             # concatenate lists (create list of lists)
print(l)

l = l1 + l2              # append lists
print(l)

l *= 2
print(l)                 # replicate list
[[1, 2, 3], [4, 5, 6]]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
In [126]:
len(l)                   # get number of elements in list
Out[126]:
12

Note: a list can only have 1 dimension

l = [1,2,3; 4,5,6]   # INVALID SYNTAX (Matlab style)

indexing and slicing¶

Access list elements using square brackets.
Reminder: Python is zero-based, meaning the first element is accessed with the index 0
Important: the result of the slicing includes the start index, but excludes the end index

In [164]:
l = [1, 2, 3, 4, 5]

# - access single element
l[0]                # access first element
l[-1]               # access last element
l[-2]               # access second to last element

# - slice (access multiple elements)
# Important: the result includes the start index, but excludes the end index
l[1:3]              # access 2nd & 3rd elements
l[1:-2]             # access 2nd until 2nd to last element
l[:3]               # access all elements from start until 4th element
l[3:]               # access all elements from 4th element until end
l[::2]              # access every nth element

# - assign element
l[0] = 0            # replace element
l[1:2] = [-1, -2]   # assign a sublist to a slice

print(l)
[0, -1, -2, 3, 4, 5]

class built-in methods¶

In [135]:
l = []

l.append(1)        # add element at the end of the list
print(l)

l.extend([2, -3])  # append list at the end of the list
print(l)

l.insert(1, .5)    # insert element at index n: insert(n, value)
print(l)

l.pop(1)           # remove element at index n: pop(n)
print(l)

l.sort()           # sorts elements
print(l)
[1]
[1, 2, -3]
[1, 0.5, 2, -3]
[1, 2, -3]
[-3, 1, 2]

iterating over lists¶

A list is an iterable container, to iterate through its elements use a for-loop:

In [142]:
l = [1, 2, 3]
for element in l:
    print(element)
1
2
3

To get both index and value during iteration, use the built-in function enumerate:

In [144]:
for index, element in enumerate(l):
    print('index={}, value={}'.format(index, element))
index=0, value=1
index=1, value=2
index=2, value=3

Use list comprehension to iterate through list and edit its values in a single line:

In [152]:
l = [1, 2, 3]

ll = [elt*2 for elt in l]                         # list comprehension
print(ll)

ll = [elt*2 for elt in l if elt == 2]             # list comprehension with if condition
print(ll)

ll = [elt*2 if elt == 2 else elt*1 for elt in l]  # list comprehension with if-else condition
print(ll)
[2, 4, 6]
[4]
[1, 4, 3]

dictionaries¶

A dictionary stores key-value pairs, values can be retrieved via their key.

creation¶

In [302]:
d = {}                                      # create empty dictionary
d = dict()                                  # create empty dictionary

d = {'a':1, 'b':2, 'c':3}                   # create dictionary by 'key':value
d = dict(a=1, b=2, c=3)                     # create dictionary by key=value
d = dict(a=1, b=2, c=dict(c1=.1, c2=.2))    # create nested dictionaries

print(d)
{'a': 1, 'b': 2, 'c': {'c1': 0.1, 'c2': 0.2}}
In [229]:
d = {1:1, 2:2, 3:3}                         # keys can be numeric values
d = {'hello world':1, 'united we stand':1}  # keys can be complex strings
d = {(0,1):0, (0,2):1}                      # keys can be tuples

print(d)
{(0, 1): 0, (0, 2): 1}
In [198]:
d = {'a':1, 'b':2, 'c':3}
d['d'] = 4                                  # append key to dictionary
print(d)
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

accessing¶

In [319]:
d = dict(a=1, b=2, c=dict(c1=.1, c2=.2))
print(d['a'])       # access value using key
print(d['c']['c1']) # access value in nested dictionaries
print(d.get('a'))   # access value using build-in method

d = {1:1, 2:2, 3:3} 
print(d[3])         # access value using key
1
0.1
1
3
In [200]:
d = dict(a=1, b=2, c=3)
d['c'] = 'c' # replace value
print(d)
{'a': 1, 'b': 2, 'c': 'c'}

iterating over dict¶

Dictionaries are iterator objects, you can iterate through it using a for-loop:

In [25]:
d = dict(a=1, b=2, c=3)

for key in d:
    print('key={}, value={}'.format(key, d[key]))
key=a, value=1
key=b, value=2
key=c, value=3

Use dictionary comprehension to create or edit dictionaries:

In [23]:
d = {key: 0 for key in ['a', 'b', 'c']}  # create a dictionary with keys defined by list values
print(d)
{'a': 0, 'b': 0, 'c': 0}
In [24]:
d = {key: d[key]+1 for key in d}        # edit existing dictionary
print(d)
{'a': 1, 'b': 1, 'c': 1}

class built-in methods¶

In [314]:
# - update a dictionary with values from another dictionary
d1 = dict(a=1, b=2, c=3)
d2 = dict(c=4, d=5)

d1.update(d2)
print(d1)
{'a': 1, 'b': 2, 'c': 4, 'd': 5}
In [321]:
# - access keys / values (returns an iterator)
d = dict(a=1, b=2, c=3)

d.keys()
d.values()

# => loop through values
for v in d.values():
    print(v)
    
# => return list of values
list(d.values())
1
2
3
Out[321]:
[1, 2, 3]
In [327]:
# - remove value
d = dict(a=1, b=2, c=3)

removed_value = d.pop('a')
print(d)
print(removed_value)
{'b': 2, 'c': 3}
1

tuples¶

A tuple is an immutable list of values, meaning they cannot be changed once created.
(This also means they are hashable, and can be used as a dictionary key).

creation¶

In [ ]:
t = (1, 2, 3)  # create tuple
t = 1, 2, 3    # create tuple (parenthesis are optional)
print(t)
print(type(t))

indexing¶

Indexing and slicing work like lists.

In [245]:
print(t[0])    # access tuple element
(1, 2, 3)
<class 'tuple'>
1

Use tuple unpacking to assign tuple elements to multiple variables at once:

In [251]:
a, b, c = 1, 2, 3      # unpack (parenthesis are optional)
_, _, c = 1, 2, 3      # unpack and ignore elements
a, *b, c = 1, 2, 3, 4  # unpack with multiple elements casted to a list (Python3 only)

print(b)
print(c)
[2, 3]
4

Combine iteration and tuple unpacking:

In [311]:
students = [
    ('Alice', ('F', 29)),
    ('Bob', ('M', 23)),
    ('Eve', ('F', 44)),
]

for name, (sex, age) in students:
    print('{name}, {pronoun} is {age}'.format(name=name, age=age, pronoun=dict(F='she', M='he')[sex]))
Alice, she is 29
Bob, he is 23
Eve, she is 44

**Notes**:

  • Warning: the following is also a tuple (note the trailing comma)
    t = 1,
      print(t)
    
  • Warning: cannot change values of a tuple (immutable)
    t[0] = 1 # will generate an error
    
  • Tuples are often used to return multiple values from a function
    def multiple_values():
          return 1, 2, 3, 4
    

sets¶

A set is an unordered collection of distinct (unique) elements.

In [295]:
s = {'tiger', 'monkey', (1,2,3), .1}            # create set
s = set(['tiger', 'monkey', (1,2,3), .1])       # create set
print(s)
print(type(s))

s = {'tiger', 'monkey', 'monkey', (1,2,3), .1}  # elements are unique (duplicates are ignored)
print(s)

s.add('monkey')                                 # adding an existing element does nothing
print(s)

s.add('zebra')                                  # add element
print(s)

s.remove(.1)                                     # remove element
print(s)

print('cat' in s)                               # check if an element is in a set
{0.1, 'tiger', (1, 2, 3), 'monkey'}
<class 'set'>
{0.1, 'tiger', (1, 2, 3), 'monkey'}
{0.1, 'tiger', (1, 2, 3), 'monkey'}
{0.1, 'tiger', (1, 2, 3), 'monkey', 'zebra'}
{'tiger', (1, 2, 3), 'monkey', 'zebra'}
False

Note: standard "trick" to remove duplicates from a list (WARNING: will change the list order!)

In [298]:
data = [3, 5, 1, 6, 2, 1, 3, 5]
list(set(data)) # remove duplicates by converting list to set
Out[298]:
[1, 2, 3, 5, 6]

Control flow statements¶

if statement¶

Python uses the if, elif, and else clauses to conditionally execute blocks of statements.

In [337]:
x = -10

if x < 0:
    print("x is negative")
elif x > 0:
    print("x is positive")
else:
    print("x = 0")
x is negative

Any non-zero number or non-empty string, tuple, list, or dictionary evaluates as true:

In [343]:
# - evaluate a string
x = []
if x:
    print('List is not empty')
else:
    print('List is empty')

# - evaluate a number
y = 0
if y:
    print('This is evaluated as True')
else:
    print('This is evaluated as False')
List is empty
This is evaluated as False

for loop¶

Python uses the for statement to loop through an iterable object.

In [348]:
for letter in 'ciao':
    print(letter)
c
i
a
o
In [350]:
for elt in ['h', 'o', 'l', 'a']:
    print(elt)
h
o
l
a
In [355]:
for idx in range(5):
    print(idx)
0
1
2
3

Use the built-in function enumerate to get both index and value during iteration:

In [352]:
l = ['h', 'o', 'l', 'a']
for index, element in enumerate(l):
    print('index={}, value={}'.format(index, element))
index=0, value=h
index=1, value=o
index=2, value=l
index=3, value=a

while loop¶

Python uses the while clause to loop over body as long as the condition is True.

In [354]:
a = 5
while a>0:
    a -= 1
    print(a)
4
3
2
1
0

try-except statement¶

Python supports exception handling with the try statement, which includes try, except, finally, and else clauses.

In [385]:
try:
    print('Try dividing by 0')
    1 / 0
except:
    print('You fool!')
Try dividing by 0
You fool!

exceptions¶

Exceptions happen at runtime and are used for error handling. Here are some standard errors:

  • ZeroDivisionError
In [24]:
1 / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-24-bc757c3fda29> in <module>
----> 1 1 / 0

ZeroDivisionError: division by zero
  • KeyError
In [26]:
d = dict(a=1)
d['b']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Input In [26], in <cell line: 2>()
      1 d = dict(a=1)
----> 2 d['b']

KeyError: 'b'

handling exceptions¶

In the cells below, we generate an error called "ZeroDivisionError" (triggered when a number is divided by zero), and we catch that error with the except statement to run another code block.

In [371]:
# Set function to computer the inverse value of the parsed variable.
def inverse(a):
    return 1. / a
In [372]:
# Calling the inverse function on a 0 returns the error 'ZeroDivisionError'
inverse(0)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-372-6020e9ef8186> in <module>
      1 # Calling the inverse function on a 0 returns the error 'ZeroDivisionError'
----> 2 inverse(0)

<ipython-input-371-107370f37cde> in inverse(a)
      1 # Set function to computer the inverse value of the parsed variable.
      2 def inverse(a):
----> 3     return 1. / a

ZeroDivisionError: float division by zero
In [402]:
# Test for the 'ZeroDivisionError'

try:
    b = inverse(0)
except ZeroDivisionError:
    # execute if 'ZeroDivisionError' caught
    print('Caught "ZeroDivisionError" in the expression.')
    b = 99999

print('b =', b)
Caught "ZeroDivisionError" in the expression.
b = 99999
In [400]:
# Test for various error types

try:
    b = inverse(1)
except (ZeroDivisionError, KeyError):
    # execute if 'ZeroDivisionError' or 'KeyError' caught
    b = 99999
except ValueError:
    # execute if 'ValueError' caught
    b = 88888
else:
    # excetute if no exceptions caught
    print('No exception occurred')
finally:
    # always execute this
    print('Try statement finished')

print('b =', b)
No exception occurred
Try statement finished
b = 1.0

raise custom exceptions¶

In [391]:
x = 10
if x > 5:
    raise Exception('x should not exceed 5. The value of x was: {}'.format(x))
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-391-3dcc1a597936> in <module>
      1 x = 10
      2 if x > 5:
----> 3     raise Exception('x should not exceed 5. The value of x was: {}'.format(x))

Exception: x should not exceed 5. The value of x was: 10

pass statement¶

The body of a Python compound statement cannot be empty—it must contain at least one statement. The pass statement, which performs no action, can be used as a placeholder when a statement is syntactically required but you have nothing specific to do.

In [361]:
def function_to_prepare():
    pass

break statement¶

The break statement is allowed only inside a loop body (for or while loop). When break executes, the loop terminates.

In [360]:
for idx in range(5):
    print(idx)
    
    if idx>=2:
        print('Index >= 2, stopping the loop')
        break
0
1
2
Index >= 2, stopping the loop

assert statement¶

Use assert statement to test that a certain condition is met. If this condition is True, the program can continue, otherwise you can have the program throw an AssertionError exception.

In [394]:
x = -1
assert (x > 0), "This value of x should be positive"
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-394-81a5e5e6332a> in <module>
      1 x = -1
----> 2 assert (x > 0), "This value of x should be positive"

AssertionError: This value of x should be positive

Functions¶

Python functions are defined using the def keyword.

  • the body of the function is indented (always use 4 spaces for one indentation level)
  • the returned value preceded by the return keyword; (functions can have multiple return statements)
  • arguments can be parsed as positional arguments (args), i.e. a list of comma seperated values: func(a, b, c)
  • arguments can be parsed as keyword arguments (kwargs), which have a default value if not explicitely parsed: func(spam=1, eggs=2)
  • when using args and kwargs, args must always come before kwargs: func(a, spam=1, eggs=2)

parse positional arguments¶

In [419]:
def add(x, y):
    return x + y

def inverse(x):
    if x != 0:
        return 1/x
    else:
        print('warning: cannot return the inverse of 0, returning None')
        return None
In [420]:
add(2, 2)
Out[420]:
4
In [421]:
inverse(2)
Out[421]:
0.5
In [422]:
inverse(0)
warning: cannot return the inverse of 0, returning None

parse keyword arguments¶

Arguments can be parsed as keyword arguments, and default values can be set.

In [20]:
def greetings(language='english', name='Jake'):
    if language == 'english':
        print('Hello {}!'.format(name))
    elif language == 'spanish':
        print('Hola {}!'.format(name))
    else:
        print('I dont speak this language.')
In [23]:
greetings(language='spanish', name='Emiliano')
greetings(language='spanish') # name not parsed => will take use default value defined in funtion
Hola Emiliano!
Hola Jake!
In [22]:
d = dict(language='english')
greetings(**d) # unpack dictionary keys-values as keywarg arguments
Hello Jake!

set documentation¶

In [459]:
def addone(a):
    '''This function is used to add one to the parsed variable.'''
    return a + 1
In [460]:
addone.__doc__
Out[460]:
'This function is used to add one to the parsed variable.'

warning: calls by value or reference¶

Call by value: for primitive variables (ex: numbers), Python copies the content of the variable into the function.

In [442]:
def addone(a):
    a = a + 1

n = 3
addone(n) # => variable 'n' is copied to new variable 'a' inside the function => 'a' is changed 'n' is not
print(n) 
3
In [443]:
# => to change 'n' we need to return the copied variable 'a' which was changed
def addone(a):
    a = a + 1
    return a

n = 3
n = addone(n)
print(n)
4

Call by reference: for complex variables (ex: lists), Python does not copy the variable inside the function, instead it references it. This mean that the parsed variable will suffer the changes, even though the variable is not returned from the function.

In [445]:
mylist = [1, 2, 3, 4]

def func(a):
    a[1] = -10

func(mylist)
print(mylist) # => the array is modified even though the list was not returned from the function!
[1, -10, 3, 4]

Intermediate Exercises¶

1. Write a function that takes two values and returns the largest one (max), and document it

Solution:

def get_max(a, b):
    """
    Return the larger of two values.

    Example:
    >>> get_max(3.5, 1.2)
    3.5
    """

    # Using numpy:
    # import numpy as np
    # return np.maximum(a, b)

    # Using built-in method:
    return max(a, b)
2. Write a function that computes the ratio between two values, and prints a warning message when the denominator is 0

Solution:

def get_ratio(a, b):
    """
    Compute the inverse of two values.

    Example:
    >>> get_ratio(5, 2)
    2.5
    >>> get_ratio(3, 0)
    Warning: Division by zero. The result is undefined.
    """
    if b == 0:
        print("Warning: Division by zero. The result is undefined.")
        raise ZeroDivisionError("Denominator cannot be zero.")
    return a / b
3. Build a for loop which iterates through numbers ranging from 0 to 20, and as it iterates it creates a list containing the squared value of the iterated value. Exist the loop when the iterator exceeds 10.

Solution:

# Solution without list comprehension:
squared_values = []  # Initialize an empty list to store squared values
for num in range(21):  # Iterate through numbers from 0 to 20
    if num > 10:
        break  # Exit the loop if the iterator exceeds 10
    squared_values.append(num ** 2)  # Square the number and append to the list
print(squared_values)

# Solution with list comprehension
squared_values = [num ** 2 for num in range(21) if num <= 10]
print(squared_values)
4. Create a dictionnary a with various volcanoes as keys, and their altitude as values. Print the altitude of your favourite volcanoe from the dictionary.

Solution:

# Create the dictionary with volcanoes and their altitudes
volcano_altitudes = {
    "Popocatépetl": 5426,
    "Etna": 3329,
    "Mount Fuji": 3776
}

# Print the altitude of your favorite volcano
volcano_name = "Popocatépetl"
print(f"The altitude of {volcano_name} is {volcano_altitudes[volcano_name]} meters.")

Classes, Methods, Attributes¶

In Python, everything is an object, even functions and attributes.
OOP = Object Oriented Programing

  • Classes are defined with the class keyword.
    ‐ class names are usually Capitalized and use CamelCase
    ‐ classes are instantiated with ()
    ‐ class attributes are fined in init (not compulsory, but considered good practice)
  • Methods are functions inside classes, and are defined with the def keyword
    ‐ function names are usually lower case
  • Attributes are set like that instance.attribute = value, and are also accessed via instance.attribute
In [5]:
class Volcano():
    
    def __init__(self, name):
        self.name = name
        self.type = 'mountain' # public attribute
        self.__imminentexplosion = False # private attribute (starts with double underscore)
    
    def print_name(self):
        print('The volcano name is', self.name)
        
    def set_altitude(self, altitude):
        self.altitude = altitude
In [6]:
# - instantiate class
volc = Volcano('colima')
In [7]:
# - get class attribute (public):
volc.type 
Out[7]:
'mountain'
In [8]:
# - private class attribute unaccesible
# volc.__imminentexplosion  # command returns 'AttributeError'
In [9]:
# - call class method:
volc.print_name() 
The volcano name is colima
In [10]:
# - set attribute through class method:
volc.set_altitude(3820)
print(volc.altitude)
3820
In [11]:
# - get class attributes (public + private) as dictionary
volc.__dict__
Out[11]:
{'name': 'colima',
 'type': 'mountain',
 '_Volcano__imminentexplosion': False,
 'altitude': 3820}
In [12]:
# - check class instance
print(type(volc))
isinstance(volc, Volcano)
<class '__main__.Volcano'>
Out[12]:
True

inheritence¶

A class can inherit methods and attributes from a parent class.

super().method() let's you call an instances parent's method from an instance method

In [14]:
class ExplosiveVolcano(Volcano):
    def __init__(self, name):
        super().__init__(name)
    
    def set_expected_VEI(self, VEI):
        '''Volcanic Explosivity Index (VEI) is a relative measure of the explosiveness of volcanic eruptions.'''
        self.expected_VEI = VEI
In [15]:
# - instantiate class which inherited from class Volcano
popo = ExplosiveVolcano('Popocatépetl')  # needs to parse arguments for parent class Volcano
In [16]:
popo.set_expected_VEI(5)
In [17]:
print(popo.expected_VEI)
5

A class can inherit from multiple classes:

In [19]:
class Mountain():
    def __init__(self):
        self.category = 'geology'
In [28]:
class SuperVolcano(ExplosiveVolcano, Mountain):
    # Note: because ExplosiveVolcano already inherits from Volcano, should not inherit both
    
    def __init__(self, name):
        super().__init__(name)
        self.info = 'stay away'
        
    def breathe(self):
        print("breathing")
In [26]:
yellowstone = SuperVolcano('Yellow Stone')
In [27]:
print(yellowstone.info)
stay away

add build-in instances¶

Example: __str__ and __repr__ instances

  • instance description ugly by default: <main.ExplosiveVolcano at 0x7fb66dca6160>
  • __str__() is supposed to give a nice string representation of the class, but uses __repr__() as a fallback
In [514]:
class ExplosiveVolcano(Volcano):
    def __init__(self, name):
        super().__init__(name)
    
    def set_expected_VEI(self, VEI):
        '''Volcanic Explosivity Index (VEI) is a relative measure of the explosiveness of volcanic eruptions.'''
        self.expected_VEI = VEI
        
    def __repr__(self):
        return '< Volcano: name=' + self.name + '>'
In [516]:
popo = ExplosiveVolcano('Popocatépetl')  # needs to parse arguments for parent class Volcano
In [519]:
print(popo)
< Volcano: name=Popocatépetl>

Modules and packages¶

  • a module is python file, which can be imported if in your PYTHONPATH
  • a package is a collection of python modules, in practice as a directory containing python modules and a __init__.py file.
    Packages can be imported as a whole, or only modules of it can be imported.

import¶

Import the numpy package:

In [29]:
import numpy
In [42]:
numpy.add(2,3)
Out[42]:
5

Import the numpy package using a shorter name (recommended):

In [31]:
import numpy as np
In [36]:
np.add(2,3)
Out[36]:
5

Import a specific function from numpy:

In [33]:
from numpy import add
In [35]:
add(2,3)
Out[35]:
5
In [37]:
from numpy import add as npadd
In [38]:
npadd(2,3)
Out[38]:
5

Import everything from the package in your current workspace using * (not recommended):

In [ ]:
from numpy import *    # not recommended

Check version of a module:

In [35]:
np.__version__
Out[35]:
'1.21.5'

Check where the module is located:

In [36]:
np.__file__
Out[36]:
'/home/balam/anaconda3/lib/python3.9/site-packages/numpy/__init__.py'

Check out module content (using Linux built-in cat command):

In [ ]:
!cat /home/balam/anaconda3/lib/python3.9/site-packages/numpy/__init__.py

Check out package architecture (using Linux third-party tree command):

$ sudo apt install tree
In [43]:
!tree /home/balam/anaconda3/lib/python3.9/site-packages/numpy/

install third-party packages¶

Third-party packages are usually installed using package managers:

  • pip => Python Packaging Authority’s recommended tool for installing packages from the Python Package Index (PyPI)
    $ pip install <package_name>
    
  • conda => Anaconda's package manager for installing packages from the Anaconda repository and Anaconda cloud:
    $ conda install <package_name>
    
Note: there are some differences between the `pip` and `conda`, see [here](https://www.anaconda.com/blog/understanding-conda-and-pip) for details.

create packages¶

You can create your own packages and modules to reuse your code in an organized manner.

create directory and python module¶

  1. Create directory for python package:
In [52]:
!mkdir /home/khola/Documents/CODE/python/volcano/
  1. Create empty __init__.py file to tell python the directory is a package:
In [54]:
!touch /home/khola/Documents/CODE/python/volcano/__init__.py
  1. Create python script:
In [74]:
%%writefile /home/khola/Documents/CODE/python/volcano/mymodule.py 

import numpy

myvariable1 = 1
myvariable2 = 2

def add(x,y):
    return x+y
Overwriting /home/khola/Documents/CODE/python/volcano/mymodule.py
  1. Check result:

Install Linux third-party tree command:

$ sudo apt install tree
In [44]:
!tree /home/khola/Documents/CODE/python/

add path to .bashrc (on Linux)¶

You should add the following line to your .bashrc file (in Linux):

export PYTHONPATH=/path/to/your/python/dir
#export PYTHONPATH=/home/khola/Documents/CODE/python

The cell below adds the line 'export PYTHONPATH=/home/khola/Documents/CODE/python' to the end of your .bashrc file.

In [69]:
#!echo 'export PYTHONPATH=/home/khola/Documents/CODE/python' >> ~/.bashrc

Check results:

In [71]:
!tail /home/khola/.bashrc  # replace </home/khola/> with your path
else
    if [ -f "/home/khola/anaconda3/etc/profile.d/conda.sh" ]; then
        . "/home/khola/anaconda3/etc/profile.d/conda.sh"
    else
        export PATH="/home/khola/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda initialize <<<

Source your file (or restart your computer):

In [73]:
# !source ~/.bashrc

import your module¶

In [61]:
from volcano import mymodule
In [62]:
print(mymodule.myvariable1)
1
In [63]:
mymodule.add(2,3)
Out[63]:
5

Functional Programming¶

Python supports functional programming with list/dict/set comprehensions, map, reduce, etc.

Functional programming is a programming paradigm — a style of building the structure and elements of computer programs — that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data (wikipedia)

list/dict comprehension¶

In [82]:
# - list comprehension
squares = [x*2 for x in range(5)]                         # list comprehension
print(squares)

squares = [x**2 for x in range(5) if x == 2]             # list comprehension with if condition
print(squares)

squares = [x**2 if x == 2 else x*1 for x in range(5)]    # list comprehension with if-else condition
print(squares)
[0, 2, 4, 6, 8]
[4]
[0, 1, 4, 3, 4]
In [76]:
# - dictionary comprehension
squares_d = {x: str(x ** 2) for x in range(5)}
squares_d
Out[76]:
{0: '0', 1: '1', 2: '4', 3: '9', 4: '16'}

map¶

map maps a function to an iterable:

In [104]:
list_of_iteratables = [range(x) for x in range(5)]
list_of_iteratables
Out[104]:
[range(0, 0), range(0, 1), range(0, 2), range(0, 3), range(0, 4)]
In [105]:
list(range(0, 4))
Out[105]:
[0, 1, 2, 3]
In [106]:
m = map(sum, list_of_iteratables)
m
Out[106]:
<map at 0x7fca6bd5b940>
In [107]:
list(m)
Out[107]:
[0, 0, 1, 3, 6]

lambda functions¶

lambda functions are anonymous functions, i.e. functions that are not bound to a name.

In [109]:
def f(x): return x**2  # standard function
In [110]:
g = lambda x: x**2     # lambda function
In [111]:
print(f(2))
print(g(2))
4
4

Code style¶

Python code style is defined in PEP8 (Python Enhancement Proposal).

Some key rules:

  • use four spaces for indenting, nothing else
  • spaces around operators (but not for keyword arguments or default values)
  • spaces behind commas, no spaces around braces or parenthesis
  • variable names at least three letters
  • classes use CamelCase
  • everything else snake_case
  • two empty lines before classes and functions
  • one empty lines before methods
  • 78 characters per line
  • tools exist to automatically check syntax in your editor
  • tools: pep8, flake8, etc

Not covered¶

  • function decorators
  • iterators, generators, yield keyword
  • context manangers for elegant file reading
  • global variables and scope
  • and so much more!

Final Exercises¶

1. Write a class Rectangle constructed by a length and width and a method which will compute the area of a rectangle

Solution:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def compute_area(self):
        return self.length * self.width

# Example usage:
rectangle = Rectangle(5, 10)
print(f"Area of rectangle = {rectangle.compute_area()}")
2. Write in the most condensed form, a way to edit a list of integers ranging from -10 to 10 with step 2, taking the squared value of negative values and the double of positive values.

Solution:

[x**2 if x<2 else x*2 for x in range(-10, 10, 2)]