martedì 14 ottobre 2025

Corso di Python: 2 – Variabili e Tipi di Dati

2 — Variabili e Tipi di Dati

Obiettivi del modulo

Al termine di questo modulo (3 ore) lo studente sarà in grado di:

  • creare e usare variabili in Python rispettandone le regole di naming e scope;

  • riconoscere i tipi base (int, float, str, bool) e gestirne le proprietà;

  • convertire valori tra tipi in modo sicuro e consapevole;

  • leggere input da tastiera in modo robusto;

  • progettare e implementare una calcolatrice semplice (con attenzione alla sicurezza e alla precisione numerica).


1 — Variabili: cosa sono e regole di naming (20 min)

Concetto

Una variabile è un nome simbolico che fa riferimento a un oggetto in memoria. In Python il binding è dinamico e il linguaggio è dynamically typed (il tipo è associato all’oggetto, non al nome).

Esempio:

x = 42 # x è legato all'oggetto intero 42
x = "ciao" # ora x è legato alla stringa "ciao"

Regole di naming (buone pratiche)

  • I nomi validi: lettere (a–z, A–Z), cifre (0–9) e underscore _. Non possono iniziare con una cifra.
    Validi: count, _temp, total_3. Invalidi: 3items, my-var.

  • Case-sensitive: Value, value sono nomi distinti.

  • Evitare parole chiave riservate (if, for, while, def, class, lambda, None, True, False, ecc.). Per la lista completa: vedere la documentazione Python keyword.kwlist.

  • Convenzione PEP8:

    • variabili e funzioni: snake_case (es. total_amount)

    • costanti (convenzionali): UPPER_SNAKE_CASE (es. MAX_RETRIES)

    • classi: CamelCase (es. MyClass)

  • Usare nomi significativi e non puntare a abbreviazioni criptiche.

Scope (ambito)

  • local (funzioni), global (modulo), builtins.

  • Per modificare una variabile globale dentro una funzione usare global, per variabili del "nonlocal" scope (closure) usare nonlocal. Evitare uso eccessivo di global.

Esempio:

x = 10
def f():
x = 5 # nuova variabile locale
def g():
global x
x = 7 # modifica la x globale

2 — Tipi base: numeri e booleani (30 min)

Interi (int)

  • Precisione arbitraria (non overflow in Python 3).

  • Operatori: + - * // % ** (divisione intera //, modulo %, potenza **).

Esempio:

a = 7
b = 3
q = a // b # 2
r = a % b # 1

Virgola mobile (float)

  • Rappresentazione in virgola mobile IEEE-754 (double precision). Attenzione alle proprietà numeriche (errori di rappresentazione).

  • 1/3 produce 0.3333333333333333 approssimato.

Problema classico: 0.1 + 0.2 != 0.3 in float.

  • Motivazione (sintesi): alcuni decimali non sono rappresentabili esattamente in base 2; la somma dà 0.30000000000000004.

  • Soluzione per applicazioni dove serve precisione decimale: usare il modulo decimal.Decimal.

Esempio con Decimal:

from decimal import Decimal, getcontext
getcontext().prec = 28
Decimal('0.1') + Decimal('0.2') == Decimal('0.3') # True

Numeri complessi (complex)

  • Sintassi: 3+4j. Utile per elaborazione segnali, matematica, alcuni algoritmi scientifici.

Booleani (bool)

  • Valori: True, False. Sottotipo di int (True == 1, False == 0).

  • Valori truthy / falsy:

    • Falsy: 0, 0.0, '' (stringa vuota), [], {}, None, False

    • Tutto il resto è truthy.

Esempio:

if items: # True se items non è vuoto
print("Ci sono elementi")

3 — Stringhe (30 min)

Creazione e tipi

  • s = "ciao" o s = 'ciao'. Multilinea con triple quote """...""".

  • Stringhe sono immutabili: ogni modifica crea un nuovo oggetto.

Operazioni comuni

  • Concatenazione: "a" + "b"

  • Ripetizione: "ha" * 3"hahaha"

  • Indicizzazione: s[0], slicing: s[1:4]

  • Metodi utili: .strip(), .lower(), .upper(), .replace(), .split(), .join()

  • F-strings (Python 3.6+): f"Valore: {x:.2f}" per formattazione leggibile.

Esempio:

name = "Maria"
greeting = f"Benvenuta, {name.upper()}!"

Raw strings e encoding

  • Raw string per regex o percorsi: r"\n" non interpreta il backslash.

  • Bytes: b'abc', conversione con .encode() / .decode().


4 — Conversioni tra tipi e problemi pratici (30 min)

Conversioni esplicite

  • int(x), float(x), str(x), bool(x), complex(x).

  • Usare Decimal per conversioni da stringhe precise: Decimal('0.1').

Esempi:

int("42") # 42
float("3.14") # 3.14
str(10) # "10"
bool(0) # False

Conversioni fallibili e gestione eccezioni

Conversioni da input utente possono fallire — usare try/except.

s = input("Inserisci un numero: ")
try:
v = int(s)
except ValueError:
print("Valore non valido")

Implicite e coercizioni

  • In espressioni int + floatint coerito a float.

  • Attenzione a == vs is: is verifica identità oggetto (non usare per confronto di valori numerici o stringhe).

Problemi di virgola mobile (ripreso)

  • Per confronti di float usare tolleranze (math.isclose()).
    Esempio:

import math
math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-9)

Esempio avanzato: parsing locale per decimali con virgola

Utenti italiani spesso usano la virgola come separatore decimale. Convertire:

def safe_parse_float(s):
s = s.strip().replace(',', '.')
return float(s)

5 — Input da tastiera (20 min)

input() (Python 3)

  • Restituisce sempre una str.

  • Necessario convertire al tipo desiderato.

Esempio robusto:

def ask_int(prompt):
while True:
s = input(prompt)
try:
return int(s)
except ValueError:
print("Inserisci un intero valido.")

Validazione e UX minima

  • Controllare range, formato, condizioni.

  • Fornire messaggi chiari e possibilità di uscita (q per quit).

Esempio con decimali e virgola:

def ask_decimal(prompt):
while True:
s = input(prompt).strip()
if s.lower() in ('q','quit'):
return None
s = s.replace(',', '.')
try:
from decimal import Decimal
return Decimal(s)
except Exception:
print("Numero non valido. Usa 12.34 o 12,34.")

6 — Esercizio principale: Calcolatrice semplice (30 min)

Presenteremo tre versioni incrementali:

Versione A — Calcolatrice menu-driven (sicura, senza eval)

  • Operazioni: addizione, sottrazione, moltiplicazione, divisione; input numero per numero; usa Decimal per precisione.

# calc_simple.py
from decimal import Decimal, InvalidOperation, getcontext
getcontext().prec = 28
def read_decimal(prompt):
while True:
s = input(prompt).strip().replace(',', '.')
try:
return Decimal(s)
except InvalidOperation:
print("Numero non valido. Riprova.")
def menu():
print("Calcolatrice semplice")
print("1) Addizione")
print("2) Sottrazione")
print("3) Moltiplicazione")
print("4) Divisione")
print("q) Esci")
def main():
while True:
menu()
choice = input("Scegli operazione: ").strip().lower()
if choice == 'q':
break
if choice not in ('1','2','3','4'):
print("Scelta non valida.")
continue
a = read_decimal("Primo numero: ")
b = read_decimal("Secondo numero: ")
if choice == '1':
res = a + b
elif choice == '2':
res = a - b
elif choice == '3':
res = a * b
elif choice == '4':
if b == 0:
print("Errore: divisione per zero.")
continue
res = a / b
print("Risultato:", +res) # +res applica il contesto Decimal
print("Arrivederci.")
if __name__ == '__main__':
main()

Note: uso di Decimal evita errori di floating-point e di rappresentazione; +res fornisce il valore normalizzato secondo il contesto.


Versione B — Evaluator sicuro di espressioni (supporta parentesi, operatori)

Non usare eval() su input non fidati. Usare ast per parsing sicuro o la libreria asteval/numexpr. Esempio con ast + operator:

# safe_eval.py
import ast, operator as op
from decimal import Decimal, InvalidOperation, getcontext
getcontext().prec = 28
# mappa nodi AST consentiti a funzioni
operators = {
ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv,
ast.Pow: op.pow, ast.USub: op.neg
}
def eval_expr(expr):
"""
Valuta un'espressione aritmetica in modo sicuro restituendo Decimal.
Supporta operatori + - * / ** e parentesi.
"""
def _eval(node):
if isinstance(node, ast.Num): # Python <3.8
return Decimal(str(node.n))
if hasattr(ast, 'Constant') and isinstance(node, ast.Constant): # py3.8+
return Decimal(str(node.value))
if isinstance(node, ast.BinOp):
left = _eval(node.left)
right = _eval(node.right)
op_type = type(node.op)
if op_type not in operators:
raise TypeError("Operatore non supportato")
return operators[op_type](left, right)
if isinstance(node, ast.UnaryOp):
operand = _eval(node.operand)
op_type = type(node.op)
if op_type not in operators:
raise TypeError("Operatore unario non supportato")
return operators[op_type](operand)
raise TypeError("Nodo non supportato: " + str(type(node)))
node = ast.parse(expr, mode='eval').body
return _eval(node)
# uso
if __name__ == '__main__':
while True:
s = input("Expr (o q per uscire)> ").strip()
if s in ('q','quit'):
break
try:
print(eval_expr(s))
except Exception as e:
print("Errore:", e)

Vantaggi: valutazione di espressioni complesse senza esporre il processo a comandi arbitrari.


Versione C — Calcolatrice REPL estesa con funzioni e test

  • Organizza le funzioni (add, sub, mul, div, evaluate) e scrive test unitari (pytest o unittest).

  • Fornire un pacchetto calc/ con __init__.py, core.py, cli.py, tests/test_core.py.

Esempio test (pytest):

# tests/test_core.py
from calc.core import eval_expr
from decimal import Decimal
def test_add():
assert eval_expr("2+3") == Decimal('5')
def test_div_zero():
import pytest
with pytest.raises(ZeroDivisionError):
eval_expr("1/0")

7 — Esercizi e problemi proposti (con soluzioni scheletrate)

Esercizio 1 — Tipi e conversioni

Scrivi una funzione to_number(s) che prova a convertire la stringa s in int, poi float, poi Decimal, restituendo il valore convertito o lancia ValueError.

Suggerimento soluzione:

from decimal import Decimal, InvalidOperation
def to_number(s):
s = s.strip().replace(',', '.')
try:
return int(s)
except ValueError:
pass
try:
return Decimal(s)
except InvalidOperation:
raise ValueError("Non è un numero")

Esercizio 2 — Parità e filtri

Dato un elenco di stringhe che rappresentano numeri con possibile virgola, filtra solo i numeri pari. Usa Decimal per la conversione.

Esercizio 3 — Calcolatrice estesa

Estendi la Versione A per supportare:

  • potenza ^ (mappandola a **),

  • memoria M+, MR (memorizzazione),

  • history: salva le ultime 10 espressioni e risultati.


8 — Errori comuni e best practices (sintesi)

  • Non usare == per confrontare con None: usare is None.

  • Non confrontare float con uguaglianza esatta: usare math.isclose o Decimal.

  • Evitare eval() su input non controllato: rischio di code injection.

  • Gestire eccezioni sempre quando converti l’input.

  • Preferire funzioni pure e testabili per la logica (separa CLI e core).

  • Usare type hints per chiarezza: def add(a: Decimal, b: Decimal) -> Decimal: e poi mypy per controlli statici.


9 — Tipi avanzati (breve cenno, oltre il modulo)

  • list, tuple, set, dict (container).

  • typing: Optional, Union, List[int], Dict[str, Any].

  • dataclasses per strutture dati leggere (@dataclass).


10 — Valutazione (rubrica proposta)

  • Comprensione concettuale (30%): nomi, scope, tipi, conversioni.

  • Codifica e correttezza (40%): calcolatrice funzionante, gestione errori, test.

  • Qualità del codice (20%): leggibilità, commenti, adherence a PEP8, uso di funzioni.

  • Estensioni e creatività (10%): extra features (history, memoria, safe eval).


11 — Risorse e letture consigliate

  • Documentazione ufficiale Python — The Python Standard Library (input(), decimal, ast).

  • PEP8 — Style Guide for Python Code.

  • Fluent Python (Luciano Ramalho) — per approfondire tipologie avanzate e idiomi Python.

  • Documentazione decimal e math module per gestione numerica.


12 — Esempio completo (Script finale compatto)

Un singolo script calc_decimal_safe.py che combina menu, Decimal e ast safe-eval:

#!/usr/bin/env python3
# calc_decimal_safe.py
import ast, operator as op
from decimal import Decimal, InvalidOperation, getcontext
getcontext().prec = 28
ops = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv,
ast.Pow: op.pow, ast.USub: op.neg}
def eval_expr_decimal(expr):
def _eval(node):
if isinstance(node, ast.Constant): # py3.8+
return Decimal(str(node.value))
if isinstance(node, ast.Num): # older py
return Decimal(str(node.n))
if isinstance(node, ast.BinOp):
l = _eval(node.left); r = _eval(node.right)
t = type(node.op)
if t not in ops:
raise TypeError("Operatore non consentito")
if t is ast.Div and r == 0:
raise ZeroDivisionError("Divisione per zero")
return ops[t](l, r)
if isinstance(node, ast.UnaryOp):
val = _eval(node.operand)
t = type(node.op)
if t not in ops:
raise TypeError("Operatore unario non consentito")
return ops[t](val)
raise TypeError("Espressione non supportata")
node = ast.parse(expr, mode='eval').body
return _eval(node)
def main():
print("Calcolatrice sicura (Decimal). Digita 'q' per uscire.")
while True:
s = input(">>> ").strip()
if s in ('q','quit'):
break
s = s.replace('^', '**').replace(',', '.')
try:
res = eval_expr_decimal(s)
except Exception as e:
print("Errore:", e)
else:
print(res)
if __name__ == '__main__':
main()

Conclusione

Questo modulo fornisce le basi indispensabili per lavorare in Python: naming corretto, tipi base, conversioni robuste, input e validazione. La calcolatrice è un esercizio didattico ideale perché obbliga ad affrontare tutti i temi: parsing, validazione, error handling, precisione numerica e sicurezza.

Nessun commento:

Posta un commento

Corso Fondamenti di Informatica e Reti: 6 Reti di computer e Internet

Reti di computer e Internet Introduzione Prova a pensare alla vita quotidiana senza reti informatiche: niente messaggi WhatsApp, niente m...