Come si e' visto in precedenza, l'interprete funziona come un semplice calcolatore, si digita un'espressione + invio e si ottiene il risultato.
La sintassi che utilizzeremo in principio e' immediata: sono disponibili i principali operatori matematici e logici presenti negli altri linguaggi e si possono utilizzare le parentesi per raggruppare le espressioni.
2+2
(50-5*7)/4
Si inizializzano con numeri, NON SEGUITI DA PUNTO
a = 2
b = 1+1 #come risultato di un'operazione
c=2.0 #e' un numero reale
print(type(1))
print(type(b))
print(type(c))#!!
a=2**1000 # l'operatore ** rappresenta l'elevazione a potenza
#print(a)
mi chiedo quante cifre abbia 2 alla millesima potenza ....
sa=str(a) #trasformo a in una stringa
print('a ha',len(sa),'cifre') #la funzione print accetta come argomenti, stringhe,
#numeri interi e altri tipi predefiniti
gli operatori: addizione, sottrazione,moltiplicazione e potenza si comportano come e' prevedibile ...
1+2 #addizione
6-10 #sottrazione
5*4 #moltiplicazione
2**3 #potenza
print(13%9) #modulo: restituisce il resto della divisione
print(type(13%9))
print(10/4 , type(10/4))#divisione con resto
print(10//4 , type(10//4))#divisione senza resto
Riassumendo sulla divisione in Python 3:
Oltre alla classica notazione in base 10 e' possibile definire un numero intero usando anche:
a=0b10 #notazione binaria
print(a,type(a))
a=0o10 #notazione ottale
print(a)
a=0xF #notazione esadecimale
b=0x10
print(a)
print(b)
Oltre alla possibilita' di definire un intero usando base binaria, ottale ed esadecimale e' possibile ottenere la rappresentazione in una diversa base usando le funzioni builtin bin, oct ed hex che restituiscono delle stringhe rispettivamente con la rappresentazione binaria, ottale ed esadecimale di un intero
Gli esempi sotto usano anche la funzione int che restituisce un intero a partire da una stringa o piu' in generale da un altro oggetto di tipo compatibile.
Rappresentazione da decimale a binaria, ottale, esadecimale:
print('rappresentazione di 35:')
print('base 2:', bin(35))
print('base 8:',oct(35))
print('base 16:',hex(35))
da binario:
print('rappresentazione di 0b100011:')
print('base 10:',int('0b100011',base=2))
print('base 8:',oct(0b100011))
print('base 16:',hex(0b100011))
da esadecimale:
print('rappresentazione di 0x23:')
print('base 10:',int('0x23',base=16))
print('base 8:',oct(0x23))
print('base 2:',bin(0x23))
esistono anche operatori che agiscono sui singoli bit.
Per comprenderne il significato e' opportuno lavorare con la rappresentazione binaria:
definisco due interi i e j
i = 0b11010011
j = 0b11101100
print('i=',i,bin(i))
print('j=',j,bin(j))
l'operatore and bit a bit si indica col carattere &
#and
iandj = bin(i&j)
print('Operazioni bit a bit sui numeri',i,' e ',j,':')
print(bin(i),' &')
print(bin(j),' =')
print('-'*13)
print(iandj)
print(i&j)
#or
iorj = bin(i|j)
print(bin(i),' |')
print(bin(j),' =')
print('-'*13)
print(iorj)
print(i|j)
#xor
ixorj = bin(i^j)
print(bin(i),' ^')
print(bin(j),' =')
print('-'*13)
print(ixorj)
#inversione dei bit (complemento a 1)
tildei=~i
print('~',bin(i),' =')
print('-'*15)
print('',bin(tildei), '(rappresentazione con segno di un numero binario in complemento a due)')
Per verificare l'effetto dell'operazione di complemento a 1 uso il modulo non standard bitstring
import bitstring
print('~',bin(i),' =')
print('-'*15)
print(' ',bitstring.BitArray(int=tildei,length = len(bin(tildei))-2).bin,
'(stringa di bit, bit di segno compreso)')
#shift a dx
idx = i>>3
#shift a sx
isx = i<<2
print()
print()
print(bin(i),' >> 3 =')
print('-'*18)
print(bin(idx))
print()
print()
print(bin(i),' << 2 =')
print('-'*18)
print(bin(isx))
print()
a=2.1
print(type(a))
Si puo' utilizzare anche la notazione esponenziale (dove e sta per 10**):
a=2.34e6
b=2.34*10**6
c=5e-8
print(a)
print(b)
print(a==b)
#queste informazioni sono accessibili grazie al modulo sys
#ed alla struttura float_info in particolare
import sys
print(sys.float_info)
se volete avere informazioni aggiuntive ...
#help(sys.float_info)
a = float('inf')
type(a)
a = 1.2e600
a
a+1
1/a
a/a
b = float('nan')
type(b)
b = 1./b
b
b+1
il modulo math mette a disposizione delle funzioni per riconoscere nan e inf
import math
a=1e700
math.isinf(a)
math.isnan(a/a)
a = 2.0
b = 7.3
#definisco le operazioni come stringhe
c = 'a+b' #addizione
d = 'a-b' #sottrazione
e = 'a*b' #moltiplicazione
f = 'a/b' #divisione
g = 'a//b'#divisione intera
h = 'a**b'#elevazione a potenza
i = 'pow(a,b)'#elevazione a potenza con funzione pow
l = '-a' #negazione
m = 'abs(-a)' #valore assoluto con funzione abs
n = 'b%a' #resto
#uso la funzione eval per 'stampare' i risultati delle operazioni definite come stringhe:
for esp in(c,d,e,f,g,h,i,l,m,n):
print(esp , '=' ,eval(esp))
Ecco una tabella riassuntiva dei vari operatori:
import math
a='math.pi'
b='math.e'
c='math.sin(math.pi/2.)'
d='math.log(math.e)'
e='math.log10(10.**3.45)'
for esp in(a,b,c,d,e):
print(esp , '=' ,eval(esp))
E' necessario tenere conto che i numeri float in Pytyon (e pi ù in generale in un calcolatore) sono rappresentati in base 2 (binary64).
Ad esempio il numero decimale 0.125 puo' essere rappresentato dalla seguente somma di frazioni:
$$0.125 = \frac{1}{10}+\frac{2}{100}+\frac{5}{1000}$$
dove i denominatori sono potenze progressive del 10.
Lo stesso numero puo' essere rappresentato anche in termini di somma di frazioni il cui denominatore sia una potenza del due: $$\frac{0}{2}+\frac{0}{4}+\frac{1}{8}$$ Di conseguenza la rappresentazione binaria del numero decimale 0.125 e': $$0.001$$
Sfortunatamente non tutte le frazioni decimali possono essere rappresentate esattamente in termini di frazioni binarie e di conseguenza quando vogliamo rappresentare un numero decimale con un float il numero binario floating point che viene memorizzato nella macchina e' una approssimazione del numero decimale di partenza:
a=0.1
print(a)
anche se Python mostra come output 0.1 cio' che e' memorizzato non e' 1/10!
A partire da Python 2.7 e 3.1
l'interprete mostra la pi ù breve rappresentazione
decimale che e' rappresentata dal numero binario memorizzato
nelle versioni precedenti veniva utilizzata una
rappresentazione con 17 cifre decimali (vedi figura sotto che illustra cosa succedeva con python 2.6)
a=0.1
#in realta' cio' che e' memorizzato e' diverso
#da 1/10, per verificarlo basta forzare
#la rappresentazione di un sufficiente
#numero di cifre decimali
print("%.25f"%a)
#print("{0:.25f}".format(a))
Quindi e' necessario ricordarsi che, sebbene normalmente Python visualizzi 0.1 in realta' cio' che e' memorizzato e' il pi ù vicino numero rappresentabile come frazione binaria.
Questo si vede bene nel seguente esempio nel quale si istanziano 3 numeri che hanno la stessa rappresentazione binaria.
a=0.1
b=0.10000000000000001
c=0.1000000000000000055511151231257827021181583404541015625
print('-'*10)
print(a)
print(b)
print(c)
print('-'*10)
print(a==b)
print(a==c)
print(b==c)
E' da notare quanto detto riguarda la 'natura' della rappresentazione binaria floating point e che NON SI TRATTA DI UN BACO DI Python.
Cio' puo' essere sperimentato in tutti i linguaggi che supportano l'aritmetica floating point (sebbene in alcuni di questi sia relativamente pi ù difficile evidenziare le differenze di rappresentazione binaria e decimale).
In relazione a quanto detto e' possibile capire perche' sia molto 'pericoloso' basarsi su test di uguaglianza tra float:
0.1+0.1+0.1==0.3
Il modulo decimal "provides support for decimal floating point arithmetic."
In particolare il modulo decimal definisce un tipo Decimal con le seguenti caratteristiche:
se creo un decimal da un float ottengo una copia del float in questione
import decimal
a=decimal.Decimal(0.1)
print(a)
a+a+a
Se invece passo una stringa
b=decimal.Decimal('0.1')
b
#segno(0=+),cifre,esponente
c=decimal.Decimal((0, (0, 1), -1))
c
somma di float
0.1 + 0.1 + 0.1 - 0.3
somma di decimal
a=decimal.Decimal('0.1')
b=decimal.Decimal('0.3')
a+a+a-b
ricordiamoci comunque che si tratta di numeri decimali con precisione finita:
#modifico la precisione
decimal.getcontext().prec=4
A=decimal.Decimal(1)/decimal.Decimal(3)
print(A)
#modifico la precisione
decimal.getcontext().prec=10
A=decimal.Decimal(1)/decimal.Decimal(3)
print(A)
in ogni caso:
A+A+A
A+A+A == decimal.Decimal('1.0')
per approfondimenti su decimal vedi qui
anche i numeri razionali hanno il loro modulo standard
import fractions
F=fractions.Fraction(1,3)
print(F)
e quindi:
b=F+F+F
print(b)
Tra i tipi standard di Python ci sono anche i numeri complessi.
Un numero complesso con parte reale r e parte immaginaria i puo' essere creato con la notazione
la parte immaginaria e' scritta con il suffisso j o J
oppure con la funzione
#inizializzazione di un numero complesso
a = 1.0 + 1.5j # suffisso minuscolo
b = 2.0 + 2.5J # suffisso maiuscolo
c = 3.5J + 3.0 # prima la parte immaginaria
d = complex(4.0,4.5) # funzione 'costruttore' con argomenti passati per posizione
e = complex(imag=5.5,real=5.0) # funzione 'costruttore' con argomenti passati per nome
print(type(c))
print(a,b,c,d,e)
a = 1.0 + 1.5j
b = 2.0 + 2.5J
#provare a 'giocare' con i vari operatori matematici
e1 = 'a+b'
e2 = 'a-b'
e3 = 'a*b'
e4 = 'a/b'
e5 = 'a**b'
#valuto le espressioni ei con i che va da 1 a 5
#mediante la funzione eval
#per capire questo costrutto tenere presente che:
# - ei e' il nome di una stringa
# - eval(ei) e' una stringa che rappresenta una espressione
# - eval(eval(ei)) e' il risultato dell'espressione descritta dalla stringa eval(ei)
for i in range(1,6):
esp = 'e%d'%i
print(eval(esp),'=',eval(eval(esp)))
a = 1.0 + 1.5j
#senza parentesi
a.real
a.imag
a.conjugate()
per determinare il valore assoluto di un numero complesso usare la funzione builtin abs
a = 1. - 1.j
abs(a)
il modulo cmath mette a disposizione le funzioni del modulo math per i numeri complessi. Una tra queste e' la funzione phase che restituisce la fase in radianti
import cmath
a = 1. - 1.j
cmath.phase(a)*180./cmath.pi
import math
import cmath
#nota: cmath.sqrt restituisce un numero complesso
# cmath.rect si aspetta come argomenti due numeri reali
a = cmath.rect(math.sqrt(2),-45*cmath.pi/180.)
a
restituisce modulo e fase in radianti
cmath.polar(a)
cmath.sin(a)
(e**(a*1j)-e**(a*-1j))/(2j)
cmath.cos(a)
(e**(a*1j)+e**(a*-1j))/(2)
le funzioni definite in cmath restituiscono comunque un numero complesso (anche con argomento reale e positivo):
import cmath
cmath.sqrt(3.)
D'altra parte se uso math e faccio la radice di un numero negativo ...
import math
#math.sqrt(-3)
il modulo numpy.lib.scimath (disponibile per chi ha installato il modulo numpy) definisce delle funzioni 'universali' che trattano in modo unificato numeri reali e complessi
import numpy.lib.scimath
numpy.lib.scimath.sqrt(3)
numpy.lib.scimath.sqrt(-3)
print(dir(numpy.lib.scimath))
e=False
type(e)
True + True
10*False
a=True
b=False
a or b
a and b
not a
#l'operatore xor non esiste
#in Pyton ma puo' essere facilmente
#implementato con l'operadore disuguaglianza !=
#che vedremo anche nel seguito
a != b #equivalente a (a xor b)
Gli operatori logici possono essere concatenati ma attenzione alle precedenze:
a = True
b = False
c = False
a or b and c
# and viene calcolato prima di or
# a or (b and c)
#se vogliamo che sia eseguito
(a or b) and c
2 < 2
2 <= 2
2*3 == 6
2. * 3.2 == 6.4
0.1 * 3. == 0.3 # questo dovrebbe ricordarvi qualcosa !!!
anche gli operatori di comparazione possono essere concatenati:
dati
a = 1
b = 2
c = 3
d = 1
e' possibile scrivere:
a < b < c
che e' equivalente a:
a < b and b < c
con la differenza che nella prima forma b viene 'valutato' una volta sola
Un altro esempio di concatenamento:
a < b < c > d
Quindi anche la precedente e' una espressione valida e non implica alcuna comparazione tra
a e d
o tra
b e d
#conversione int -> float
float(1)
#conversione float -> int
int(1.2)
#conversione int - > complex
complex(1)
#conversione int -> bool
bool(0)
#conversione int -> bool
bool(1) == (bool(1) == bool(33))
Ci sono anche conversioni non permesse, ad esempio se provo a convertire un complex in un float Python mi segnala un TypeError:
#float(6.2+5.1j)
Vediamo come fare a definire una funzione senza approfondire.
La sintassi e' molto semplice:
Come si vede il corpo della funzione è indentato ad un livello diverso rispetto alla sua definizione.
Pi ù in generale l'indentazione definisce il livello del codice.
Definisco due funzioni che restituiscono il quadrato ed il cubo di un numero:
def quadrato(val):
print('Nella funzione quadrato('+str(val)+')')
return val**2
#sono tornato al livello di indentazione del 'def'
#il blocco di codice che definisce la funzione e' terminato
#definisco una nuova funzione
def cubo(val):
print('Nella funzione cubo('+str(val)+')')
return val**3
print(quadrato(10))
print(cubo(10.))
Se passo una stringa alla funzione ...
#quadrato('pippo')
Posso definire funzioni che accettano pi ù parametri
def retta(x,a,b):
"""
Questo e' un esempio di "docstring"
ordinata della retta con pendenza a, intercetta b per ascissa pari a x.
Parameters:
===========
x: (number) ascissa
a: (number) pendenza
b: (number) intercetta
Returns:
========
a*x + b
Examples:
=========
vretta = [retta(x,10.,20.) for x in range(10)]
"""
return a*x+b
print(retta(2.54,10.,20.))
Questo lo vedremo nella prossima lezione:
print([retta(x,10.,20.) for x in range(10)])
La sintassi della proposizione if e' molto semplice:
vediamo un esempio:
#generazione di un numero casuale
import random
x=100.*random.random()
if x <= 33.0:
print('piccino: ',x)
elif x<=66.:
print('medio: ',x)
else:
print('grande :',x)
Nell'uso interattivo con IDLE si deve fare attenzione all'indentazione: