Basics
Basics
Basics
– slides
– esercizi risolti
• DATE esami
– 20/6 ore 9
– 5/7 ore 9
– 30/7 ore 14
• [ITA] Introduzione a python, Tony Gaddis
• Think Python, by Allen B. Downey
Why python ?
Where to get python from
Freely available (for any platform) at http://www.python.org/
x=3*2\ x = 3 * (2 +
(5 + 4) 6)
x = 0; y = 0
BLANK SPACES
• Indentation is used to define blocks of code
• The number of blank spaces at the beginning of the line is arbitrary
(multiple of 4 is the standard) but it needs to be consistent within the
block
• Spaces within a line are meaningless C++
if (x > y) {
cout << ‘x is greater than y’ << endl;
if (x > y): dist = x – y;
print(‘x is greater than y’) } else {
This is a block of code
dist = x – y dist = y – x;
else: }
dist = y – x cout << dist << endl;
print(dist)
print(‘SpamSpamSpam’)
print ( ‘SpamSpamSpam’ )
These two lines are identical
VARIABILE_NAME = EXPRESSION
Binding: a logical name is associated with an object (the result of
the expression)
C
python
int a;
a = 1 # int
float b;
b = 1.0 # float
bool c;
c = True # bool
a = 1;
b = 1.0;
c = True;
• Variables still have a type, it’s just not explicitly defined by the programmer
Beware of rounding
Floating points are stored as binary numbers à rounding are possible
It is usually a bad idea to test if two floating numbers are the same
Functions
• Block of codes that perform a specific operation
• They can receive inputs
• They can return an output
• Some input parameters might have default values (keyword parameters)
• When using a function you don’t need to know anything about how it works
• positional arguments:
• the position is important
• some positional argument might be compulsory
• an arbitrary number of positional arguments might follow
• keyword arguments:
• the position is not important
• if a keyword argument is not defined, the default value is used
• there can be an arbitrary number of keyword arguments
type(True) à bool
type(object): return the type(1) à int
type(1.0) à float
type of an object
print(‘Cleese’) Cleese
print(‘Gilliam’) Gilliam
it means that the cell is being executed. In this case the execution
ends when typing the new line character
help()
• For any object it returns the help string
help(print)
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
x=1
Try:
help(x)
help(object) Return the help string for the object
complex(real [, imag]) Return a complex number with real and imag part comples(1,2) à 1+2j
isinstance(object, type) Return True is the object is of class type isinstance(1,int) à True
type(object) Return the type of the object type(1) à int
bool(), int(), float(), complex(), str()
• Convert input to an object of the corresponding type
bool(1) à True
bool(0) à False
bool(‘any not empty string’) à True
bool(‘’) à False
complex(1) à 1+0j
complex(1,2) à 1+2j
int(‘12’) à 12 complex(‘1+2j’) à 1+2j
int(‘12.1’) à Error
x = x + 1 à x += 1
x = x – 2 à x -= 2
x = x * 3 à x *= 3
x = x / 4 à x /= 4
x = x // 5 à x //= 5
x = x % 7 à x %= 7
x = x ** 4 à x **= 4
“ATCG”*4 “ATCGATCGATCGATCG”
4*“ATCG”
s = ‘Spam’ s = ‘Spam’
s += ‘Spam’ à s = ‘SpamSpam’ s *= 3 à s = ‘SpamSpamSpam’
in Operator
object1 in object2
True if object1 is included in object2
It can be used any time object2 is iterable
“TATA” in “ACGTACGCTATACG” True
1 in 12 ERROR
True False
False True
== Equal 1 == 0 False
“ATC” == “ATC” True
>
>=
Bitwise operators
Operator Example Result
^ xor 2^6 4
~ not
…
if expression:
<Block of code>
…
• Indentation is used to define the block of code that is executed only when the
expression is True
• Be consistent in using tabs/spaces for indentation throughout the code
IF (2/3)
if expression1:
<Block of code 1>
else:
<Block of code 2>
IF (3/3)
if expression_1:
<Block of code 2>
Only the block of code corresponding
elif expression_2: to the first True expression is executed
x if x > 0 else -x
a b c d e f g h
0 1 2 3 4 5 6 7
-8 -7 -6 -5 -4 -3 -2 -1
…
while expression:
<Block of code>
…
i=0
while i < 10:
import math
x = input(‘Provide a positive number: ‘)
l = [1.1, 3.2, -0.1]
for x in l: x = int(x)
if x < 0: if x > 0:
continue print(‘x = ‘,x)
print(‘log(x) = ‘,math.log(x)) break
i += 1
else:
print(‘I surrender !’)
Methods
• Similar to functions but they operate on a specific object
• They can change the object itself
• Positional and keyword parameters work as in function
OBJECT.
s = ‘a usEleSs sTRIng’
s.lower() à ‘a useless string’
S = s.upper() à ‘A USELESS STRING’
S.replace(’STRING’, ‘EXAMPLE’) à A USELESS EXAMPLE
s.lower() Return a string with all the characters converted to lower case
s1 = ‘Wanda’
s2 = s1.lower() à s2 = ‘wanda’
s.upper() As before, but converting to upper case
s.find(substring) Search for substring into s. It returns the position of the first occurence (indexes
start at zero). If substring is not included in s, it return -1.
s1 = ‘SpamSpamSpam’
i = s1.find(‘am’) à i = 2
s.strip(remove_chr) Remove all the characters in remove_chr at the begin/end of s. At default it
removes blank spaces and new lines.
s1 = ‘ ATCG ‘
s2 = s1.strip() à s2 = ‘ATCG’
s.replace(old,new) Return a new string where any occurance of old is replaced by new
dna = ‘ATCGTCG’
rna = dna.replace(‘T’,’U’)
s.join(list) Create a string by merging the elements in seq using s as a separator
Strings are immutable à the methods cannot change the string, they return a new one
format strings
It creates formatted strings by substituting the parts between
curly brackets by the actual values provided to the method
‘This {} weights {} kg’.format(’dog’, 16)
à This dog weights 16 kg
‘This {0} weights {1} kg’.format(’dog’, 16) ‘This {1} weights {0} kg’.format(16, ’dog’)
à This dog weights 16 kg à This dog weights 16 kg
s string
< left-justified {:justificationfield_witdhfield_type}
c character
^ centered
d integer, base 10
> right-justified s = ‘{:3.1f}'.format(1.0)
f floating print(s)
e exponential s = '{:<10s}'.format('dog') s = ‘{:4.2f}'.format(1.0)
notation print(s) print(s)
dog
b binary s = ‘{:^10s}'.format('dog') dog
print(s) dog 1.0
o octal 1.00
s = ‘{:>10s}'.format('dog')
x hexadecimal print(s)
Built-in data structures
L = [‘A’, ‘C’, ‘T’, ‘G’, ’A’, ’T’] [‘A’, ‘C’, ‘T’, ‘G’, ’A’, ’T’]
del L[i] Remove i-th element from the list. It changes the list
itself
del L[i:j] Remove elements for i to j-1
del L[i:j:k] Remove elements from i to j-1 with step k
l = [32, 17, 1, 8, 21, 9]
Lists are mutable à these methods can modify the list itself
l = [32, 17, 1, 8, 21, 9]
l1 = [3,2,4]
l2 = 2*l1 à 3,2,4,3,2,4
l3 = [7,8] + l2 à 7, 8, 3, 2, 4, 3, 2, 4
8 in l3 à True
Common functions for lists
L = [0,1,2,3]
len(L) 4 Number of elements
min(L) 0
max(L) 3
sum(L) 6
any(L) True True if any element is True
all(L) False True is all the elements are True
l2 = [4,5,6]
l3 = [l1, l2] from copy import deepcopy
l4 = l3.copy()
l4 is l3 à False l1 = [1,2,3]
l2 = [4,5,6]
l4[0] is l3[0] à True l3 = [l1, l2]
l4 = deepcopy(l3)
l4 is l3 à False
l4[0] is l3[0] à False
Aliasing and binding operator
l1 = [1,2,3]
The l1 defined in the last line points to a new
l2 = l1 object, so l2 still points to [1,2,3]
l1 = l1 + [4,5,6]
s = ‘dog’ l = list(‘dog’)
s[0] = ‘f’ à ERROR l[0] = ‘f’ à now it works
tuples
• Similar to list but immutable
l1 = [1,2,3]
l1 = [1,2,3] t1 = tuple(l1) # it works with any iterable
l2 = [4,5,6]
t1 = l1, l2
elements can be mutable objects
SET
S = {‘A’, ‘C’, ‘T’, ‘G’, ’A’, ’T’} {‘A’, ‘C’, ‘T’, ‘G’}
Operator Method
D[k] = v Add the k:v pair (or redefine it, if already there)
l = [n**2 for n in range(10)] à [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[x for x in l if (x % 3) == 0] à [0,9,36,81]
def Function(parp1, parp2,…, parpN, park1 = X1, , park2 = X2, …, parkM = XM):
function(0) à ERROR
function(0,1) à p1 = 0 , p2 = 1 , k1 = 2 , k2 = 3
function(0,1, k1 = 4) à p1 = 0 , p2 = 1 , k1 = 4 , k2 = 3
function(0,1, k2 = 5) à 1 = 0 , p2 = 1 , k1 = 2 , k2 = 5
function(0,1, k1 = 4, k2 = 5) à p1 = 0 , p2 = 1 , k1 = 4 , k2 = 5
function(k1 = 4, k2 = 5, 0, 1) à ERROR
function(k1 = 4, k2 = 5, p1 = 0, p2 = 1) à It works, but it’s horrible
Scope and Lifetime of variables
• Variables defined in the main body of the file are global
• Variables defined inside a block are local to that block
• more local variables override less local variable
def function():
def function(): 2
a=2
print(a) 1 1
print(a)
a=1
a=1
function()
function()
print(a)
a inside the
function is not the
same a as the one
outside
global
• The keyword global is needed when we want to change a global
variable inside a function
Beware: it’s not possible to refer both to a local and a
global variable with the same name
def function(): These programs raise an exception UnboundLocalError
global a 2
a=2 2
print(a) def function(): def function():
a=1 print(a) a=a+1
function() a=2 a=1
print(a) print(a) function()
a=1
Now, we’re saying that the variable function()
a inside the function is the same
variable a defined outside
*
Number of arguments: 1
function(1)
Arguments: (1,)
Number of arguments: 2
function(1,2)
Arguments: (1,2)
Number of arguments: 3
function(1,2,3)
Arguments: (1,2,3)
This syntax can be used to pass an arbitrary number of positional arguments to a function
It is convention to call the sequence of arbitrary positional arguments args (but anything would
work, it’s the * making the trick)
def welcome(message,*args):
print(message)
for other_message in args:
print(other_message)
Hello !
welcome(‘Hello !’)
Hello !
welcome(‘Hello !’, ’Ciao !’, ‘Hola !’)
Ciao !
Hola !
**: expand a dictionary in its key = value components
def function(**kwargs):
print('Number of keyword arguments: ',len(kwargs))
print('Arguments: ‘,kwargs)
Number of arguments: 1
function(k1 = 1)
Arguments: {‘k1’:1}
Number of arguments: 2
function(k1 = 1, k2 = 2)
Arguments: {‘k1’:1, ‘k2’:2}
This syntax can be used to pass an arbitrary number of keyword arguments to a function
It is convention to call the sequence of arbitrary keyword arguments kwargs (but anything would
work, it’s the ** making the trick)
def welcome(message,**kwargs):
print(message)
for language, greeting in kwargs.items():
print(language,’: ‘,greeting)
l.sort() à [['bruno', 74, 164], ['giorgio', 23, 187], ['mario', 54, 173], ['vittorio', 12, 145]]
l.sort(key = age) à [['vittorio', 12, 145], ['giorgio', 23, 187], ['mario', 54, 173], ['bruno', 74, 164]]
l.sort(key = height) à [['vittorio', 12, 145], ['bruno', 74, 164], ['mario', 54, 173], ['giorgio', 23, 187]]
Anonymous lambda functions (functions on one-
line)
l.sort() à [['bruno', 74, 164], ['giorgio', 23, 187], ['mario', 54, 173], ['vittorio', 12, 145]]
l.sort(key = lambda x: x[1]) à [['vittorio', 12, 145], ['giorgio', 23, 187], ['mario', 54, 173], ['bruno', 74, 164]]
l.sort(key = lambda x: x[2]) à [['vittorio', 12, 145], ['bruno', 74, 164], ['mario', 54, 173], ['giorgio', 23, 187]]
File Objects
file_object = open(Name_of_the_file, mode)
• Name_of_the_file is a string with the file name (full path, or with
respect to the current path)
• mode is a string that defines the kind of the file and how you want
to open the file
r Read from the beginning Error if the file does not exist t text
a Write from the end if the file does not exist, it is created
f = open(‘file.txt’, ‘r’) x
file.txt x = f.read(1) “L”
ValueError Raised when a function gets argument of correct type but improper value
TypeError
Raised when a function or operation is applied to an object of incorrect type
try:
<Block of code>
except Exception1:
<Block of code to execute if Exception1 occurred>
else:
<Block executed if no exception occurred>
finally:
<Block executed in any case>
Raising exceptions
• sometimes it is useful to raise a particular exception in your
own code (so that another part of the program can handle it)
• Exceptions are raised by the raise function
def convert_kelvin_to_fahrenheit(temperature):
if (not isinstance(temperature, float)) and (not isinstance(temperature, int)):
raise TypeError('temperature is a number')
if temperature < 0:
raise ValueError('temperature in kelvin is >= 0')
return (temperature - 273.15) * 9/5 + 32
Import
import MODULE
import the entire module, objects of the module are accessible with the syntax
MODULE.object
• class = an abstract model for a group of entities (persons, vehicles, random number
generators, …) à The data type
• object = an entity of a particular class à The variable
• attribute = a characteristic of the object (random number generator: mean, std, … )
• method = function offered to the outside world (random number generator: generate new
sample)
• inheritance = relationship among classes (generators of random numbers with
gaussian/uniform/… distributions are a special kind of random number generators)
class Point:
“””A point in a 2D-dimensional space”””
def __init__(self, x = 0.0, y = 0.0):
self.x = x
self.x = y
class Point:
“””A point in a 2D-dimensional space”””
def __init__(self, x = 0.0, y = 0.0): self is the first
self.x = x argument of all
self.y = y the methods
def __repr__(self):
return 'x = ‘+str(self.x)+’ y = ‘+str(self.y)
p1 = Point(2.3,6.1)
print(p1) à x = 2.3 y = 6.1
• Add to the class Point a method for calculating the distance from the
origin
class Point:
“””A point in a 2D-dimensional space”””
def __init__(self, x = 0.0, y = 0.0): All the rules discussed for function
self.x = x definition apply to method definition
self.y = y
def __repr__(self):
return 'x = ‘+str(self.x)+’ y = ‘+str(self.y)
def norm(self):
return (self.x**2 + self.y**2)**0.5
p1 = Point(2.3,6.1)
print(‘distance of p1 from origin = ‘,p1.norm())
• Add a method that returns a new point rotated by teta degrees
class Point:
def __init__(self, x = 0.0, y = 0.0):
self.x = x
self.y = y
def __repr__(self):
return 'x = ‘+str(self.x)+’ y = ‘+str(self.y)
def norm(self):
return (self.x**2 + self.y**2)**0.5
def rotate(self, teta):
import math
x = math.cos(teta)*self.x - math.sin(teta)*self.y
y = math.sin(teta)*self.x + math.cos(teta)*self.y
return Point(x,y)
p1 = Point(2.3,6.1)
p2 = p1.rotate(3.14)
• Change the method rotate so that it changes the object itself, instead
of returning a new object
class Point:
def __init__(self, x = 0.0, y = 0.0):
self.x = x
self.y = y
def __repr__(self):
return 'x = ‘+str(self.x)+’ y = ‘+str(self.y)
def norm(self):
return (self.x**2 + self.y**2)**0.5
def rotate(self, teta):
import math
x = math.cos(teta)*self.x - math.sin(teta)*self.y
y = math.sin(teta)*self.x + math.cos(teta)*self.y
self.x = x
self.y = y
Remember aliasing
p2 = p1
print('p1: ', p1) à p1: x=1y=2
print('p2: ', p2) à p2: x=1y=2 So here, we’re changing the object pointed
both by p1 and p2
p1.x = 3
print('p1: ', p1) à p1: x=3y=2
print('p2: ', p2) à p2: x=3y=2
Operator overloading
• It is possible to define how operators (+,-,==,…) work on object
of your own classes
+ __add__ unary operators
< __lt__ += __iadd__
+ __pos__
<= __le__ - __sub__
- __neg__
-= __isub__
> __gt__ abs() __abs__
* __mul__
>= __ge__ *= __imul__ int() __int__
== __eq__ / __truediv__ float() __float__
!= __ne__ /= __idiv__ round() __round__
// __floordiv__ bool()* __bool__
//= __ifloordiv
For binary operators, it is convention % __mod__
to call the second operand in the %= __imod__ * This is also used when checking if
method definition other an object is True
** __pow__
**= __ipow__
• Define the > operator of the class Point, so that p1 > p2 returns True if p1 is
further away from the origin than p2
class Point:
def __init__(self, x, y): Now sort knows what to do !
self.x = x import random
self.y = y points = [Point(random.uniform(0,1),
random.uniform(0,1)) for i in range(10)]
def __repr__(self):
points.sort()
return 'x = ‘+str(self.x)+’ y = ‘+str(self.y)
def norm(self):
return (self.x**2 + self.y**2)**0.5
def __gt__(self, other):
return self.norm() > other.norm()
• Define the == operator to check if two objects of the class Point are the same
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Polygon:
def __init__(self, *args):
self.vertexes = []
for vertex in args:
if not isinstance(vertex, Point):
raise TypeError()
self.vertexes.append(vertex)
class New(Parent): With this syntax, the class New inherits from the class Parent
class New: Actually when the parent class is not defined, it is assumed equal to the
class object, where object is a completely generic python class
class New(object):
• Define the classes Triangle and Rectangle as classes inheriting the
class Polygon
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
class Polygon(object):
def __init__(self, *args):
self.v = []
for point in args:
self.v.append(point)
def __getitem__(self, ind):
return self.v[ind]
class Triangle(Polygon): What if I want to check that the number of
def area(self): vertexes is 3 when a triangle is defined ?
return 0.5*self.v[2].y*(self.v[1].x - self.v[0].x)
class Rectangle(Polygon):
def area(self):
return (self.v[1].x - self.v[0].x) * (self.v[2].y - self.v[0].y)
class Point(object): Here, part of the code is repeated between the
def __init__(self, x, y): __init__ method of Polygon and the __init__
self.x = x method of Triangle
self.y = y à Difficult to maintain the code
class Polygon(object):
def __init__(self, *args): But it is possible to call a method of one class
self.v = [] from any other class
for point in args:
class Triangle(Polygon):
self.v.append(point)
def __init__(self, *args):
def __getitem__(self, ind):
if len(args) != 3:
return self.v[ind]
raise ValueError()
class Triangle(Polygon):
Polygon.__init__(self, *args)
def __init__(self, *args):
if len(args) != 3:
The built-in function super() returns the parent
raise ValueError()
class
self.v = []
for point in args: class Triangle(Polygon):
self.v.append(point) def __init__(self, *args):
def area(self): if len(args) != 3:
return 0.5*self.v[2].y*(self.v[1].x - self.v[0].x) raise ValueError()
class Rectangle(Polygon): super().__init__(*args)
def area(self):
With this syntax, self does not need to be
return (self.v[1].x - self.v[0].x) * (self.v[2].y - self.v[0].y) passed as argument
enumerate(iter)
• It creates an iterator that provides the sequence of pairs
(index, value) for all the elements in object iter
l = [7,3,9] i = 0, x = 7
for i, x in enumerate(l): i = 1, x = 3
print(‘i = ‘,i,’, x = ’,x) i = 2, x = 9
range(start, stop, step)
Sequence of ordered integer numbers