Python Crash Course¶

Table of contents¶

    • Python features
    • What is jupyter (formerly ipython) notebook?
  • PART - 1
    • Built-in magic commands in jupyter
    • Python interpreter
    • print()
    • type()
    • help()
    • Identifiers
    • Python3 keywords list
    • Assignment operator
    • List of python literals
    • Comments and multiline strings in python
    • Python is strongly dynamically typed
    • Type conversion
    • Math operators
    • Boolean operations
    • Explicit type conversion
    • Standard numeric operations
    • Input data
  • PART-2
    • Python modules
    • Import a module
    • Common compound data structure literals
    • Tuples
    • named tuples
    • Lists
    • Shallow copy vs Deep copy
    • More on lists
    • Set types
    • Set operations
    • Dictionaries
    • Looping dictionary's elements
    • Other dictionary's methods
    • Control flow tools:
    • Conditional statements
    • Loops
    • The pass statement
    • Iterables
    • Looping techniques on iterables
    • Context manager
    • Handling exceptions
    • Function definition in python
    • Keyword and positional arguments
    • Lambda expressions

Python features¶

  • Easy to use and learn
    • Readability
  • Interpreted language
    • Portability

What is jupyter (formerly ipython) notebook?¶

  • this document is a jupyter notebook
  • flexible tool to create readable documents that embed:
    • code
    • images
    • comments
    • formulas
    • plots
  • jupyter supports notebooks form many different languages (including python, julia and R)
  • each document runs a computational engine that executes the code written in the cells

PART - 1¶

Built-in magic commands in jupyter¶

  • jupyter embeds special commands (called magics) that enables to run different kind of code.
    • magic inline commands start with "%"
    • magic multiple line commands start with "%%"
    • you can list magic commands running %lsmagic
    • you can pop-up the magic command documentation (if any) adding "?" after the command with no space
  • few magics you might find useful:
    • ! runs inline shell commands
    • %%bash to run bash program (same syntax for other languages)
    • embed latex inline between \$\$
    • %%latex to render a latex block
    • ...
    • %reset to remove all names defined by the user
In [ ]:
%lsmagic
In [ ]:
!ls | grep ipy

Python interpreter¶

  • Compile source to bytecode then execute it on a virtual machine
  • Survives any execution error, even in case of syntax errors
    • in python also indentation is syntax
  • expression is code that returns an object
  • if no error, the prompt (">>>") automatically:
    • prints the result on screen
    • assigns the result to "_"
In [ ]:
# expression that returns something
12345
In [ ]:
# that something was assigned to "_"
_
In [ ]:
# Error example: division by 0
1/0

Note: Some of the cells in this notebook create such errors as you will see once you try them. Notice them carefully to see how python errors are written.

print()¶

  • prints arguments on standard output
  • can receive an arbitrary number of arguments
  • ends with "\n" unless differently specified with "end" key

type()¶

  • returns the type of the argument

help()¶

  • The Python interpreter comes with a help() function
    • call "help(something)" to learn about something (if something is defined
    • type "help()" to start the help prompt ("help>"), to exit execute quit
In [ ]:
# Following import is for printing in python 3.x format
# ignore this line for now
from __future__ import print_function 
In [ ]:
print("print") 
print("ends")
print("with")
print("newline") 

print("\nunless", end=" ")   # ends with a space instead of newline
print("differently specified")

print("\nprint","can","accept","multiple","arguments")   # sep with a separator instead of a space 
print("\nprint","can","accept","multiple","arguments", sep="\_/")  
In [ ]:
type(print)
In [ ]:
help(print)

Identifiers¶

  • Identifiers are names that are assigned to objects
    • Identifiers cannot start with a digit
    • nor contain hyphens (minus signs)
    • nor periods (dot signs)
    • nor blank spaces
  • descriptive names should be preferred
  • short names should be preferred

Python3 keywords list¶

  • keywords are reserved words that already means something
  • reserved means these words cannot be Identifiers
False,None,True,and,as,assert,break,class,continue,def,del,elif,else,except,finally,for,
from,global,if,import,in,is,lambda,nonlocal,not,or,pass,raise,return,try,while,with,yield

Assignment operator¶

  • the equal sign is the assignment operator, which means...
  • ..."evaluate the operand/expression on the right side of the equal sign and assign that to the operand on the left (the 'lvalue').”
  • python provides cascaded assignments
    • var1 = var2 = var3 = value
  • python provides augmented assignment operators
    • standard math operations: +=,-=,*=,/=
    • modulo,remainder,exponentiation: %=,//=,**=
    • bitwise math operations: &=,|=,^=,>>=,<<=
In [ ]:
# cascade assignments
x = y = z = 10  
# augmented assignments
x/=2
y+=1
print("x =",x,type(x))
print("y =",y,type(x))
print("z =",z,type(x))

List of python literals¶

  • strings (collection of characters enclosed in quotes "" or '')
    • ''' or """ starts (ends) a multiline string (triple-quoted string)
  • integer numeric literals
  • float numeric literals
  • complex numeric literals ('j'/'J' yields an imaginary number)
  • boolean literals which are "True" or "False" (both keywords)
  • None
In [ ]:
print("\"A\"", "is a", type("A"))
print("\'A\'", "is also a", type("A"))
print("in other words, \"A\" is 'A' - >","A" is 'A')
print("\"\"\"\nmulti\nline\nstring\n\"\"\"", "is also a", type("""\nmulti\nline\nstring\n"""))
print(1,"is a", type(1))
print(1.,"is a", type(1.))
print(1.+1j,"is a",type(1.+1j))
print("True","is a",type(True))
print(None,"is a",type(None))
In [ ]:
print("\nyou should use \\ to print special characters \" or \'")
print("\nstrings defined by \" understands special character ' without \\ ")
print('strings defined by \' understands special character " without \\' )

Comments and multiline strings in python¶

  • # starts an inline comment
  • triple-quoted strings can be used for documentation (returned by help() function)
    • we'll see this later
  • triple-quoted strings can be used as multiline comments
In [ ]:
# to comment this line

s = '''triple-quoted 
strings
are   
multiline
'''
'''
triple-quoted strings that are not docstrings (first thing in a class/function/module) are ignored 
and can be used 
as multiline comments
'''

print(s) 

Python is strongly dynamically typed¶

  • strong means that every change requires an explicit conversion
  • dynamic typing means that values have a type, not variables
    • i.e., variables can name any object
    • only keywords cannot be reassigned
  • also functions such as print() and type() use names that are not keywords
    • they could potentially reassigned to new objects
In [ ]:
print("What means that python is DYNAMICALLY typed\n")
myVar = 1
print("myVar =",myVar,"\n -> myVar names a",type(myVar))
myVar = "myVar"
print("myVar =",myVar,"\n -> now it names a",type(myVar))
myVar = None
print("myVar =",myVar,"\n -> now it names a",type(myVar))
In [ ]:
print("\n#reassign objects to print and type \nprint=10 \ntype=-1 \nprint,type")
print = 10
type = -1
print, type
In [ ]:
print(1)
In [ ]:
# force reset of all user defined names, included print and type
%reset -f 
# now print and type are back to work
print("back to work", type("back to work"))

Type conversion¶

  • Generally, conversions MUST be explicit because Python is strongly dynamically typed
  • However, Python does some automatic conversions
    • between numeric types (where it makes sense)
    • empty container types as well as zero are considered False in a boolean context

Math operators¶

  • python provides standard arithmetic operators +,-,*,/
  • exponentiation operator **
    • ** is right associative
  • floor division operator //
    • a//b returns the greatest whole number that does not exceed a/b
  • modulo (reminder) %
    • complements // by telling what remains from the floor division
  • parentheses can change the order of operations
In [ ]:
print("implicit conversion between numeric literals is allowed, if that makes sense")
print("(12.+1j)+3 -> ",(12.+1j)+3)
print("\nimplicit conversion from string to a numeric literal returns an error")
print("'12'+3 \n -> returns")
'12'+3
In [ ]:
print("\n#Operations between numbers")
print("\n# summation with float -> float","\ntype(1+1.)")
print(type(1+1.))     
print("\n# division with integers -> float","\ntype(3/2)")
print(type(3/2))      
print("\n# operations between parentheses are executed earlier ","\n4/(2+2)")
print(4/(2+2))        
print("\n# exponentiation (default right associative)","\n2**3",", 2**3**2",", 2**(3**2)",", (2**3)**2")
print(2**3, ",", 2**3**2, ",", 2**(3**2), ",", (2**3)**2)
print("\n# floor division ","\n9.5/2",", 9.5//2",", 9.1//2",", 9.9//2")
print(9.5/2, ",", 9.5//2, ",", 9.1//2, ",", 9.9//2)
print("\n# modulo ",", 9.5%2",", 9.1%2",", 9.9%2")
print(9.5%2, ",", 9.1%2, ",", 9.9%2)

Boolean operations¶

  • standard boolean operations: and, or, not
    • x or y (if x is false, then y, else x)
    • x and y (if x is false, then x, else y)
    • not x (if x is false, then True, else False)
  • standard comparison operations: <,<=,>,>=,==,!=
    • (strictly) greater/less (or equal) than, equal, not equal
  • object identity "is"
  • negated object identity "is not"
In [ ]:
x = True
y = False

print("\nx=True","y=False")
print ("\nx or y =",x or y,"\nx and y =",x and y,"\nnot x =", not x)

print("\n0>0 =",0>0, "\n0>=0 =",0>=0,"\n0<0 =",0>0, "\n0<=0 =",0>=0, "\n0==0 =",0==0, "\n0!=0 =",0!=0)

print("\ntype(5.) is float =",type(5.) is float, "\ntype(5.) is not float =",type(5.) is not float)

Explicit type conversion¶

  • int(x): x converted to integer
    • bin(x): returns a string with the binary representation
  • float(x): x converted to float
  • complex(re,im): real part re, imaginary part im (im default is zero)
  • str(x): x converted to string
In [ ]:
print("\nconverting numeric literals to other numeric literals")
print("int(-4.) ->",int(-4.),type(int(-4.)))
print("float(-4) ->",float(-4),type(float(-4)))
print("complex(-4) ->",complex(-4),type(complex(-4)))
In [ ]:
print("complex type cannot be converted neither to int...")
int(4.+0j)
In [ ]:
print("...nor to float")
float(4.+0j)
In [ ]:
print("\nconverting strings to numbers")
print("int(\"3\") ->",int("3"),type(int("3")))
print("float(\"3.\") ->",float("3."),type(float("3.")))
print("complex(\"3.\") ->",complex("3."),type(complex("3.")))
print("float(\"3\") ->",float("3"),type(float("3")))
print("float(\"3.\") ->",float("3."),type(float("3.")))
print("complex(\"3\") ->",complex("3"),type(complex("3")))
print("complex(\"3.\") ->",complex("3."),type(complex("3.")))    
print("complex(\"3+0j\") ->",complex("3+0j"),type(complex("3+0j")))    
In [ ]:
print("\nconverting numbers to strings")
print("str(3) ->",str(3),type(str(3)))
print("str(3.) ->",str(3.),type(str(3.)))
print("str(3+3j) ->",str(3+3j),type(str(3+3j)))
In [ ]:
print("\nwhen the conversion numeric to string fails: \n")
try:
    print("int(\"3.\") ->",int("3."),type(int("3.")))
except ValueError:
    print("int(\"3.\")\n ->","error: invalid literal for int() with base 10: '3.'")
print()    
try:
    print("float(\"3+0j\") ->",float("3+0j"),type(float("3+0j")))    
except ValueError:
    print("float(\"3+0j\")\n ->","error: could not convert string to float: '3+0j'")    
In [ ]:
print("print integers in binary format")
print("bin(40) ->",bin(40),type(bin(40)))
print("bin(40%2**4) ->",bin(40%2**4))
print("bin(40%2**6) ->",bin(40%2**6))
print("bin(40%2**32) ->",bin(40%2**32))

print("\nbinary format make sense only for integers\n")
try:
    print("bin(0.) ->",bin(0.))
except: 
    print("bin(0.)\n ->","'float' object cannot be interpreted as an integer")
print()
try:
    print("bin(0j) ->",bin(0j))
except:
    print("bin(0j)\n ->","'complex' object cannot be interpreted as an integer")

Standard numeric operations¶

  • abs(x): absolute value or magnitude of x
  • c.conjugate(): conjugate of the complex number c
  • divmod(x,y): the pair (x // y, x % y)
  • pow(x,y): x to the power y, same as x ** y
  • round(x,n): rounds x to a given precision in n decimal digits
    • default n=0
    • n can be negative
In [ ]:
print("\nabs(-4.5) ->",abs(-4.5),type(abs(-4.5)))
print("complex(-4+1j).conjugate() ->",complex(-4+1j).conjugate())
print("divmod(10,3) -> ",divmod(10,3))
print(" -> (10//3,10%3) -> ",(10//3,10%3))
print("pow(10,3) -> ",pow(10,3))
print(" -> (10**3) -> ",(10**3))
In [ ]:
print('round(1234.1234,4) ->',round(1234.1234,4))
print('round(1234.1234,3) ->',round(1234.1234,3))
print('round(1234.1234,2) ->',round(1234.1234,2))
print('round(1234.1234,1) ->',round(1234.1234,1))
print('round(1234.1234,0) ->',round(1234.1234,0))
print('round(1234.1234,-1) ->',round(1234.1234,-1))
print('round(1234.1234,-2) ->',round(1234.1234,-2))
print('round(1234.1234,-3) ->',round(1234.1234,-3))
print('round(1234.1234,-4) ->',round(1234.1234,-4))

Input data¶

  • input(): forward row input to frontends (as a string)
  • eval(): evaluate the expression contained in a string
In [ ]:
name = input("name: ")
age = input("age: ")
print(name,"is",age,"years old")
In [ ]:
thisVal = "\nthis expression just prints a string\n"
expression = input("expression (e.g., print(thisVal)): ")
eval(expression)



PART-2¶

Python modules¶

  • a module is a file containing Python definitions and statements
  • the file name is the module name with the suffix .py appended
  • within a module, the module’s name (as a string) is available as the value of the global variable __name__
  • a module can contain executable statements as well as function definitions
    • statements initialize the module and are executed once
  • modules can import other modules
  • dir() builtin function lists the names the user has defined currently
    • dir(module) returns which names the module "module" defines as a sorted list of strings

Import a module¶

  • import module [as] followed by a module name
    • imports the module referred as that name
    • customary but not required to place import statements at the beginning of a module/script
  • from import keywords
    • pros: less typing and more control over which items can be accessed
    • cons: lose context (i.e., you call fun() instead of module.fun())
    • the namespace can be cluttered by import * (anything)
import <module-name> [as <alias>] 
from <module-name> import <submodule>
from <module-name> import *
In [ ]:
import math

print("\nimport math","# include math"\
      "\n%reset -f", "# cleans the environment")
%reset -f

print("\nprint(dir())", "# math should not be listed")
print(dir())
print("\nimport math", "")
import math
print("\nprint(dir())", "# now math is listed")
print(dir())
print("\nprint(dir(math))")
print(dir(math))

#dir(math) 
In [ ]:
print("math.__name__ ->",math.__name__)
print("math.sin(math.pi/2) ->",math.sin(math.pi/2),"\n")

import math as m
print("import math as m")
print("m.__name__ ->",m.__name__)
print("m.sin(m.pi/2) ->",m.sin(m.pi/2),"\n")

from math import sin
print("from math import sin")
print("sin(3.14/2) ->",sin(3.14/2),"\n")

from math import * # now I know pi!
print("from math import * # now I know pi!")
print("pi is",pi)

Common compound data structure literals¶

  • sequence types
    • tuples: (1,2,3)
    • lists: [1,2,3]
    • range() - explained with loop statements

  • set types
    • sets: {1,2,3}
    • frozenset

  • mapping type
    • dictionaries: {1:'one',2:'two',3:'three'}

Tuples¶

  • tuples store a collection of ordered objects
  • tuples are meant to store data and are immutable (do not support item assignment)
    • no add, remove or or replace on the fly
    • immutability is a feature, not a restriction
  • tuples can be used to assign simultaneously multiple objects
In [ ]:
t = (1,1.,"str",("another","tuple"))
v1,v2,v3,v4 = t

print("\nsimultaneous assignment")
print("t = (1,1.,\"str\",(\"another\",\"tuple\"))",type(t))
print("\nsimultaneous assignment")
print("v1,v2,v3,v4 = t")
print("-->\nv1 =",v1,type(v1),"\nv2 =",v2,type(v2),"\nv3 =",v3,type(v3),"\nv4 =",v4,type(v4))
In [ ]:
print("tuples are immutable")
t[0] = 2

named tuples¶

  • named tuples are tuples who have an identifiers and attributes
    • need to import from the module collections
In [ ]:
from collections import namedtuple
contact = namedtuple("Contact", "Name Surname Email Phone")
myContact = contact("J","R","jr-mail","jr-phone")

name,surname,email,phone=myContact
print(myContact,"is a",type(myContact))
print(name,surname,email,phone)
In [ ]:
print("# assign nametuple needs all fields")
anotherContact = contact('Name')

Lists¶

  • a list collects comma separated values between squared brackets
    • len(list) or list.__len__() gives the list length
  • lists are mutable
  • a list can indexed and sliced (using ":")
  • a list can contain any data type
  • the same list can contain different data types (e.g., lists can nest lists)
  • support concatenation
In [ ]:
squares = [1, 4, 9, 16, 24] # 24 should be 25 
print("squares ->", squares) 
print("length of square is", squares.__len__())
print("\n# indexing returns the item")
print("squares[0] ->",squares[0],type(squares[0])) 
print("squares[-1] -> ",squares[-1],type(squares[-1]))   

print("\n# slicing returns a list of items")
print("squares[2:] ->",squares[2:],type(squares[2:])) # list of idx 2,3,...,len(list)
print("squares[:3] ->",squares[:3],type(squares[:3])) # list of idx 0,1,2
print("squares[0:1] ->",squares[0:1],type(squares[0:1]))  
print("squares[-3:] -> ",squares[-3:],type(squares[-3:])) # list of last three elements
In [ ]:
squares = [1, 4, 9, 16, 24] # 24 should be 25 
print("\n# new values can be assigned both using indexing and slicing")
print("squares ->",squares)
squares[4] = 25 
print("squares[4] = 25 ")
print("squares ->",squares)
print("squares[0:2] = [-1,-1]")
squares[0:2] = [-1,-1] 
print("squares ->",squares)

print("\n# NB: assignment does not need consistency between lengths of slices")
print("squares[0:1] = ['what?',squares[0]]")
squares[0:1] = ['what?',squares[0]]
print("squares ->", squares)
print("squares[0:3] ->", squares[0:3])
print("squares[0:3] = [1,4]")
squares[0:3] = [1,4]
print("squares ->", squares)
In [ ]:
squares = [1, 4, 9, 16, 25]
other_squares = [36,49,64]
print("\n# lists can be concatenated")
print("squares -> ",squares)
print("other_squares -> ",other_squares)
print("squares+other_squares -> ",squares+other_squares)

print("\n# new values can be appended to list")
print("squares -> ",squares)
print("squares.append(36)")
squares.append(36)
print("squares -> ",squares)
In [ ]:
print("\n# values can be removed just by slicing")
print("squares[5:6] = []")
squares[-3:] = []
print("squares -> ",squares)

print("\n# clear a list replacing with empty list")
print("squares[:] = []")
squares[:] = []
print("squares -> ",squares)
In [ ]:
print("\n# nested list")
print("l = [['a','b'],[1,2]]")
l = [['a','b'],[1,2]]
print("l[0][1] ->",l[0][1])
print("l[1][0] ->",l[1][0])

Shallow copy vs Deep copy¶

  • shallow copy: the target is bind to the assigned object (i.e., they have the same reference)
  • deep copy: the target contains the same values of the assigned object (i.e., they do not have the same reference)

  • python copies the reference

    • there is a difference for compound objects (i.e., objects that contain other objects, like nested lists)
  • module copy contains the implementation for deep copy

    • copy.copy(x): returns a shallow copy of x
    • copy.deepcopy(x): returns a deep copy of x
In [ ]:
%reset -f
import copy

l_1 = [1,2,'3']
l_2 = copy.copy(l_1)     # shallow copy
l_3 = copy.deepcopy(l_1) # deep copy

l_2[0] = 'changes!'
l_3[0] = 'changes!'

print(l_1)
print(l_2, "<- shallow copy")
print(l_3, "<- deep copy")
In [ ]:
%reset -f
import copy
a1 = [[1, 2, 3], [4, 5, 6]]
a2 = copy.copy(a1) # shallow copy

print("before assignments")
print("a1 =",a1)
print("a2 =",a2)

a2[0][0] = 'one'

print("after assignments")

print("a1 =",a1)
print("a2 =",a2)
print("Note: ")
print("aX[0][0] ==",\
      a1[0][0],'==',\
      a2[0][0])
print("aX[1][1] ==",\
      a1[1][1],'==',\
      a2[1][1])
In [ ]:
%reset -f
import copy
a1 = [[1, 2, 3], [4, 5, 6]]

a2 = copy.deepcopy(a1) # deep copy

print("before assignments")
print("a1 =",a1)
print("a2 =",a2)

a2[0][0] = 'one'
a2[1][1] = 'five'

print("after assignments")

print("a1 =",a1)
print("a2 =",a2)
print("Note: ")
print("aX[0][0] ->",\
      a1[0][0],'!=',\
      a2[0][0])
print("aX[1][1] ->",\
      a1[1][1],'!=',\
      a2[1][1])

More on lists¶

  • list.append(x): appends x to the end of the list
  • list.extend(iterable): appends all items from iterable
    • same of a[len(a):] = iterable
  • list.insert(i,x): inserts the value x at a given i-th position
  • list.remove(x): removes the first item whose value is x
  • list.pop([i]): returns the i-th item and removes it from the list
  • list.clear(x): clear the list
    • same of del a[:]
  • list.index(x[, start[, end]]): returns index of first item whose value is x
    • if none raises a ValueError
    • start/end limit the search in a slice
  • list.count(x): returns the number of times x appears in the list
  • list.sort(key=None,reverse=False): sort items, see sorted()
  • list.reverse(): reverse the elements in place
  • list.copy(): returns a shallow copy
In [ ]:
%reset -f
# append, extend
l = [] 
l.append(1)
l.append(2)

l2 = [0] 
l2.extend(l)

print('l  ->',l)
print('l2 ->',l2)
In [ ]:
%reset -f
# insert, remove, pop, clear
l = [0,1,2] 
l.insert(2,1.5)
l.insert(4,2.5)
l.insert(4,3.5)
print('l  ->',l)
l.remove(2.5)
print('l.remove(2.5)  ->\n\t l =',l)
el = l.pop()
print('el = l.pop() ->\n\tl =',l, '\n\tel =',el)
In [ ]:
%reset -f
# copy,sort,count,reverse,index
l = [0,1,0,1,1,2,2,2,3,1,1,2,2,2,2] 
lc = l # lc = l.copy() works only in python 3.X
lc[0] = -1
print('l   -> ',l)
print('lc  ->',lc)
l.sort()
print('\nl.sort()\nl ->',l)
print('\nl.count(2) is',l.count(2))
l.reverse()
print('\nl.reverse()\nl ->',l)
print('\nfirst zero at idx',l.index(0))
print('l[12:] ->',l[12:])

Set types¶

  • curly braces or set() function create sets
    • set() with no arguments creates a set
    • {} with no arguments creates a dictionary - see later
  • sets are unordered collections with no duplicate elements
  • sets are mutable objects
  • in keyword is suitable to check the membership of a value in the list
  • elements in set must be hashable object
    • means they have a hash value which never changes during their lifetime
    • hashability makes objects usable as a dictionary key and a set member
  • sets themselves are NOT hashable
    • cannot be used as dictionary keys
    • sets of sets are not allowed
  • frozen sets are hashable sets
    • can be used as dictionary keys
    • sets of frozen sets are allowed

Set operations¶

  • a.difference(b) or a-b: elements in set a that are not in set b
  • a.union(b) a|b: union of elements in set a and in set b
  • a.intersection(b) a&b: intersection of elements in set a and in set b
  • a.symmetric_difference(b) a^b: elements in set a or set b but not in both
    • equals union-intersection ((a|b)-(a&b))
  • a.issubset(b) a<=b: test whether every element of a is in b
  • a.issuperset(b) a>=b: test whether every element of b is in a
In [ ]:
set_1 = set([1,2,1,2,3]) 
set_2 = set([4,5,6,1,7]) 

print("set_1 ->","{1,2,1,2,3} # 1 and 2 are repeated")
print("set_2 ->","{4,5,6,1,7} # 1 is in both ")
print("\n# use of 'in' keyword")
print("3 in set_1 ->",3 in set_1)
print("\n#set difference: elements in one but not in the other")
print("set_1-set_2 ->",set_1-set_2)
print("set_2-set_1 ->",set_2-set_1)
print("\n#set union and intersection")
print("set_1|set_2 ->","set_2|set_1 ->",set_1|set_2)
print("set_1&set_2 ->","set_2&set_1 ->",set_1&set_2)
print("\n#set symmetric difference == set difference between set union and set intersection")
print("set_1^set_2 ->","set_2^set_1  ->",set_1^set_2)
print("(set_1|set_2)-(set_1&set_2) ->",(set_1|set_2)-(set_1&set_2))
print("\n#super/subset test")
print("set_1>=set_2 ->",set_1>=set_2)
print("set_1<=set_2 ->",set_1<=set_2)
print("{1,2,3}<={1,2,3,4,5,6,7} ->",{1,2,3}<={1,2,3,4,5,6,7})
print("{1,2,3,4,5,6,7}>={1,2,3} ->",{1,2,3,4,5,6,7}>={1,2,3})
In [ ]:
print("# sets cannot contain unhashable types")
set_1 = set([set(['a'])])
In [ ]:
print("\n# frozen sets are hashable",\
      "\n# -> can be elements of set",\
      "\n# -> can be keys of dictionaries")
set_1 = set([frozenset('a'),frozenset('b')])
print(set_1)

Dictionaries¶

  • it is mapping type (also called "associative memories" or "associative arrays")
  • dict() builds dictionaries from sequences of key-value pairs
  • unlike sequence types, indexed by numbers, dictionaries are indexed by keys
    • only hashable types can be keys
  • dict are unordered sets of key:value pairs
    • module collections offers a class of ordered dictionaries (i.e., OrderedDict, which remembers the order in which its contents are added)
    • ordered dictionaries support reversed()
  • [not] in keywords check if a key is [not] in dictionaries
  • del keyword is needed to delete a key:value pair
In [ ]:
%reset -f

# using {}
address_book={'Anoop':1,'Vasily':2}
print("\naddress_book ->",\
      address_book,"\n ->",\
      type(address_book))
print("'Jens' not in address_book' ->",\
         'Jens' not in address_book)


# using dict(list_of_tuples)
mensa_dishes=dict([('first','pasta'),\
                   ('second','salade'),\
                   ('dessert','apple')])
print("\nmensa_dishes ->",\
      mensa_dishes,"\n ->",\
      type(address_book))
print("'first' in address_book' ->",\
         'first' in mensa_dishes)
In [ ]:
from collections import OrderedDict
data = {'c':3,'a':1,'b':2,'d':4}
ordered_data = OrderedDict(sorted(data.items())) 
print('\n# dictionary vs ordered dictionary')
print('\ndata is', type(data),'\n ->            ',data)
print('\nordered_data is',  type(ordered_data),'\n ->',ordered_data)
In [ ]:
%reset -f
address_book={'Anoop':1,'Vasily':2}
print("address_book =",address_book)

print("\n# delete key")
print("'Anoop' in address_book ->",'Anoop' in address_book)
print("del address_book['Anoop']")
del address_book['Anoop']
print("address_book ->",address_book)
print("'Anoop' in address_book ->",'Anoop' in address_book)

print("\n# add new key")
print("address_book['Jens'] = 3")
address_book['Jens'] = 3
print("address_book ->",address_book)

Looping dictionary's elements¶

  • it is possible looping dictionary's elements
    • dict.keys(): retrieves list of keys
    • dict.values(): retrieves list of values
    • dict.items(): retrieves list of (key,value) pairs
  • list() or sorted() built-in functions list values (arbitrary or sorted order)
  • to sort keys by value
    • sorted(dict,key=dict.get)
    • use lambda functions
In [ ]:
%reset -f
week = {'mon':1,'tue':2,'wed':3,'thu':4,'fri':5,'sat':6,'sun':7}
print('\nlist(week)\n->',list(week))
print('\nsorted(week)\n->', sorted(week))
print('\nsorted(week, key=week.get)\n->',sorted(week, key=week.get))

Other dictionary's methods¶

  • dict.copy(): shallow copy of of dict
  • dict.clear(): removes all key/value pairs in dict
  • dict.update([key:value]): overwrites key/value pairs from another dict
  • dict.get(k[,x]): retreives dict[k] if k in dict else x
  • dict.setdefault(k[,x]): if k in keys, then get(), otherwise set the key
  • dict.pop(k[,x]): get(k[,x]), then remove key k
  • dict.popitem(): pop() first element in dict
In [ ]:
%reset -f
week = {'mon':1,'tue':2,'wed':3,'thu':4,'fri':5,'sat':6,'sun':7}

print("\n#update value for a key")
print("week.get('_8thday',8) ->",\
      week.get('_8thday',8)) # week['_8thday'] does not exist

week.setdefault('_8thday',8)
print("\nweek.setdefault('_8thday',8)") 
print("week\n ->",week)

print("\n#update value for a given key")
print("week['_8thday'] ->",week['_8thday'])
week.update({'_8thday':None})
print("week.update({'_8thday':None})\nweek['_8thday'] ->",week['_8thday'])
In [ ]:
%reset -f
week = {'mon':1,'tue':2,'wed':3,'thu':4,'fri':5,'sat':6,'sun':7}

print("\nweek\n ->",week)
_9thday = week.pop('_9thday','8_days_already_too_much')
print("_9thday = week.pop('_9thday','8_days_already_too_much')"+\
      "\n_9thday ->",_9thday)

print("\nweek\n ->",week)
_8thday = week.pop('_8thday','8_days_already_too_much')
print("_8thday = week.pop('_8thday','8_days_already_too_much')"+\
      "\n_8thday ->",_8thday)
print("week\n ->",week)

print("\nweek\n ->",week)
a_day = week.popitem()
print("a_day = week.popitem() ->",a_day)
print("week\n ->",week)

Control flow tools:¶

  • conditional statements
    • if, elif, else, keywords
  • loop statements
    • while, for, break, continue, else keywords
    • range() function
  • reminder statement
    • pass keyword
  • context manager
    • with, as keywords
    • open() function
  • handling exceptions statement
    • try, except, finally keywords

Conditional statements¶

  • conditional statements
    • if, elif, else, keywords
      if <condition>:
          <body_1>
      elif <another_condition>:
          <body_2>
      else:
          <body_default>
      
    • in, not keywords
      # is there an item equal to value?
      if <value> in <group_of_items> 
      # none items are equal to value?
      if <value> not in <group_of_items>
      
In [ ]:
if True: print("if can be inline")
In [ ]:
if 5>4:
    print("5 is bigger than 4")
if 4<5:
    print("because 4 is smaller than 5")
else:
    print("I would be confused otherwise")
In [ ]:
if 0:
    print("this should not be printed")
elif None:    
    print("this should not be printed")
elif (-1.+2j): 
    print("0 and None means False\n any other value is True ")
In [ ]:
%reset -f
t = (3,4,5)       # tuple
l = [3,4,5]       # list
s = {3,4,5}       # set
d = {3:1,4:2,5:3} # dictionary
v = 1
print("'in'/'not in' work on any iterable")
if v not in t:
    print("if v not in t:     -> True if none is equal to v")
if v not in l:
    print("if v not in l:     -> True if none is equal to v")   
if v not in s:
    print("if v not in s:     -> True if none is equal to v")    
if v not in d:
    print("if v not in d:     -> True if none is equal to v")      

Loops¶

  • loop statements
    • while, for, break, continue, else keywords
  • range([start,]stop[,step]) is very handy in loops
    • returns an object that produces a sequence of integers from start (inclusive, default 0) to stop (exclusive) by step (default 1)
    • start, stop and step must be integers
  • pass statement is a reminder and can be used anywhere

for <var> in <set_of_values>: 
    <body>
    if <condition>:
        continue
    <something_that_runs_if_condition_not_True>

while <true_condition>:
    <body>
    if <another_condition>:
        break
In [ ]:
print("range(2) is",type(range(2)),"\n")
print("\nfor loop example")
for i in range(2):
    print(i)
    
print("\nsame behavior using while loop")
i = 0    
while i in range(2): 
    print(i)
    i+=1
In [ ]:
for i in range(5):
    if i is 0: 
        continue # 0 is neither odd nor even
    if i%2 is 0:
        print(i,"is even")
        continue # an even number is not odd
    print(i,"is odd") # if i is even, this expression is not run

The pass statement¶

  • does nothing
  • helps avoiding unexpected EOF
  • useful to include conditions that have not been coded yet
In [ ]:
if True:
    pass
print('no syntax error')
In [ ]:
# use range with start,stop and step
for i in range(-5,7,2):
    print(i,end=",")# -5,-3,-1,1,3,5,
print()    
for i in range(-5,7,2): 
    if i is 3:
        print(i,"== 3")
    elif i == -1:
        print(i,"== -1")
    else:
        pass # not decided yet 
             # pass is handy even if it does nothing

Iterables¶

  • objects capable of returning its members one at a time
  • e.g., list, str, tuple, dict are all iterables

Looping techniques on iterables¶

  • reversed(iterable): returnes a reversed iterator (iterates from last to first)
  • zip(*iterables): returns an iterator of tuples that aggregates elements from each iterable
    • if *iterables have different size, zip aggregates up to the shortest length
  • enumerate(iterable): returns an iterator of tuples that retreives both position index and item
  • sorted(iterable[,key][,reverse]): returns the [reverse] sorted sequence as a key for sort comparison
  • dictionary items() method: retreives key and value zipped
In [ ]:
%reset -f

myList1 = ['1st_el','2nd_el','3rd_el'] 
myList2 = [111,222,333]

# myList1
for i in myList1:
    print(i,end=", ")
print()    

# reversed myList1
for i in reversed(myList1):
    print(i,end=", ")  
print('\n')        

# zipped lists (with same size)
for i,j in zip(myList1,myList2):
    print("[",i,",",j,"]", end=", ")
print()

# zipped lists (with different size)
myList2.pop()
for i,j in zip(myList1,myList2):
    print("[",i,",",j,"]", end=", ")
In [ ]:
%reset -f

myList1 = ['1st_el','2nd_el','3rd_el'] 
for i,j in enumerate(myList1):
    print("[",i,",",j,"]", end=", ")   
print('\n')   

unorderedList = ['2nd_el','1st_el','3rd_el'] 
print(unorderedList)
for i in sorted(myList1):
    print(i,end=", ")

Context manager¶

  • with [as] creates a context manager
with <expression> [as <variable>]:
    <with-body-block>
  • the expression (e.g., open()) must be an object that defines __enter__ and __exit__ methods
    • open() opens a file and returns a stream (read/write)
In [ ]:
with open('README.md', 'r') as f:
     print(f.readline()) # read first line of the file README.md 

Handling exceptions¶

  • handling exceptions using try, except, finally keywords
    • finally is executed no matters what
    • finally is good to release external resources
  • common exceptions are ZeroDivisionError, NameError, TypeError, ValueError, SyntaxError
  • exceptions of type AssertionError: assert keyword
  • exceptions of type Exception: raise keyword
try: 
    <try-body-block>    
except <Exception-name> as <alias>: 
    <except-body-block>
In [ ]:
print("# let's catch some common exceptions\n")
to_execute = ['1/0',\
              '4+unknown_var',\
              '"2"+2',\
              "int('s')",
              "print 1"]
for i in to_execute:
    try:
        print(i)
        eval(i)
        print("I am not going to be printed")
    except (ZeroDivisionError,NameError,TypeError,ValueError) as err:
        print(err.__class__,":",err)
    except SyntaxError as err: 
        print("'print 1' was not caught because it causes SyntaxError")
        print(err.__class__,":",err)
    finally: # should be at the end of try statement
             # useful to make sure all resources are released
             # even if an exception occurs
             # even if no exception was caught
        print("\t---last but not least, finally is executed---")
In [ ]:
x = -1
try:
    assert x>=0, 'x is negative'
except AssertionError as err: 
    print(err)
In [ ]:
try: 
    raise Exception(1,[2],{'3':3}) # an exception can be raised
                                   # with any argument
except Exception as err:
    print(type(err))
    print(err.args)
    print(err)      # print its arguments
    a,b,c = err.args    
    print(a,b,c)
    try: 
        a,b,c = err
    except TypeError as err: 
        print(err)

Function definition in python¶

  • def [return] keywords
    • non-void functions should be called for the returned value
    • void functions should be called to obtain side effects (e.g., print() function)
  • parentheses follow function's name and contain comma separated parameters
    • mandatory even if no parameter
  • return keywords embeds multiple outputs in a tuple
  • functions can embed docstrings for documentation
  • scope of a function
def <function_name>(<parameter_list>):
    '''docstring'''
    <body>
    return <result>
In [ ]:
print_returns = print()
print("print() returns ",print_returns)
In [ ]:
def f0():
    def g0():
        return "g0 is scoped"
    return g0()

try:
    g0()
except NameError as err:
    print(err)
print("\n",f0(),sep="")
In [ ]:
def EUR_to_USD(eur):
    '''
    calculate conversion from EUR to USD
    '''
    usd_for_1eur = 1.17
    return eur*usd_for_1eur

print("10 EUR means",EUR_to_USD(10),"USD")
print("\n---------------\n")
help(EUR_to_USD)
In [ ]:
def EUR_to_many(eur):
    '''
    calculate conversion from EUR to different currencies
    '''
    usd_for_1eur = 1.17
    yen_for_1eur = 0.0075
    gbp_for_1eur = 1.13 
    
    return (eur*usd_for_1eur,\
            eur*yen_for_1eur,\
            eur*gbp_for_1eur)

currencies = EUR_to_many(10) # tuple
USD,YEN,GBP = currencies     # unpack tuple
print("10 EUR means",USD,"USD\n\t",\
                "or",YEN,"YEN\n\t",\
                "or",GBP,"GBP")
print("EUR_to_many returns",type(currencies))

print("\n---------------\n")
help(EUR_to_many) # docstring

Keyword and positional arguments¶

  • default argument values can be passed, even unordered, using keyword arguments (named arguments)
  • keyword arguments are passed as a dictionary {key:value,...}
  • not keyword arguments are called positional arguments
  • positional arguments should be starred if after a keyword argument
    • *expression must evaluate an iterable
In [ ]:
def foo(par1=None,par2=None):
    res = None
    if par1 is 1:
        res = par1
    elif par2 is 2:
        res = par2
    if par1 is 1 and par2 is 2:
        res = "what a surprise!"
    return res

# order of positional args is important
print("foo(par1=1) ->",foo(par1=1),\
      "which equals foo(1) ->",foo(1))
print("foo(par2=2) ->",foo(par2=2),\
      "which is not foo(2) ->",foo(2))
# order of keyword args is not important
print("foo(par1=1,par2=2) ->",\
      foo(par1=1,par2=2))  
print("foo(par2=2,par1=1) ->",\
      foo(par2=2,par1=1))
In [ ]:
def foo(*positional, **keywords):
    print("Positional:", positional, end='\t')
    print("Keywords:", keywords)
    
foo('1st', '2nd', '3rd')
foo(par1='1st', par2='2nd', par3='3rd')
foo('1st', par2='2nd', par3='3rd')

# #Uncomment the following lines in python 3.x
# foo(par1='1st_key',*'1st_pos', par2='2nd_key')
# foo(par1='1st_key',*['1st_pos'], par2='2nd_key',*['2st_pos','3rd_pos'])

Lambda expressions¶

  • anonymous functions can be created with the lambda keyword
In [ ]:
def weighted_value(value):
    return lambda weight,bias: value*weight+bias # Please note: returns a lambda function

f1 = weighted_value(1)      
f10 = weighted_value(10)
print(f1(1.5,-1))           # weight=1.5, bias=-1:             (1.5*1)-1 == 0.5
print(f1(weight=0,bias=-1)) # supports keyword arguments       (0*1)-1 == -1    
print(f10(0,-2))            # (0*10)-2 == -2
print(f10(1.5,100))         # (1.5*10)+100 == 115
In [ ]:
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[0])
print(pairs)
pairs.sort(key=lambda pair: pair[1])
print(pairs)