Python & AI Tutorials Logo
Programmazione Python

40. Scrivere codice pulito e leggibile

Nel corso di questo libro, hai imparato la sintassi di Python, le strutture dati, il flusso di controllo, le funzioni e le classi, oltre a molti altri concetti di programmazione. Ora puoi scrivere programmi che funzionano. Ma c’è una differenza cruciale tra codice che funziona e codice che è manutenibile—codice che tu e gli altri potete comprendere, modificare e fare debugging mesi o anni dopo.

Questo capitolo si concentra sulla scrittura di codice pulito e leggibile. Imparerai le convenzioni e le pratiche che rendono il codice Python professionale e manutenibile. Non sono solo regole arbitrarie: sono linee guida collaudate sul campo che rendono più semplice collaborare, riducono i bug e ti aiutano a capire il tuo stesso codice quando ci ritorni più avanti.

40.1) Perché lo stile conta: leggere vs. scrivere codice

40.1.1) Il codice viene letto più spesso di quanto venga scritto

Quando scrivi codice, spendi minuti o ore per crearlo. Ma quel codice verrà letto molte volte: quando fai debugging, quando aggiungi funzionalità, quando altri sviluppatori ci lavorano e quando ci ritorni mesi dopo cercando di ricordare cosa fa.

Considera questo codice funzionante ma con uno stile scadente:

python
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
def c(l):
    t=0
    for i in l:
        t=t+i
    return t/len(l)
 
data=[85,92,78,90,88]
result=c(data)
print(result)  # Output: 86.6

Questo codice funziona perfettamente. Calcola la media di una lista(list) di numeri. Ma capire cosa fa richiede un’analisi accurata. Ora confrontalo con questa versione:

python
def calculate_average(numbers):
    """Calculate the arithmetic mean of a list of numbers."""
    total = 0
    for number in numbers:
        total = total + number
    return total / len(numbers)
 
test_scores = [85, 92, 78, 90, 88]
average_score = calculate_average(test_scores)
print(average_score)  # Output: 86.6

Cosa rende migliore la seconda versione?

  • Il nome della funzione (calculate_average) dichiara chiaramente lo scopo
  • I nomi delle variabili (numbers, total, test_scores) sono descrittivi
  • La docstring spiega cosa fa la funzione
  • La spaziatura corretta rende la struttura chiara
  • Chiunque può capire questo codice senza studiarlo

Entrambe le versioni producono risultati identici, ma la seconda versione è immediatamente comprensibile.

L’intuizione chiave: scrivi codice una volta, ma lo leggi decine o centinaia di volte. Investire pochi secondi in più in naming e formattazione chiari ti fa risparmiare ore di confusione più avanti.

40.1.2) La leggibilità riduce i bug

Un codice chiaro è più facile da sottoporre a debugging perché puoi capire rapidamente cosa fa ciascuna parte. Quando i nomi delle variabili sono descrittivi e la struttura è pulita, puoi individuare più facilmente gli errori logici.

python
# Difficile da sottoporre a debugging - cosa rappresentano queste variabili?
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
def process(x, y):
    if x > y:
        return x * (1 - y)
    return x
 
result = process(100, 0.1)
python
# Facile da sottoporre a debugging - è chiaro cosa sta succedendo
def apply_discount(price, discount_rate):
    """Calculate price after applying discount rate (0.0 to 1.0)."""
    discount_amount = price * discount_rate
    final_price = price - discount_amount
    return final_price
 
original_price = 100
discount = 0.1  # sconto del 10%
final_price = apply_discount(original_price, discount)
print(f"Final price: ${final_price}")
# Output: Final price: $90.0

Nella seconda versione, puoi vedere immediatamente la logica: "Calcoliamo l'importo dello sconto e poi lo sottraiamo dal prezzo." Nella prima versione, devi tenere mentalmente traccia di cosa rappresentano x e y e capire cosa significhi x * (1 - y).

40.1.3) La coerenza favorisce la collaborazione

Quando tutti in un team seguono le stesse convenzioni di stile, il codice diventa prevedibile. Non sprechi energia mentale a decifrare stili di formattazione diversi: puoi concentrarti sulla comprensione della logica.

Python ha una guida di stile ufficiale chiamata PEP 8 (Python Enhancement Proposal 8). PEP 8 definisce convenzioni per:

  • Come nominare variabili, funzioni e classi
  • Come formattare il codice (spaziatura, lunghezza delle righe, indentazione)
  • Quando usare commenti e docstring
  • Come organizzare gli import

Seguire PEP 8 significa che il tuo codice apparirà familiare agli altri programmatori Python, rendendo la collaborazione più fluida. Copriremo le linee guida essenziali di PEP 8 nelle prossime sezioni.

No

Scrivere codice

Seguire la guida di stile?

Codice coerente e leggibile

Codice incoerente

Facile da capire

Facile da mantenere

Facile da collaborare

Sovraccarico mentale

Confusione

Bug

40.2) Convenzioni di naming: variabili, funzioni e classi (PEP 8)

40.2.1) Principi generali di naming

I buoni nomi sono descrittivi e non ambigui. Dovrebbero dirti cosa rappresenta o fa qualcosa senza richiedere di leggere l’implementazione.

Principi chiave:

  • Usa parole complete, non abbreviazioni (tranne quelle molto comuni come id, url, html)
  • Sii specifico: user_count è meglio di count, calculate_total_price è meglio di calculate
  • Evita nomi a lettera singola tranne che per cicli molto brevi o formule matematiche
  • Non includere informazioni sul tipo nei nomi (Python è dinamicamente tipizzato)
python
# Nomi scarsi - non è chiaro cosa rappresentino
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
# Cos'è 'n'? Un numero? Un nome? Un nodo?
# Cos'è 'd'? Una data? Una distanza? Una durata?
# Cos'è 'l'? Sembra il numero 1!
n = "Alice"
d = 25
l = [1, 2, 3]
calc = lambda x: x * 2
 
# Buoni nomi - chiari e descrittivi
student_name = "Alice"
age_in_years = 25
test_scores = [1, 2, 3]
double_value = lambda x: x * 2

Eccezione: variabili brevi nei cicli

python
# Accettabile: molto corto, contesto chiaro
for i in range(10):
    print(i)
 
for x, y in coordinates:
    distance = (x**2 + y**2) ** 0.5
 
# Ma preferisci nomi descrittivi per chiarezza
for student_index in range(len(students)):
    print(students[student_index])
 
for point_x, point_y in coordinates:
    distance = (point_x**2 + point_y**2) ** 0.5

40.2.2) Nomi di variabili e funzioni: snake_case

In Python, variabili e funzioni usano snake_case: tutto minuscolo con parole separate da underscore.

python
# Variabili
user_name = "Bob"
total_price = 99.99
is_valid = True
max_retry_count = 3
 
# Funzioni
def calculate_tax(amount, rate):
    """Calculate tax on a given amount."""
    return amount * rate
 
def send_email_notification(recipient, message):
    """Send an email to the specified recipient."""
    print(f"Sending to {recipient}: {message}")
 
# Uso delle funzioni
tax_amount = calculate_tax(100, 0.08)
send_email_notification("user@example.com", "Welcome!")

Perché snake_case? È altamente leggibile. Gli underscore creano confini chiari tra le parole, rendendo i nomi facili da scansionare. Confronta calculatetotalprice (difficile da leggere) con calculate_total_price (immediatamente chiaro).

40.2.3) Nomi delle costanti: UPPER_SNAKE_CASE

Le costanti—valori che non dovrebbero cambiare durante l’esecuzione del programma—usano UPPER_SNAKE_CASE: tutto maiuscolo con underscore.

python
# Costanti a livello di modulo
MAX_LOGIN_ATTEMPTS = 3
DEFAULT_TIMEOUT_SECONDS = 30
PI = 3.14159
DATABASE_URL = "postgresql://localhost/mydb"
 
def validate_password_length(password):
    """Check if password meets minimum length requirement."""
    MIN_PASSWORD_LENGTH = 8  # Costante all'interno della funzione
    return len(password) >= MIN_PASSWORD_LENGTH
 
# Uso delle costanti
if login_attempts > MAX_LOGIN_ATTEMPTS:
    print("Account locked")

Importante: Python non ha una sintassi built-in per le costanti. A differenza di alcuni linguaggi (come const in JavaScript o final in Java), Python non ha un modo per dichiarare che una variabile non può essere cambiata.

Invece, i programmatori Python usano una convenzione di naming per segnalare l’intento:

  • UPPER_SNAKE_CASE significa: "intendo che questa sia una costante—non modificarla"
  • È uno strumento di comunicazione tra programmatori, non una feature del linguaggio
python
# Python non ha una sintassi per le costanti - questa è solo una variabile normale
MAX_LOGIN_ATTEMPTS = 3
 
# Python non ti impedirà di modificarla
MAX_LOGIN_ATTEMPTS = 5  # ❌ Tecnicamente funziona, ma viola la convenzione
 
# La convenzione di naming è un segnale sull'INTENTO:
# "L'ho chiamata in MAIUSCOLO per mostrare che non voglio che cambi"

Best practice: se un valore deve davvero cambiare durante l’esecuzione del programma, non chiamarlo come una costante:

python
# Questo valore cambierà - usa il minuscolo
max_login_attempts = 3
max_login_attempts = 5  # ✅ OK - il nome indica che può cambiare
 
# Questo valore non dovrebbe mai cambiare - usa il MAIUSCOLO
MAX_LOGIN_ATTEMPTS = 3
# Non riassegnarlo più avanti nel codice

La convenzione aiuta i programmatori a capire il tuo intento ed evitare bug. Quando vedi MAX_LOGIN_ATTEMPTS, sai di non doverlo modificare.

40.2.4) Nomi delle classi: PascalCase

I nomi delle classi usano PascalCase (chiamato anche CapWords): ogni parola inizia con una lettera maiuscola, senza underscore.

python
# Definizioni di classi
class Student:
    """Represent a student with name and grades."""
    def __init__(self, name):
        self.name = name
        self.grades = []
 
class ShoppingCart:
    """Manage items in a shopping cart."""
    def __init__(self):
        self.items = []
    
    def add_item(self, item):
        """Add an item to the cart."""
        self.items.append(item)
 
class DatabaseConnection:
    """Handle database connection and queries."""
    def __init__(self, url):
        self.url = url
 
# Creazione di istanze (nota: le istanze usano nomi variabile in snake_case)
student = Student("Alice")
shopping_cart = ShoppingCart()
db_connection = DatabaseConnection("localhost")

Perché PascalCase per le classi? Distingue visivamente le classi da funzioni e variabili. Quando vedi Student(), sai subito che sta creando un’istanza di una classe. Quando vedi calculate_average(), sai che sta chiamando una funzione.

40.2.5) Nomi privati e interni: underscore iniziale

I nomi che iniziano con un singolo underscore (_name) indicano uso interno—sono pensati per l’uso all’interno del modulo o della classe, non da codice esterno.

Python non ha una sintassi per marcare metodi o attributi come "privati" (a differenza di private in Java o C++). Invece, Python usa una convenzione di naming con un underscore iniziale (_name) per comunicare l’intento.

Cosa significa _name:

  • "Questo è solo per uso interno"
  • "L’ho creato per l’uso all’interno di questa classe/modulo, non per codice esterno"
  • "Questo potrebbe cambiare in qualsiasi momento in versioni future—non farci affidamento"
python
class BankAccount:
    """Represent a bank account with balance tracking."""
    
    def __init__(self, account_number, initial_balance):
        self.account_number = account_number
        self._balance = initial_balance  # Attributo interno
    
    def deposit(self, amount):
        """Add money to the account."""
        if self._validate_amount(amount):  # Metodo interno
            self._balance += amount
    
    def _validate_amount(self, amount):
        """Internal helper to validate transaction amounts."""
        return amount > 0
    
    def get_balance(self):
        """Return the current balance."""
        return self._balance
 
# Uso della classe
account = BankAccount("12345", 1000)
account.deposit(500)
print(account.get_balance())  # Output: 1500
 
# Tecnicamente funziona, ma viola la convenzione
print(account._balance)
# Output: 1500 (works, but you shouldn't do this!)
 
# Tecnicamente funziona, ma viola la convenzione
result = account._validate_amount(100)
# Output: True (works, but you shouldn't do this!)

Punto chiave: Python non può impedirti di accedere a _balance o chiamare _validate_amount(). L’underscore è un segnale tra programmatori, non una feature di sicurezza.

Perché esiste questa convenzione

Dato che Python non può imporre la privacy, l’underscore è il modo in cui gli autori di classi comunicano il loro intento:

Cosa segnala l’underscore:

  • "Questa è un’implementazione interna—potrebbe cambiare in versioni future"
  • "Usa invece i metodi pubblici—sono garantiti come stabili"
  • "Se dipendi dai dettagli interni, il tuo codice potrebbe rompersi quando aggiorno la libreria"

La convenzione crea un contratto: gli autori delle classi possono cambiare liberamente l’implementazione interna (qualunque cosa con _), ma devono mantenere stabile l’interfaccia pubblica. Questo permette alle librerie di evolvere senza rompere il codice degli utenti.

40.2.6) Nomi speciali: doppi underscore

I nomi con doppio underscore iniziale e finale (__name__) sono metodi speciali o magic methods definiti da Python. Non creare i tuoi nomi con questo schema: è riservato all’uso di Python.

python
class Point:
    """Represent a 2D point."""
    
    def __init__(self, x, y):  # Metodo speciale: inizializzazione
        self.x = x
        self.y = y
    
    def __str__(self):  # Metodo speciale: rappresentazione come stringa
        return f"Point({self.x}, {self.y})"
    
    def __add__(self, other):  # Metodo speciale: operatore di addizione
        return Point(self.x + other.x, self.y + other.y)
 
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1)  # Output: Point(1, 2)
print(p1 + p2)  # Output: Point(4, 6)

Come abbiamo imparato nel Capitolo 31, questi metodi speciali abilitano l’overloading degli operatori e l’integrazione con le funzioni built-in di Python.

40.2.7) Tabella riepilogativa del naming

TipoConvenzioneEsempio
Variabilisnake_caseuser_name, total_count
Funzionisnake_casecalculate_tax(), send_email()
CostantiUPPER_SNAKE_CASEMAX_SIZE, DEFAULT_TIMEOUT
ClassiPascalCaseStudent, ShoppingCart
Interno/Privato_leading_underscore_balance, _validate()
Speciale/Magicdouble_underscore__init__, __str__

40.3) Layout del codice: indentazione, spaziatura e righe vuote

40.3.1) Indentazione: quattro spazi

Python usa l’indentazione per definire i blocchi di codice. Usa sempre 4 spazi per livello di indentazione—mai tab, e non mescolare mai tab e spazi.

python
def calculate_grade(score):
    """Determine letter grade from numeric score."""
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    else:
        return "F"
 
# Indentazione annidata: 4 spazi per livello
def process_students(students):
    """Process a list of student records."""
    for student in students:
        if student["active"]:
            grade = calculate_grade(student["score"])
            print(f"{student['name']}: {grade}")
 
students = [
    {"name": "Alice", "score": 92, "active": True},
    {"name": "Bob", "score": 78, "active": True}
]
process_students(students)
# Output:
# Alice: A
# Bob: C

Perché 4 spazi? È lo standard della community Python. La maggior parte del codice Python che troverai usa 4 spazi, quindi seguire questa convenzione rende il tuo codice coerente con l’ecosistema.

Configurare il tuo editor: gli editor di codice moderni possono essere impostati per inserire 4 spazi quando premi Tab. Questo ti dà la comodità del tasto Tab mantenendo lo standard dei 4 spazi.

40.3.2) Lunghezza massima delle righe: 79 caratteri

PEP 8 raccomanda di limitare le righe a 79 caratteri (con fino a 99 caratteri per docstring e commenti). Può sembrare restrittivo, ma ha benefici pratici:

  • Il codice resta leggibile su schermi più piccoli
  • Puoi visualizzare due file affiancati
  • Incoraggia a spezzare espressioni complesse in parti più semplici

Nota: molti progetti moderni usano limiti leggermente più lunghi (88, 100 o 120 caratteri). La chiave è la coerenza all’interno del tuo progetto. Scegli un limite e rispettalo.

python
# Troppo lungo - difficile da leggere
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
def calculate_monthly_payment(principal, annual_rate, years):
    return principal * (annual_rate / 12) * (1 + annual_rate / 12) ** (years * 12) / ((1 + annual_rate / 12) ** (years * 12) - 1)
 
# Meglio - spezzato in righe leggibili
def calculate_monthly_payment(principal, annual_rate, years):
    """Calculate monthly loan payment using amortization formula."""
    monthly_rate = annual_rate / 12
    num_payments = years * 12
    
    numerator = principal * monthly_rate * (1 + monthly_rate) ** num_payments
    denominator = (1 + monthly_rate) ** num_payments - 1
    
    return numerator / denominator
 
payment = calculate_monthly_payment(200000, 0.045, 30)
print(f"Monthly payment: ${payment:.2f}")  # Output: Monthly payment: $1013.37

Spezzare righe lunghe: quando devi spezzare una riga, usa la continuazione implicita all’interno di parentesi tonde, quadre o graffe:

python
# Chiamata di funzione lunga
result = some_function(
    first_argument,
    second_argument,
    third_argument,
    fourth_argument
)
 
# Lista lunga
colors = [
    "red", "green", "blue",
    "yellow", "orange", "purple",
    "pink", "brown", "gray"
]
 
# Stringa lunga
message = (
    "This is a very long message that needs to be broken "
    "across multiple lines for readability. Python automatically "
    "concatenates adjacent string literals."
)
print(message)
# Output: This is a very long message that needs to be broken across multiple lines for readability. Python automatically concatenates adjacent string literals.

40.3.3) Spaziatura attorno agli operatori e dopo le virgole

Usa spazi attorno agli operatori e dopo le virgole per migliorare la leggibilità:

python
# Spaziatura scarsa - tutto stretto e difficile da leggere
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
x=5
y=x*2+3
result=calculate_tax(100,0.08)
data=[1,2,3,4,5]
 
# Buona spaziatura - chiaro e leggibile
x = 5
y = x * 2 + 3
result = calculate_tax(100, 0.08)
data = [1, 2, 3, 4, 5]
 
# Spaziatura nelle espressioni
total = (price * quantity) + shipping_cost
is_valid = (age >= 18) and (has_license == True)
 
# Spaziatura nelle definizioni di funzione
def calculate_discount(price, discount_rate, minimum_purchase=0):
    """Calculate discounted price if minimum purchase is met."""
    if price >= minimum_purchase:
        return price * (1 - discount_rate)
    return price

Eccezione: non usare spazi attorno a = negli argomenti keyword o nei valori di default dei parametri:

python
# Spaziatura corretta per argomenti keyword
result = calculate_discount(price=100, discount_rate=0.1, minimum_purchase=50)
 
# Spaziatura corretta per parametri di default
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

40.3.4) Righe vuote per separazione logica

Usa righe vuote per separare sezioni logiche del codice:

Due righe vuote tra funzioni e classi di primo livello:

python
def first_function():
    """First function."""
    pass
 
 
def second_function():
    """Second function."""
    pass
 
 
class MyClass:
    """A class definition."""
    pass

Una riga vuota tra i metodi all’interno di una classe:

python
class Student:
    """Represent a student with grades."""
    
    def __init__(self, name):
        self.name = name
        self.grades = []
    
    def add_grade(self, grade):
        """Add a grade to the student's record."""
        self.grades.append(grade)
    
    def get_average(self):
        """Calculate the student's grade average."""
        if not self.grades:
            return 0
        return sum(self.grades) / len(self.grades)

Righe vuote dentro le funzioni per separare passaggi logici:

python
def process_order(order_items, customer):
    """Process a customer order and calculate total."""
    
    # Calcola il subtotale
    subtotal = 0
    for item in order_items:
        subtotal += item["price"] * item["quantity"]
    
    # Applica lo sconto cliente
    discount = 0
    if customer["is_premium"]:
        discount = subtotal * 0.1
    
    # Calcola la tassa
    tax = (subtotal - discount) * 0.08
    
    # Calcola il totale finale
    total = subtotal - discount + tax
    
    return {
        "subtotal": subtotal,
        "discount": discount,
        "tax": tax,
        "total": total
    }

Queste righe vuote agiscono come "paragrafi" visivi, rendendo immediatamente evidente la struttura del codice.

40.3.5) Evitare gli spazi finali

Non lasciare spazi alla fine delle righe: sono invisibili ma possono causare problemi con i sistemi di controllo versione e alcuni editor.

python
# Cattivo - spazi finali invisibili (mostrati come · solo per illustrazione)
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
def calculate(x):···
    return x * 2···
 
# Buono - nessuno spazio finale
def calculate(x):
    return x * 2

La maggior parte degli editor moderni può essere configurata per rimuovere automaticamente gli spazi finali quando salvi un file.

40.4) Documentazione: scrivere commenti e docstring utili

40.4.1) Quando scrivere commenti

I commenti spiegano perché il codice fa qualcosa, non cosa fa. Variabili e funzioni ben nominate dovrebbero rendere ovvio il "cosa".

python
# Commento scarso - dice l'ovvio
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
x = x + 1  # Aggiungi 1 a x
 
# Commento buono - spiega il perché
x = x + 1  # Adatta per indicizzazione basata su zero
 
# Commento scarso - ridondante rispetto al codice
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
# Controlla se age è maggiore o uguale a 18
if age >= 18:
    print("Adult")
 
# Commento buono - spiega la logica di business
# Età legale per bere negli Stati Uniti
if age >= 21:
    print("Can purchase alcohol")

Quando i commenti sono preziosi:

  1. Spiegare algoritmi complessi:
python
def binary_search(sorted_list, target):
    """Search for target in sorted list using binary search."""
    left = 0
    right = len(sorted_list) - 1
    
    while left <= right:
        # Calcola il punto medio, evitando overflow di interi
        # (right + left) // 2 potrebbe andare in overflow con indici molto grandi
        mid = left + (right - left) // 2
        
        if sorted_list[mid] == target:
            return mid
        elif sorted_list[mid] < target:
            left = mid + 1  # Il target è nella metà destra
        else:
            right = mid - 1  # Il target è nella metà sinistra
    
    return -1  # Target non trovato
  1. Chiarire regole di business non ovvie:
python
def calculate_shipping_cost(weight, distance):
    """Calculate shipping cost based on weight and distance."""
    base_cost = 5.00
    
    # Promozione spedizione gratuita per articoli pesanti (policy aziendale al 2024)
    # Questo incoraggia ordini in grandi quantità e riduce i costi di spedizione per unità
    if weight > 50:
        return 0
    
    # Tariffa standard: $0.50 per libbra più $0.10 per miglio
    # Basata sul contratto con il corriere negoziato nel Q1 2024
    return base_cost + (weight * 0.50) + (distance * 0.10)
  1. Documentare workaround o soluzioni temporanee:
python
def process_data(data):
    """Process incoming data records."""
    # TODO: Questa è una correzione temporanea per record malformati
    # Rimuovere una volta implementata la validazione dei dati a monte
    if not isinstance(data, list):
        data = [data]
    
    for record in data:
        # Elabora ogni record
        pass

40.4.2) Scrivere docstring efficaci

Le docstring sono commenti speciali che documentano moduli, classi e funzioni. Sono racchiuse tra triple virgolette e compaiono come prima istruzione nella definizione.

python
def calculate_bmi(weight_kg, height_m):
    """
    Calculate Body Mass Index (BMI).
    
    BMI is calculated as weight in kilograms divided by the square of height in meters.
    
    Args:
        weight_kg: Weight in kilograms (float or int)
        height_m: Height in meters (float or int)
    
    Returns:
        float: The calculated BMI value
    
    Example:
        >>> calculate_bmi(70, 1.75)
        22.857142857142858
    """
    return weight_kg / (height_m ** 2)
 
# Accedere alle docstring
print(calculate_bmi.__doc__)
# Output:
#     Calculate Body Mass Index (BMI).
#     
#     BMI is calculated as weight in kilograms divided by the square of height in meters.
#     ...

Docstring su una sola riga per funzioni semplici:

python
def square(x):
    """Return the square of x."""
    return x * x
 
def is_even(n):
    """Return True if n is even, False otherwise."""
    return n % 2 == 0

Docstring multi-linea per funzioni complesse:

python
def find_prime_factors(n):
    """
    Find all prime factors of a positive integer.
    
    This function returns a list of prime numbers that, when multiplied
    together, equal the input number. The factors are returned in ascending order.
    
    Args:
        n: A positive integer greater than 1
    
    Returns:
        list: Prime factors in ascending order
    
    Raises:
        ValueError: If n is less than 2
    
    Example:
        >>> find_prime_factors(12)
        [2, 2, 3]
        >>> find_prime_factors(17)
        [17]
    """
    if n < 2:
        raise ValueError("n must be at least 2")
    
    factors = []
    divisor = 2
    
    while n > 1:
        while n % divisor == 0:
            factors.append(divisor)
            n = n // divisor
        divisor += 1
    
    return factors

Docstring delle classi:

python
class BankAccount:
    """
    Represent a bank account with deposit and withdrawal operations.
    
    This class maintains an account balance and provides methods for
    depositing and withdrawing money. All transactions are validated to prevent negative balances.
    
    Attributes:
        account_number: Unique identifier for the account
        balance: Current account balance in dollars
    """
    
    def __init__(self, account_number, initial_balance=0):
        """
        Initialize a new bank account.
        
        Args:
            account_number: Unique account identifier (string)
            initial_balance: Starting balance (default: 0)
        """
        self.account_number = account_number
        self.balance = initial_balance
    
    def deposit(self, amount):
        """
        Add money to the account.
        
        Args:
            amount: Amount to deposit (must be positive)
        
        Raises:
            ValueError: If amount is not positive
        """
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self.balance += amount

40.4.3) Convenzioni per le docstring

Prima riga: breve riepilogo di cosa fa la funzione/classe. Dovrebbe stare su una riga.

Riga vuota: separa il riepilogo dalla descrizione dettagliata.

Descrizione dettagliata: spiega cosa fa la funzione, eventuali dettagli importanti e come usarla.

Args/Parameters: elenca ogni parametro con il suo tipo e scopo.

Returns: descrive cosa restituisce la funzione e il suo tipo.

Raises: documenta le eccezioni che la funzione potrebbe sollevare.

Example: mostra un uso tipico (opzionale ma utile).

python
def calculate_compound_interest(principal, rate, time, compounds_per_year=1):
    """
    Calculate compound interest on an investment.
    
    Uses the compound interest formula: A = P(1 + r/n)^(nt)
    where A is the final amount, P is principal, r is annual rate,
    n is compounds per year, and t is time in years.
    
    Args:
        principal: Initial investment amount (float)
        rate: Annual interest rate as decimal (e.g., 0.05 for 5%)
        time: Investment period in years (float)
        compounds_per_year: Number of times interest compounds annually
                           (default: 1 for annual compounding)
    
    Returns:
        float: Final amount after compound interest
    
    Example:
        >>> calculate_compound_interest(1000, 0.05, 10, 12)
        1647.0095
    """
    return principal * (1 + rate / compounds_per_year) ** (compounds_per_year * time)

40.4.4) Commenti TODO per lavori futuri

Usa commenti TODO per segnare aree che richiedono attenzione in futuro:

python
def process_payment(amount, payment_method):
    """Process a payment transaction."""
    # TODO: Aggiungere supporto per pagamenti in criptovaluta
    # TODO: Implementare controlli di rilevamento frodi
    
    if payment_method == "credit_card":
        return process_credit_card(amount)
    elif payment_method == "paypal":
        return process_paypal(amount)
    else:
        raise ValueError(f"Unsupported payment method: {payment_method}")

Molti editor possono cercare commenti TODO, rendendo facile trovare aree che necessitano di lavoro.

40.5) Organizzare il codice: import, costanti, funzioni e main

40.5.1) Struttura standard di un modulo

Un modulo Python ben organizzato segue questa struttura:

  1. Docstring del modulo: descrive cosa fa il modulo
  2. Import: libreria standard, terze parti, poi import locali
  3. Costanti: costanti a livello di modulo
  4. Funzioni e classi: codice principale
  5. Blocco di esecuzione main: codice che viene eseguito quando lo script viene avviato
python
"""
student_manager.py
 
Gestisce i record degli studenti, inclusi voti e calcoli del GPA.
 
Questo modulo fornisce funzioni per aggiungere studenti, registrare i voti
e calcolare le medie dei voti (grade point averages).
"""
 
# Import della libreria standard
import sys
from datetime import datetime
 
# Import di terze parti (se presenti)
# import requests
 
# Import locali (se presenti)
# from .database import save_student
 
# Costanti
MAX_GRADE = 100
MIN_GRADE = 0
PASSING_GRADE = 60
 
# Funzioni
def calculate_gpa(grades):
    """
    Calculate GPA from a list of numeric grades.
    
    Args:
        grades: List of numeric grades (0-100)
    
    Returns:
        float: GPA on 4.0 scale
    """
    if not grades:
        return 0.0
    
    average = sum(grades) / len(grades)
    
    # Conversione alla scala 4.0
    if average >= 90:
        return 4.0
    elif average >= 80:
        return 3.0
    elif average >= 70:
        return 2.0
    elif average >= 60:
        return 1.0
    else:
        return 0.0
 
def validate_grade(grade):
    """
    Check if a grade is within valid range.
    
    Args:
        grade: Numeric grade to validate
    
    Returns:
        bool: True if grade is valid, False otherwise
    """
    return MIN_GRADE <= grade <= MAX_GRADE
 
# Esecuzione main
if __name__ == "__main__":
    # Codice che viene eseguito quando lo script viene avviato direttamente
    test_grades = [85, 92, 78, 88]
    gpa = calculate_gpa(test_grades)
    print(f"GPA: {gpa}")  # Output: GPA: 3.0

40.5.2) Organizzazione degli import

Raggruppa gli import in tre sezioni, separate da righe vuote:

  1. Import della libreria standard: moduli Python built-in
  2. Import di terze parti: pacchetti installati (come requests, numpy)
  3. Import locali: i tuoi moduli
python
# Import della libreria standard
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
 
# Import di terze parti
import requests
from flask import Flask, render_template
 
# Import locali dell'applicazione
from myapp.database import connect_db
from myapp.models import User, Product
from myapp.utils import format_currency

Stili di import:

python
# Importa l'intero modulo
import math
result = math.sqrt(16)  # Output: 4.0
 
# Importa elementi specifici
from math import sqrt, pi
result = sqrt(16)  # Output: 4.0
 
# Import con alias
import numpy as np
array = np.array([1, 2, 3])
 
# Importa elementi multipli
from os import path, getcwd, listdir

Evita gli import wildcard (from module import *)—rendono poco chiaro da dove provengano i nomi:

python
# Scarso - non è chiaro da dove provenga sqrt
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
from math import *
result = sqrt(16)
 
# Buono - import esplicito
from math import sqrt
result = sqrt(16)

40.5.3) Organizzare le costanti

Posiziona le costanti a livello di modulo vicino all’inizio, dopo gli import:

python
"""Configuration settings for the application."""
 
import os
 
# Costanti dell'applicazione
APP_NAME = "Student Manager"
VERSION = "1.0.0"
DEBUG_MODE = True
 
# Configurazione database
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///students.db")
MAX_CONNECTIONS = 10
 
# Regole di business
MAX_STUDENTS_PER_CLASS = 30
PASSING_GRADE = 60
GRADE_WEIGHTS = {
    "homework": 0.3,
    "midterm": 0.3,
    "final": 0.4
}
 
def calculate_final_grade(homework, midterm, final):
    """Calculate weighted final grade."""
    return (
        homework * GRADE_WEIGHTS["homework"] +
        midterm * GRADE_WEIGHTS["midterm"] +
        final * GRADE_WEIGHTS["final"]
    )

40.5.4) Ordinamento logico delle funzioni

Organizza le funzioni in un ordine logico:

  1. Funzioni pubbliche per prime: funzioni pensate per essere usate da altri moduli
  2. Funzioni di supporto dopo: funzioni interne che supportano quelle pubbliche
  3. Funzioni correlate insieme: raggruppa funzioni che lavorano insieme
python
"""Order processing module."""
 
# Funzioni dell'API pubblica
def process_order(order_items, customer):
    """
    Process a customer order.
    
    This is the main entry point for order processing.
    """
    subtotal = _calculate_subtotal(order_items)
    discount = _calculate_discount(subtotal, customer)
    tax = _calculate_tax(subtotal - discount)
    total = subtotal - discount + tax
    
    return {
        "subtotal": subtotal,
        "discount": discount,
        "tax": tax,
        "total": total
    }
 
def validate_order(order_items):
    """Validate that an order contains valid items."""
    if not order_items:
        return False
    
    for item in order_items:
        if not _validate_item(item):
            return False
    
    return True
 
# Funzioni helper interne
def _calculate_subtotal(items):
    """Calculate order subtotal (internal use)."""
    total = 0
    for item in items:
        total += item["price"] * item["quantity"]
    return total
 
def _calculate_discount(subtotal, customer):
    """Calculate customer discount (internal use)."""
    if customer.get("is_premium"):
        return subtotal * 0.1
    return 0
 
def _calculate_tax(amount):
    """Calculate sales tax (internal use)."""
    TAX_RATE = 0.08
    return amount * TAX_RATE
 
def _validate_item(item):
    """Validate a single order item (internal use)."""
    required_fields = ["name", "price", "quantity"]
    return all(field in item for field in required_fields)

Nota come le funzioni pubbliche (process_order, validate_order) vengono prima, e le funzioni helper (prefissate con _) vengono dopo. Questo rende chiaro quali funzioni sono l’API principale.

40.5.5) Organizzazione delle classi nei moduli

Quando un modulo contiene classi, organizzale in modo logico:

python
"""User management system."""
 
# Costanti
DEFAULT_ROLE = "user"
ADMIN_ROLE = "admin"
 
# Prima le classi base
class User:
    """Base user class."""
    
    def __init__(self, username, email):
        self.username = username
        self.email = email
        self.role = DEFAULT_ROLE
    
    def can_edit(self, resource):
        """Check if user can edit a resource."""
        return resource.owner == self.username
 
# Poi le classi derivate dopo le classi base
class AdminUser(User):
    """Administrator with elevated privileges."""
    
    def __init__(self, username, email):
        super().__init__(username, email)
        self.role = ADMIN_ROLE
    
    def can_edit(self, resource):
        """Admins can edit any resource."""
        return True
 
# Classi correlate raggruppate insieme
class Resource:
    """Represent a resource that can be owned and edited."""
    
    def __init__(self, name, owner):
        self.name = name
        self.owner = owner
 
# Funzioni di utilità correlate alle classi
def create_user(username, email, is_admin=False):
    """Factory function to create appropriate user type."""
    if is_admin:
        return AdminUser(username, email)
    return User(username, email)

Principi di organizzazione delle classi:

  • Classi base prima delle classi derivate (i lettori devono capire prima la base)
  • Classi correlate raggruppate insieme (User e Resource sono correlate)
  • Le funzioni di utilità che lavorano con le classi vengono dopo le definizioni delle classi
  • Ogni classe dovrebbe avere una docstring chiara che spiega il suo scopo

40.6) Il pattern if name == "main"

40.6.1) Comprendere il pattern

Ogni file Python ha una variabile built-in chiamata __name__. Python imposta automaticamente il valore di questa variabile a seconda di come viene usato il file:

  • Quando esegui un file direttamente (ad esempio python my_script.py), Python imposta __name__ a "__main__"
  • Quando importi un file come modulo, Python imposta __name__ al nome del modulo (il nome del file senza .py)

Questo ti permette di scrivere codice che viene eseguito solo quando il file viene avviato direttamente, non quando viene importato:

python
"""math_utils.py - Mathematical utility functions."""
 
def add(a, b):
    """Add two numbers."""
    return a + b
 
def multiply(a, b):
    """Multiply two numbers."""
    return a * b
 
# Questo codice viene eseguito solo quando il file viene avviato direttamente
if __name__ == "__main__":
    # Testa le funzioni
    print(f"5 + 3 = {add(5, 3)}")  # Output: 5 + 3 = 8
    print(f"5 * 3 = {multiply(5, 3)}")  # Output: 5 * 3 = 15

Quando esegui python math_utils.py, vedrai l’output. Ma quando lo importi in un altro file:

python
# another_file.py
from math_utils import add, multiply
 
result = add(10, 20)
print(result)  # Output: 30
# Il codice di test da math_utils.py NON viene eseguito

Nota che il codice di test (dentro if __name__ == "__main__":) NON viene eseguito quando viene importato!

python math_utils.py

import math_utils

File Python eseguito

Come viene avviato?

name = 'main'

name = 'math_utils'

Il codice in if name == 'main' viene eseguito

Il codice in if name == 'main' viene saltato

40.6.2) Perché questo pattern è importante

Questo pattern serve a diversi scopi importanti:

1. Test e dimostrazione: puoi includere esempi d’uso nello stesso file delle tue funzioni:

python
"""temperature.py - Temperature conversion utilities."""
 
def celsius_to_fahrenheit(celsius):
    """Convert Celsius to Fahrenheit."""
    return (celsius * 9/5) + 32
 
def fahrenheit_to_celsius(fahrenheit):
    """Convert Fahrenheit to Celsius."""
    return (fahrenheit - 32) * 5/9
 
if __name__ == "__main__":
    # Dimostra le funzioni
    print("Temperature Conversion Examples:")
    print(f"0°C = {celsius_to_fahrenheit(0)}°F")  # Output: 0°C = 32.0°F
    print(f"100°C = {celsius_to_fahrenheit(100)}°F")  # Output: 100°C = 212.0°F
    print(f"32°F = {fahrenheit_to_celsius(32)}°C")  # Output: 32°F = 0.0°C

2. Moduli riutilizzabili: lo stesso file può essere sia uno script standalone sia un modulo importabile:

python
"""data_processor.py - Process and analyze data files."""
 
import sys
 
def load_data(filename):
    """Load data from a file."""
    with open(filename) as f:
        return [line.strip() for line in f]
 
def analyze_data(data):
    """Perform analysis on data."""
    return {
        "count": len(data),
        "average_length": sum(len(item) for item in data) / len(data)
    }
 
if __name__ == "__main__":
    # Quando viene eseguito come script, elabora gli argomenti da riga di comando
    if len(sys.argv) < 2:
        print("Usage: python data_processor.py <filename>")
        sys.exit(1)
    
    filename = sys.argv[1]
    data = load_data(filename)
    results = analyze_data(data)
    
    print(f"Processed {results['count']} items")
    print(f"Average length: {results['average_length']:.2f}")

Puoi eseguirlo come script:

bash
$ python data_processor.py data.txt
Processed 42 items
Average length: 15.23

Oppure importarlo in un altro file:

python
# my_analysis.py
from data_processor import load_data, analyze_data
 
my_data = load_data("myfile.txt")
results = analyze_data(my_data)
print(f"Found {results['count']} items")

40.6.3) Pattern comuni per i blocchi main

Pattern 1: casi di test semplici

python
"""calculator.py - Basic calculator operations."""
 
def add(a, b):
    """Add two numbers."""
    return a + b
 
def subtract(a, b):
    """Subtract b from a."""
    return a - b
 
if __name__ == "__main__":
    # Test rapidi
    assert add(2, 3) == 5
    assert subtract(10, 4) == 6
    print("All tests passed!")  # Output: All tests passed!

Pattern 2: funzione main

Per script più complessi, definisci una funzione main():

python
"""report_generator.py - Generate reports from data."""
 
import sys
 
def load_data(filename):
    """Load data from file."""
    # Implementazione qui
    pass
 
def generate_report(data):
    """Generate report from data."""
    # Implementazione qui
    pass
 
def save_report(report, output_file):
    """Save report to file."""
    # Implementazione qui
    pass
 
def main():
    """Main entry point for the script."""
    if len(sys.argv) < 3:
        print("Usage: python report_generator.py <input> <output>")
        return 1
    
    input_file = sys.argv[1]
    output_file = sys.argv[2]
    
    try:
        data = load_data(input_file)
        report = generate_report(data)
        save_report(report, output_file)
        print(f"Report saved to {output_file}")
        return 0
    except Exception as e:
        print(f"Error: {e}")
        return 1
 
if __name__ == "__main__":
    # Esci con il codice di stato restituito da main (0 = successo, 1 = errore)
    sys.exit(main())

Questo pattern ha diversi vantaggi:

  • La funzione main() può essere testata in modo indipendente
  • Punto di ingresso chiaro per lo script
  • Codici di uscita corretti (0 per successo, diversi da zero per errori)
  • Separazione pulita tra logica dello script e funzioni del modulo

40.6.4) Best practice per i blocchi main

Mantieni i blocchi main focalizzati: il codice dentro if __name__ == "__main__" dovrebbe gestire principalmente l’esecuzione dello script, non contenere logica complessa:

python
# Scarso - logica complessa nel blocco main
# ATTENZIONE: stile scadente - solo a scopo dimostrativo
if __name__ == "__main__":
    data = []
    for i in range(100):
        if i % 2 == 0:
            data.append(i * 2)
    result = sum(data) / len(data)
    print(result)
 
# Buono - logica nelle funzioni, il blocco main coordina
def generate_even_doubles(limit):
    """Generate doubled even numbers up to limit."""
    return [i * 2 for i in range(limit) if i % 2 == 0]
 
def calculate_average(numbers):
    """Calculate average of numbers."""
    return sum(numbers) / len(numbers)
 
if __name__ == "__main__":
    data = generate_even_doubles(100)
    result = calculate_average(data)
    print(result)  # Output: 99.0

Usa una funzione main() per script complessi: come mostrato in precedenza, definire una funzione main() rende lo script più testabile e organizzato.

Documenta l’uso dello script: se il tuo script accetta argomenti da riga di comando, documentali nella docstring del modulo:

python
"""
file_processor.py - Process text files with various operations.
 
Usage:
    python file_processor.py <input_file> <output_file> [--uppercase]
 
Arguments:
    input_file: Path to input file
    output_file: Path to output file
    --uppercase: Convert text to uppercase (optional)
"""
 
import sys
 
def process_file(input_path, output_path, uppercase=False):
    """Process file with specified options."""
    with open(input_path) as f:
        content = f.read()
    
    if uppercase:
        content = content.upper()
    
    with open(output_path, 'w') as f:
        f.write(content)
 
if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(__doc__)  # Stampa la docstring del modulo
        sys.exit(1)
    
    input_file = sys.argv[1]
    output_file = sys.argv[2]
    uppercase = "--uppercase" in sys.argv
    
    process_file(input_file, output_file, uppercase)
    print(f"Processed {input_file} -> {output_file}")

Scrivere codice pulito e leggibile è un’abilità che si sviluppa con la pratica. Le convenzioni e i pattern di questo capitolo non sono regole arbitrarie: sono pratiche dimostrate che rendono il codice più facile da capire, mantenere e sottoporre a debugging. Man mano che scrivi più codice Python, questi pattern diventeranno una seconda natura.

Ricorda: il codice viene letto molto più spesso di quanto venga scritto. I pochi secondi extra che spendi per scegliere un nome chiaro, aggiungere un commento utile o organizzare correttamente gli import ti faranno risparmiare ore di confusione più avanti—per te stesso e per gli altri che lavorano con il tuo codice.

Nel prossimo capitolo, esploreremo tecniche di debugging e test che si basano su queste pratiche di clean code, aiutandoti a scrivere non solo codice leggibile, ma codice corretto e affidabile.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai