Attributi privati
Fa parte della sezione Programmazione orientata agli oggetti del percorso Python di Coddy. Lezione 15 di 64.
Gli attributi privati usano i trattini bassi per indicare che determinati dati non dovrebbero essere accessibili direttamente dall’esterno della classe.
Perché abbiamo bisogno degli attributi privati? Immagina di avere un conto bancario. Non vuoi che qualcuno modifichi direttamente il tuo saldo: dovrebbe seguire i canali appropriati (metodi di deposito/prelievo), così la banca può convalidare la transazione, verificare eventuali frodi, tenere traccia delle operazioni e così via. Gli attributi privati funzionano allo stesso modo: proteggono i tuoi dati da modifiche errate.
Ecco un esempio che usa un singolo trattino basso (convenzione per «uso interno»):
class Person:
def __init__(self, name, age):
self._name = name # "protected" - uso interno
self._age = age # "protected" - uso internoÈ comunque possibile accedere agli attributi con un singolo trattino basso, ma è un segnale che indica di non farlo:
person = Person("Alice", 30)
print(person._name) # Funziona, ma non è consigliatoUsa i doppi underscore per una maggiore privacy (name mangling):
class Person:
def __init__(self, name, age):
self.__name = name # "private" - il nome viene modificato automaticamente
self.__age = age # "private" - il nome viene modificato automaticamente
def get_name(self):
return self.__name
def set_age(self, age):
if age >= 0:
self.__age = age
else:
print("Age must be positive!")Perché usare i metodi getter e setter? Sono come dei guardiani. Invece di permettere a chiunque di modificare direttamente __age (il che potrebbe causare età negative o altri dati non validi), imponiamo di usare set_age(), che convalida prima l’input. Questo previene i bug e garantisce l’integrità dei dati.
Il «codice extra» ha uno scopo:
get_name()- Accesso controllato per leggere il nomeset_age(age)- Convalida l’età prima di consentire le modifiche (niente età negative!)- Senza questi metodi, qualcuno potrebbe impostare
age = -100e compromettere il tuo programma
Usa i metodi di accesso per interagire con gli attributi privati:
person = Person("Bob", 25)
print(person.get_name()) # Bob
person.set_age(30) # Valido: age diventa 30
person.set_age(-5) # Non valido: age deve essere positivo! (age rimane 30)Vedi il vantaggio? Il metodo set_age() impedisce dati non validi. Senza di esso, potresti creare accidentalmente una persona con età -5, il che non ha senso!
Gli attributi con doppio trattino basso subiscono il «name mangling», ma è comunque possibile accedervi:
person = Person("Charlie", 35)
# Questo non funziona:
# print(person.__name) # AttributeError
# Ma questo funziona (sconsigliato):
print(person._Person__name) # CharlieEsempio reale - Perché tutto questo codice?
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private: can't be changed directly
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
def get_balance(self):
return self.__balance
# Without private attributes:
# account.__balance = -1000000 # Disaster! Negative balance allowed
# With private attributes:
account = BankAccount(100)
account.deposit(50) # Safe: validated
print(account.get_balance()) # 150Il "codice extra" (metodi) protegge i tuoi dati da modifiche non valide. È come avere guardie di sicurezza invece di lasciare i tuoi oggetti di valore senza protezione.
Output:
Alice
Bob
Age must be positive!
Charlie
150Un trattino basso singolo _attribute significa per convenzione «solo per uso interno». Il doppio trattino basso __attribute attiva la manipolazione dei nomi per una maggiore riservatezza. Usa i metodi getter/setter (metodi di accesso) per interagire correttamente con i dati privati e aggiungere la convalida. Il «codice extra» previene i bug assicurando che i dati siano sempre validi.
Sfida
FacileCompleta la classe User in user.py con la funzionalità di password privata, poi usala in driver.py. Segui i commenti TODO per implementare i metodi di verifica e modifica della password.
Provalo tu
# TODO: Importa la classe User dal modulo user
# TODO: Ottieni il caso di test dall'input
test_case = input()
if test_case == "constructor_test":
# TODO: Crea un oggetto User con password iniziale "secure123"
user = User("secure123")
print("User created successfully")
print("Initial password set")
elif test_case == "correct_password_test":
# TODO: Crea un oggetto User con password iniziale "secure123"
user = User("secure123")
# TODO: Verifica il metodo check_password con la password corretta
# Stampa "Password check successful!" se restituisce True
# Stampa "Password check failed!" se restituisce False
result = user.check_password("secure123")
if result:
print("Password check successful!")
else:
print("Password check failed!")
elif test_case == "incorrect_password_test":
# TODO: Crea un oggetto User con password iniziale "secure123"
user = User("secure123")
# TODO: Verifica il metodo check_password con la password errata "wrong"
# Stampa "Incorrect password rejected!" se restituisce False
# Stampa "Security issue: incorrect password accepted!" se restituisce True
result = user.check_password("wrong")
if not result:
print("Incorrect password rejected!")
else:
print("Security issue: incorrect password accepted!")
elif test_case == "change_password_test":
# TODO: Crea un oggetto User con password iniziale "secure123"
user = User("secure123")
# TODO: Verifica la modifica della password da "secure123" a "newpass456"
# Stampa "Password changed successfully!" se restituisce True
# Stampa "Password change failed!" se restituisce False
result = user.change_password("secure123", "newpass456")
if result:
print("Password changed successfully!")
else:
print("Password change failed!")
# TODO: Verifica che la nuova password funzioni controllando "newpass456"
# Stampa "New password works!" se restituisce True
# Stampa "New password doesn't work!" se restituisce False
if user.check_password("newpass456"):
print("New password works!")
else:
print("New password doesn't work!")
elif test_case == "change_password_wrong_old_test":
# TODO: Crea un oggetto User con password iniziale "secure123"
user = User("secure123")
# TODO: Prova a cambiare la password usando una vecchia password errata
# Stampa "Security working: incorrect old password rejected!" se restituisce False
# Stampa "Security issue: password changed with wrong old password!" se restituisce True
result = user.change_password("wrong", "hackerpw")
if not result:
print("Security working: incorrect old password rejected!")
else:
print("Security issue: password changed with wrong old password!")
elif test_case == "comprehensive_test":
# TODO: Crea un oggetto User con password iniziale "secure123"
user = User("secure123")
print("User created with initial password")
# TODO: Verifica il metodo check_password con la password corretta
if user.check_password("secure123"):
print("Initial password verification successful")
# TODO: Verifica il metodo check_password con una password errata
if not user.check_password("wrongpass"):
print("Incorrect password properly rejected")
# TODO: Verifica la modifica della password da "secure123" a "newpass456"
if user.change_password("secure123", "newpass456"):
print("Password successfully changed")
# TODO: Verifica che la vecchia password non funzioni più
if not user.check_password("secure123"):
print("Old password no longer works")
# TODO: Verifica che la nuova password funzioni
if user.check_password("newpass456"):
print("New password works correctly")
# TODO: Prova a cambiare la password usando una vecchia password errata
if not user.change_password("wrongold", "hackerpw"):
print("Security maintained: wrong old password rejected")
# TODO: Verifica che la password sia ancora sicura dopo un tentativo di modifica non riuscito
if user.check_password("newpass456"):
print("Password remains secure after failed change attempt")
else:
print("Unknown test case")Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP
File esterniIntroduzione a OOPClassi vs. oggettiIl parametro selfMetodiAttributiMetodo costruttore (__init__)Riepilogo - Calcolatrice semplice4Ereditarietà
Ereditarietà di baseLa funzione super()Sovrascrittura dei metodiEreditarietà multiplaOrdine di risoluzione dei metodiRiepilogo - Gerarchia dei dipendenti7Metodi speciali
Introduzione ai metodi magiciSovraccarico degli operatoriMetodi magici dei contenitoriRiepilogo - Lista personalizzata10Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello ObserverModello Strategy2Decoratori
Introduzione ai decoratoriDecoratore di proprietàDecoratore di metodo staticoDecoratore di metodo di classe5Polimorfismo
Ripasso dell'override dei metodiDuck typingClassi e metodi astrattiProgettazione delle interfacceRiepilogo - Calcolatore di forme8Concetti avanzati di OOP
Composizione vs ereditarietàMixinMetodi statici e di classeDecorator di classeContext manager11Pattern di progettazione - Parte 2
Pattern dei comandiPattern adattatorePattern decoratorePattern metodo templatePattern statoPattern composito3Proprietà delle classi
Variabili di istanza e di classeDecorator delle proprietàAttributi privatiRiepilogo - Gestore di conti bancari6Incapsulamento
Membri pubblici, protetti e privatiModificatori di accessoOccultamento delle informazioniDecorator delle proprietà: avanzatiRipasso - Sistema di registri degli studenti12Progetto: Gestione della biblioteca
Panoramica del progettoClassi Libro e UtenteSistema di prestitoFunzionalità di ricercaInterfaccia amministratoreTest e integrazioneEsercitati da solo: Compilatore Python online