Leo vs Python
Stesso compito, due linguaggi
Undici programmi concreti, scritti in Leo e in Python uno accanto all'altro. Per ciascuno diciamo dove Leo è più comodo e, senza giri di parole, dove Python resta la scelta migliore. Ogni esempio Leo qui sotto è stato eseguito davvero: l'uscita che vedi è quella vera.
Leo gira sopra Python, quindi non è una gara fra motori: è lo stesso motore con un volante italiano. La domanda vera non è «chi è più potente», ma «con quale dei due questo lavoro viene più semplice, più chiaro e più sicuro».
Caso 1 di 11
Ciao, mondo
Il primo programma: una riga di saluto a schermo.
print("Ciao, mondo!")
stampa("Ciao, mondo!")
Ciao, mondo!
Dove Leo è più comodo
La parola stampa è italiana e il programma si avvia con python3 -m leo ciao.leo: Python e tutta la libreria sono già dentro Leo, non si installa nulla.
Dove Python resta avanti
print è conosciuto da chiunque e gira ovunque ci sia Python, senza passare da Leo. L'avvio di Python “nudo” è anche più rapido di qualche millisecondo.
Caso 2 di 11
Una classe con ereditarietà
Definire un tipo di oggetto, con campi, metodi e una sottoclasse che specializza il genitore.
class Veicolo:
def __init__(self, nome, ruote=4):
self.nome = nome
self.ruote = ruote
def descrivi(self):
return f"{self.nome}: {self.ruote} ruote"
class Moto(Veicolo):
def __init__(self, nome):
super().__init__(nome, ruote=2)
def descrivi(self):
return super().descrivi() + " (una moto)"
print(Veicolo("Panda").descrivi())
print(Moto("Vespa").descrivi())
classe Veicolo
ha nome
ha ruote = 4
azione descrivi() dà testo
restituisci "{questo.nome}: {questo.ruote} ruote"
fine
fine
classe Moto estende Veicolo
ha ruote = 2
azione descrivi() dà testo
restituisci genitore.descrivi() + " (una moto)"
fine
fine
variabile v = Veicolo("Panda")
variabile m = Moto("Vespa")
stampa(v.descrivi())
stampa(m.descrivi())
Panda: 4 ruote Vespa: 2 ruote (una moto)
Dove Leo è più comodo
I campi si dichiarano con ha, i metodi con azione, questo è l'oggetto corrente e genitore richiama il metodo del genitore. Il costruttore è automatico: Veicolo("Panda") basta e avanza, niente __init__ né self da ripetere.
Dove Python resta avanti
La programmazione a oggetti di Python è più profonda: self esplicito, metodi speciali (__len__, __eq__…), proprietà, classi astratte, ereditarietà multipla e decoratori. Per librerie grandi quella ricchezza conta.
Caso 3 di 11
Fette di testi e liste
Prendere una porzione di una stringa o di una lista con un solo taglio.
parola = "primavera"
print(parola[0:5]) # primi 5 caratteri
print(parola[-3:]) # ultimi 3
numeri = [10, 20, 30, 40, 50, 60]
print(numeri[1:4])
print(numeri[3:6])
variabile parola = "primavera"
stampa(parola[0..4])
stampa(parola[-3..-1])
variabile numeri = [10, 20, 30, 40, 50, 60]
stampa(numeri[1..3])
stampa(numeri[3..5])
prima era [20, 30, 40] [40, 50, 60]
Dove Leo è più comodo
Leo usa l'intervallo da..a con estremi inclusi, come quando diciamo “dal 1º al 5”: parola[0..4] sono i caratteri da 0 a 4, tutti e cinque. Funzionano anche gli indici negativi.
Dove Python resta avanti
Lo slicing di Python ([inizio:fine:passo]) ha il passo: lista[::-1] inverte, lista[::2] prende uno sì e uno no. Leo oggi non ha il passo; per invertire usa inverti.
Caso 4 di 11
Denaro esatto
Sommare dei prezzi e applicare uno sconto senza gli errori dei decimali.
# Con i float normali i centesimi non tornano:
print(0.1 + 0.2) # 0.30000000000000004
from decimal import Decimal
carrello = [Decimal("4.99"), Decimal("0.90"), Decimal("12.50")]
totale = sum(carrello)
print(f"Totale: {totale} EUR")
print(f"Con sconto 20%: {totale * Decimal('0.8')}")
stampa("Con i decimali normali: {0.1 + 0.2}")
variabile carrello = [soldi("4.99"), soldi("0.90"), soldi("12.50")]
variabile totale = soldi("0.00")
per ogni prezzo in carrello fai
totale = totale.piu(prezzo)
fine
stampa("Totale carrello: {totale}")
stampa("Con lo sconto del 20%: {totale.per(0.8)}")
Con i decimali normali: 0.30000000000000004 Totale carrello: 18,39 € Con lo sconto del 20%: 14,71 €
Dove Leo è più comodo
soldi(...) è nel linguaggio di serie, senza import: calcola al centesimo esatto e stampa all'italiana (virgola e simbolo €). In Python per evitare lo 0.30000000000000004 devi ricordarti di from decimal import Decimal.
Dove Python resta avanti
Il modulo decimal di Python è uno standard maturo, con arrotondamenti configurabili, contesti e precisione arbitraria usati in ambito bancario da anni. La base su cui Leo poggia è proprio quella.
Caso 5 di 11
Errori leggibili
Gestire un dato sbagliato e mostrare un messaggio comprensibile.
def eta_da_testo(testo):
return int(testo)
try:
eta = eta_da_testo("trenta")
print(f"Hai {eta} anni")
except ValueError as e:
print("Non è un numero valido.")
print(f"Python dice: {e}")
funzione eta_da_testo(testo)
restituisci intero(testo)
fine
inizio
prova
variabile eta = eta_da_testo("trenta")
stampa("Hai {eta} anni")
cattura ErroreValore come e
stampa("Non è un numero valido.")
stampa("Leo dice: {e.messaggio}")
stampa("Alla riga {e.riga}")
fine
fine
Non è un numero valido. Leo dice: Non posso trasformare il testo "trenta" in un intero. Alla riga 2
Dove Leo è più comodo
Il messaggio è in italiano e discorsivo, e l'errore porta con sé .messaggio, .riga e .colonna: puoi leggerli dal programma. Anche a terminale Leo mostra la riga col segnalino ^^^ e spesso un suggerimento.
Dove Python resta avanti
Il messaggio di Python (invalid literal for int() with base 10: 'trenta') è in inglese, ma la gerarchia delle eccezioni è ampia e documentatissima, e il traceback completo con tutte le chiamate è uno strumento di debug potente.
Caso 6 di 11
Leggere un CSV
Aprire un file CSV della spesa e calcolare il totale.
import csv
with open("spesa.csv", encoding="utf-8") as f:
righe = list(csv.DictReader(f))
totale = 0
for r in righe:
costo = float(r["prezzo"]) * float(r["quantita"])
print(f"{r['prodotto']}: {costo:.2f} EUR")
totale += costo
print(f"Spesa totale: {totale:.2f} EUR")
inizio
variabile righe = leggi_tabella("spesa.csv")
variabile totale = 0
per ogni r in righe fai
variabile costo = decimale(r["prezzo"]) * decimale(r["quantita"])
stampa("{r["prodotto"]}: {costo:migliaia 2} €")
totale += costo
fine
stampa("Spesa totale: {totale:migliaia 2} €")
fine
Mele: 20,00 € Pane: 3,60 € Latte: 8,70 € Spesa totale: 32,30 €
Dove Leo è più comodo
leggi_tabella apre il file, riconosce le intestazioni e restituisce righe già pronte come dizionari, in una riga sola. L'accesso ai file passa dai permessi di Leo, quindi di serie è più sicuro.
Dove Python resta avanti
Il modulo csv di Python gestisce dialetti, separatori strani, virgolette e file enormi in streaming; con pandas l'analisi di tabelle grandi è di un altro livello. Leo copre i casi comuni, non quelli estremi.
Caso 7 di 11
Leggere un JSON
Interpretare una risposta JSON, per esempio quella di un servizio web.
import json
risposta = '{"citta": "Bologna", "gradi": 19, "venti": ["N", "NE"]}'
meteo = json.loads(risposta)
venti = ", ".join(meteo["venti"])
print(f"A {meteo['citta']}: {meteo['gradi']} gradi, venti da {venti}")
inizio
variabile risposta = "\{\"citta\": \"Bologna\", \"gradi\": 19, \"venti\": [\"N\", \"NE\"]}"
variabile meteo = da_json(risposta)
stampa("A {meteo["citta"]}: {meteo["gradi"]} gradi, venti da {", ".unisci(meteo["venti"])}")
fine
A Bologna: 19 gradi, venti da N, NE
Dove Leo è più comodo
da_json è globale, senza import, e i dati diventano dizionari e liste come in Python. Se il testo non è valido, l'errore dice dove (riga e colonna) e che cosa manca, in italiano.
Dove Python resta avanti
Il modulo json è nella libreria standard di Python e scrivere una stringa JSON a mano è più pulito: Leo interpola ogni testo tra { }, così le graffe del JSON vanno protette con \{. Da un file o da una risposta web il problema non si pone.
Caso 8 di 11
Un piccolo server web
Esporre due rotte JSON su un server locale e interrogarle.
from http.server import BaseHTTPRequestHandler, HTTPServer
import json, threading, urllib.request
PAGINE = {
"/api/saluto": {"messaggio": "Ciao da Python!"},
"/api/ora": {"servizio": "orologio", "attivo": True},
}
class Gestore(BaseHTTPRequestHandler):
def do_GET(self):
corpo = json.dumps(PAGINE.get(self.path, {})).encode()
self.send_response(200 if self.path in PAGINE else 404)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(corpo)
def log_message(self, *a): pass
server = HTTPServer(("127.0.0.1", 0), Gestore)
threading.Thread(target=server.serve_forever, daemon=True).start()
porta = server.server_address[1]
with urllib.request.urlopen(f"http://127.0.0.1:{porta}/api/saluto") as r:
print(json.load(r)["messaggio"])
server.shutdown()
inizio
variabile servizio = server_web(0, pagine = {
"/api/saluto": {"messaggio": "Ciao da Leo!"},
"/api/ora": {"servizio": "orologio", "attivo": vero}
})
servizio.avvia()
stampa("Server avviato, solo su questo computer (127.0.0.1).")
variabile r = scarica_json(servizio.indirizzo + "api/saluto")
stampa("La rotta /api/saluto risponde: {r["messaggio"]}")
servizio.ferma()
stampa("Server spento.")
fine
Server avviato, solo su questo computer (127.0.0.1). La rotta /api/saluto risponde: Ciao da Leo! Server spento.
Dove Leo è più comodo
server_web trasforma un dizionario di rotte in un servizio JSON: niente classi gestore, header o thread da scrivere a mano. server_web(0, ...) sceglie una porta libera e ascolta solo in locale.
Dove Python resta avanti
Per il web vero Python ha un ecosistema insuperabile: Flask, Django e FastAPI, con rotte, template, autenticazione, database e un server async capace di reggere molte connessioni insieme. server_web di Leo è pensato per demo e strumenti, non per siti in produzione.
Caso 9 di 11
Parallelismo
Avviare un calcolo lungo senza bloccare il resto del programma.
from concurrent.futures import ThreadPoolExecutor
def somma_fino(n):
totale = 0
for i in range(1, n + 1):
totale += i
return totale
with ThreadPoolExecutor() as pool:
lavoro = pool.submit(somma_fino, 1_000_000)
print("Il calcolo è partito; intanto vado avanti...")
print(f"Risultato: {lavoro.result():,}")
funzione somma_fino(n)
variabile totale = 0
per ogni i in 1..n fai
totale += i
fine
restituisci totale
fine
inizio
variabile lavoro = in parallelo fai
restituisci somma_fino(1_000_000)
fine
stampa("Il calcolo è partito; intanto vado avanti...")
stampa("Risultato: {aspetta(lavoro):migliaia}")
fine
Il calcolo è partito; intanto vado avanti... Risultato: 500.000.500.000
Dove Leo è più comodo
in parallelo ... fine è un costrutto del linguaggio: avvia il blocco di fianco e con aspetta ne raccogli il risultato. Leo controlla anche che il lavoro non modifichi di nascosto le variabili di fuori.
Dove Python resta avanti
Python ha il parallelismo più maturo e vario: threading, concurrent.futures, multiprocessing per usare davvero tutti i core, e asyncio per decine di migliaia di operazioni di rete insieme. Leo non ha ancora async/await.
Caso 10 di 11
Una password sicura
Conservare una password nel modo giusto: mai in chiaro, solo l'impronta.
import hashlib, os
def impronta(password):
sale = os.urandom(16)
chiave = hashlib.scrypt(password.encode(), salt=sale,
n=16384, r=8, p=1, dklen=32)
return sale.hex() + ":" + chiave.hex()
def verifica(password, salvata):
sale_hex, chiave_hex = salvata.split(":")
sale = bytes.fromhex(sale_hex)
chiave = hashlib.scrypt(password.encode(), salt=sale,
n=16384, r=8, p=1, dklen=32)
return chiave.hex() == chiave_hex
scelta = "Cavallo-Batteria-Pinzatrice-42"
salvata = impronta(scelta)
print("Login con password giusta:", verifica(scelta, salvata))
print("Login con password sbagliata:", verifica("123456", salvata))
inizio
variabile scelta = "Cavallo-Batteria-Pinzatrice-42"
variabile forza = misura_forza_password(scelta)
stampa("Robustezza: {forza["livello"]} (circa {forza["bit"]} bit)")
variabile salvata = impronta_password(scelta)
stampa("Salvo solo l'impronta scrypt, irreversibile ({lunghezza(salvata)} caratteri).")
stampa("Login con password giusta: {verifica_password(scelta, salvata)}")
stampa("Login con password sbagliata: {verifica_password("123456", salvata)}")
fine
Robustezza: ottima (circa 184.1 bit) Salvo solo l'impronta scrypt, irreversibile (83 caratteri). Login con password giusta: vero Login con password sbagliata: falso
Dove Leo è più comodo
impronta_password e verifica_password usano scrypt con sale casuale e confronto a tempo costante: la cosa giusta, in due righe e senza poterla sbagliare. In più misura_forza_password stima la robustezza senza mandare nulla in rete.
Dove Python resta avanti
In Python gli stessi mattoni ci sono (hashlib.scrypt), ma vanno montati a mano; in cambio hai la scelta fra tanti algoritmi provati (bcrypt, argon2) e librerie di sicurezza mantenute da esperti e aggiornate di continuo.
Caso 11 di 11
Una data e una durata
Calcolare quanto tempo passa fra due momenti e dire che giorno cade.
from datetime import datetime
avvio = datetime(2026, 3, 2, 9, 0)
consegna = datetime(2026, 3, 20, 17, 30)
giorni = (consegna - avvio).days
giorno = ["lunedì", "martedì", "mercoledì", "giovedì",
"venerdì", "sabato", "domenica"][consegna.weekday()]
print(f"Dal {avvio:%d/%m/%Y} al {consegna:%d/%m/%Y}")
print(f"Giorni di calendario: {giorni}")
print(f"La consegna cade di {giorno}")
inizio
variabile avvio = data(2026, 3, 2, ore = 9)
variabile consegna = data(2026, 3, 20, ore = 17, minuti = 30)
stampa("Dal {formatta_data(avvio, "medio")} al {formatta_data(consegna, "medio")}")
stampa("Giorni di calendario: {giorni_tra(avvio, consegna)}")
stampa("Durata esatta: {differenza_tra(avvio, consegna)}")
stampa("La consegna cade di {consegna.nome_giorno}")
fine
Dal 2 mar 2026 al 20 mar 2026 Giorni di calendario: 18 Durata esatta: 18 giorni, 8 ore e 30 minuti La consegna cade di venerdì
Dove Leo è più comodo
giorni_tra, differenza_tra e nome_giorno parlano italiano: la durata esce già scritta in parole (“18 giorni, 8 ore e 30 minuti”) e i nomi dei giorni sono in italiano, senza tabelle da costruire né codici di formato da ricordare.
Dove Python resta avanti
Il modulo datetime di Python, con zoneinfo per i fusi orari e librerie come dateutil, copre fusi, calendari e parsing di date in qualunque formato con una precisione che a Leo oggi manca.
In sintesi
Quando scegliere Leo, quando Python
Nessuno dei due “vince” sempre: dipende da che cosa stai facendo. Ecco la regola pratica.
Scegli Leo se…
- Vuoi programmare (o insegnare) in italiano, senza la barriera dell'inglese.
- Ti servono numeri esatti su denaro e misure senza pensarci.
- Vuoi errori chiari che dicono dove e perché, con un suggerimento.
- Fai app con finestre, giochi 2D, automazioni, strumenti interni.
- Vuoi un
.exeper Windows o un'app Android con un comando. - Vuoi tutto incluso: niente
pip install, niente ambienti da configurare.
Scegli Python se…
- Hai bisogno della massima velocità su calcoli pesanti e numerici.
- Ti serve l'enorme ecosistema di pacchetti (PyPI): IA, dati, scienza, web.
- Costruisci servizi web in produzione o molta rete con
async/await. - Lavori in team dove Python è lo standard e la comunità è sterminata.
- Ti servono librerie di nicchia mantenute da esperti del settore.
- Cerchi un linguaggio con vent'anni di documentazione e corsi in ogni lingua.
Leo gira sopra Python e ne eredita la solidità: i concetti sono gli stessi, chi conosce Python legge Leo al primo sguardo e chi impara con Leo passa a Python senza ricominciare da capo.
Il modo migliore per decidere è provarlo
Installa Leo Studio o apri l'app sul telefono, incolla uno di questi esempi e premi Esegui. In trenta secondi hai la tua risposta.
stampa("Ciao, mondo!")