Automatizzare la ridenominazione sicura di variabili con rope
Rinomina variabili in Python in modo sicuro con rope: niente sostituzioni cieche. Setup in venv, un esempio concreto e come verificare il diff.
Quando generi script con l’IA o assembla pezzi da chat diverse, il codice può funzionare con nomi pessimi: data, temp, result2, x sparsi ovunque.
Correggerli a mano o con «Trova e sostituisci» può rompere stringhe, commenti e omonimi in altri scope. Questo micro, spoke del refactoring automatico con AST, Ruff e Rope, mostra come usare rope per un rename scope-aware: solo i riferimenti al simbolo giusto.
ℹ️ Prerequisito: Python 3.11+ (lab verificato con Rope 1.15.x). Lavora in un venv. Per la mappa degli indizi prima del rename, vedi lo scanner AST.
Perché «Trova e sostituisci» non basta
Ctrl+H / Cmd+H tratta il file come testo. Non distingue variabili, funzioni e letterali.
def calcola_totale(data):
# Processiamo i data
log_message = "Analisi della variabile data iniziata"
return sum(data)
def aggiorna_data_database(nuova_data):
print(nuova_data)
Se sostituisci cieco data → valori_input:
- il commento cambia (rumore);
- la stringa cambia (output/test possono rompersi);
aggiorna_data_databasediventaaggiorna_valori_input_database(simbolo sbagliato).
Serve uno strumento che ragioni sull’AST, non sulle sottostringhe.
Cos’è rope e come ragiona
Rope è una libreria Python orientata al refactoring semantico. Diverso da Ruff (lint/format veloce): rope modifica struttura.
In un rename tipico:
- apre un
Project(grafo dei moduli nella cartella); - individua il simbolo all’offset (posizione carattere del cursore);
- trova i riferimenti a quell’oggetto nello scope corretto;
- applica le modifiche (anche cross-file, se il progetto le include).
Molti IDE usano motori simili dietro il rename. Qui usiamo l’API per capire l’offset e automatizzare il lab.
Preparare l’ambiente
Non installare rope nel Python globale.
Windows (PowerShell):
python -m venv venv
.\venv\Scripts\Activate.ps1
python -m pip install rope
macOS / Linux:
python3 -m venv venv
source venv/bin/activate
python3 -m pip install rope
python -c "import rope; print('Rope OK')"
Laboratorio: tmp → invoice_total
1. File di partenza
Crea demo.py:
# demo.py
def calcola_fattura(prezzi):
tmp = sum(prezzi)
# Questo commento cita tmp, ma non deve cambiare
stato = "Calcolato tmp"
if tmp > 100:
return tmp * 0.90 # Sconto del 10%
return tmp
def altra_funzione(tmp):
# Questo è un altro 'tmp' in uno scope diverso.
# Non deve essere toccato dalla nostra ridenominazione.
return tmp + 1
2. Script di ridenominazione
L’offset è l’indice del carattere su cui «punti» il cursore. Nello script usiamo find('tmp =') come espediente didattico (in IDE arriva dal cursore / LSP).
🚨 Modifica file: lo script sovrascrive
demo.py. Su codice reale: commit Git prima.
Crea esegui_rename.py:
# esegui_rename.py
import os
from rope.base.project import Project
from rope.refactor.rename import Rename
def rinomina_variabile() -> None:
percorso_progetto = os.path.abspath(".")
progetto = Project(percorso_progetto)
risorsa = progetto.get_resource("demo.py")
codice = risorsa.read()
# Espediente lab: prima definizione "tmp =".
# In produzione: offset dal cursore IDE / da AST (lineno+col).
target = "tmp ="
offset = codice.find(target)
if offset == -1:
print("Variabile non trovata.")
return
print(f"Variabile trovata all'offset {offset}.")
refactoring = Rename(progetto, risorsa, offset)
cambiamenti = refactoring.get_changes("invoice_total")
progetto.do(cambiamenti)
print("Ridenominazione completata.")
progetto.close()
if __name__ == "__main__":
rinomina_variabile()
python esegui_rename.py
Output tipico verificato nel lab: Variabile trovata all'offset 44. poi conferma di completamento.
Come verificare il risultato
demo.py dopo Rope (lab):
# demo.py
def calcola_fattura(prezzi):
invoice_total = sum(prezzi)
# Questo commento cita tmp, ma non deve cambiare
stato = "Calcolato tmp"
if invoice_total > 100:
return invoice_total * 0.90 # Sconto del 10%
return invoice_total
def altra_funzione(tmp):
# Questo è un altro 'tmp' in uno scope diverso.
# Non deve essere toccato dalla nostra ridenominazione.
return tmp + 1
Rope ha:
- rinominato definizione e usi di
tmpsolo incalcola_fattura; - lasciato intatte stringa e commento;
- ignorato il parametro
tmpdialtra_funzione(altro scope).
💡 Git prima e dopo:
git add/ commit del codice «brutto», lancia Rope, poigit diff. Se qualcosa non torna:git restore .. Meglio del controllo a occhio.
Errori comuni e casi limite
- SyntaxError / AST rotto — Rope non rinomina se il file non parse-a. Sistema la sintassi prima (fence markdown rimasti nel
.py, parentesi aperte, …). - Radice progetto sbagliata —
Project('.')deve essere la cartella del lab/repo, non la home. Rope crea.ropeprojectper la cache: tienila nel perimetro del progetto (e in.gitignorese non vuoi versionarla). - File non salvati — Rope legge il disco. Salva l’editor prima, altrimenti offset e contenuto possono non coincidere.
- Dinamismo forte —
getattr/setattr/evalcon nomi come stringhe: l’analisi statica può non seguirli. Controlla il diff. find()fragile — setmp =compare prima in un commento, l’offset punta lì. Mitigazione lab: patterntmp =; in produzione usa cursore IDE o offset da AST.
Undo rapido in sessione Rope: progetto.history.undo() — comunque preferisci Git.
IDE rename vs API: quando cosa
| Situazione | Preferisci |
|---|---|
| Un simbolo, un file, cursore già sopra | Rename dell’IDE / LSP |
| Capire perché non tocca stringhe/altri scope | Questo lab API |
| Batch su più simboli / CI headless | Script Rope + Git |
| Solo rumore stilistico (import, spazi) | Ruff, non Rope |
Nel vibe coding: dopo un Accept grosso, spesso basta l’IDE. Quando l’IA ha lasciato dieci tmp/data omonimi in scope diversi, l’API (o rename mirati uno alla volta) riduce il rischio di «sostituisci tutto» emotivo.
Offset da AST (cenno)
Invece di find('tmp =') puoi calcolare l’offset dal nodo:
import ast
src = open("demo.py", encoding="utf-8").read()
tree = ast.parse(src)
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
for t in node.targets:
if isinstance(t, ast.Name) and t.id == "tmp":
# byte offset: somma lunghezze righe precedenti + col_offset
lines = src.splitlines(keepends=True)
offset = sum(len(lines[i]) for i in range(t.lineno - 1)) + t.col_offset
print("offset", offset)
Poi passi quell’offset a Rename(...). Più verboso, meno fragile del find su file lunghi.
Domande Frequenti (FAQ)
Cos’è rope in Python?
Libreria open source di refactoring basata sull’AST: rename, extract, move, ecc.
Differenza con «Trova e sostituisci»?
Il primo è testuale; rope è scope-aware e distingue stringhe/commenti dai simboli (nei limiti dell’analisi statica).
Come lo installo?
Nel venv: python -m pip install rope (o python3 -m pip …).
È sicuro al 100%?
No. È accurato sui simboli statici tipici, ma può mancare riferimenti dinamici. git diff obbligatorio.
Rename su più file?
Sì, se il Project include i moduli: aggiorna definizione, import e chiamate nel perimetro analizzato.
Rope vs Ruff?
Complementari. Nella pipeline madre: Ruff (format/import) → Rope (rename) → Ruff check → review 15 minuti.
Devo usare l’API o basta l’IDE?
Per un rename singolo, l’IDE basta. L’API serve a capire l’offset, a lab riproducibili e a batch headless.
In sintesi
- Non rinominare a occhio con sostituzione cieca.
- Rope punta un offset, risolve lo scope, aggiorna solo i riferimenti giusti.
- Commit → rename →
git diff→ test smoke. - Poi rientra nel flusso completo AST → Ruff → Rope della guida madre.