I descriptor di Python, o più in generale i descriptor, ti offrono una potente tecnica per scrivere codice riutilizzabile che può essere condiviso tra classi. Possono sembrare simili al concetto di ereditarietà, ma tecnicamente non lo sono. Sono un modo generico per intercettare l’accesso agli attributi. I descriptor sono il meccanismo alla base di property, metodi statici, metodi di classe, super, ecc.
I descriptor sono stati aggiunti in Python dalla versione 2.2 e da allora sono considerati strumenti “magici” che hanno dato un nuovo stile alle classi tradizionali. Sono classi che ti permettono di gestire proprietà in un’altra classe. Nello specifico, implementano l’interfaccia per i metodi __get__(), __set__() e __delete__(), il che li rende interessanti per molti motivi. Per esempio, un class decorator e un property decorator che avrai già visto in Python.
In parole povere, una classe che implementa i metodi __get__(), __set()__ o __delete()__ per un oggetto è detta "Descriptor". Citando direttamente la documentazione ufficiale di Python, un descriptor è un attributo di oggetto con binding behavior, il cui accesso è stato ridefinito dai metodi del protocollo dei descriptor. Questi metodi sono __get__(), __set__() e __delete__() (Fonte).
Il binding behavior rispetto ai descriptor significa vincolare il modo in cui un valore può essere impostato, interrogato (get) o eliminato per una determinata variabile, oggetto o insieme di dati. Questa interazione completa è vincolata a quel pezzo di dato perché si applica solo ai dati su cui l’hai impostata, legandola quindi solo a quella specifica parte di dati.
I descriptor possono essere ulteriormente classificati in data e non-data descriptor. Se il descriptor che scrivi ha solo il metodo __get__(), allora è un non-data descriptor; invece, un’implementazione che coinvolge i metodi __set__() e __delete__() è chiamata data descriptor. I non-data descriptor sono solo leggibili, mentre i data descriptor sono sia leggibili che scrivibili.
È importante notare che i descriptor sono assegnati a una classe, non all’istanza di una classe. Modificare la classe sovrascrive o elimina il descriptor stesso, invece di attivarne il codice (IBM Developer).
Infine, la classe descriptor non è limitata ad avere solo questi tre metodi, il che significa che può anche contenere qualsiasi altro attributo oltre ai metodi get, set e delete.
Capiremo ora i metodi get, set e delete più nel dettaglio, ispirandoci a questa pagina di IBM Developer:
self è l’istanza del descriptor che crei (Real Python).
object è l’istanza dell’oggetto a cui è agganciato il descriptor (Real Python).
type è il tipo dell’oggetto a cui è agganciato il descriptor (Real Python).
value è il valore assegnato all’attributo del descriptor. get(self, object, type) set(self, object, value) delete(self, object)
__get__() accede all’attributo o quando vuoi estrarre qualche informazione. Restituisce il valore dell’attributo o solleva l’eccezione AttributeError se l’attributo richiesto non è presente.
__set__() viene chiamato in un’operazione di assegnazione che imposta il valore di un attributo. Non restituisce nulla, ma può sollevare l’eccezione AttributeError.
__delete__() controlla un’operazione di eliminazione, cioè quando vuoi eliminare l’attributo da un oggetto. Non restituisce nulla.
Ora capiamo lo scopo dei Descriptor con l’aiuto di alcuni esempi!
Scopo dei descriptor
Definiamo una classe car che ha tre attributi, ovvero make, model e fuel_cap. Userai il metodo __init__() per inizializzare gli attributi della classe. Poi userai la funzione magica __str__(), che restituirà semplicemente l’output dei tre attributi che passerai alla classe durante la creazione dell’oggetto.
Nota che il metodo __str__() restituisce la rappresentazione testuale dell’oggetto. Viene chiamato quando si invoca la funzione print() o str() su un oggetto della classe.
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)Offerto da
BMW model X7with a fuel capacity of-40 ltr.Offerto da
Un attimo, c’è qualcosa che non va, vero? La capacità del serbatoio di un’auto non può mai essere negativa. Tuttavia, Python l’accetta come input senza alcun errore. Questo perché Python è un linguaggio dinamico che non supporta il type checking in modo esplicito.
Per evitare questo problema, aggiungiamo una condizione if nel metodo __init__() e verifichiamo se la capacità del serbatoio inserita è valida o meno. Se la capacità inserita è non valida, solleviamo un’eccezione ValueError.
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
if self.fuel_cap <0:raise ValueError("Fuel Capacity can never be less than zero")def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)Offerto da
----------------------------------------ValueErrorTraceback(most recent call last)<ipython-input-22-1c3d23be72f7>in<module>---->1 car2 =Car("BMW","X7",-40)2print(car2)<ipython-input-20-1e154783588d>in__init__(self, make, model, fuel_cap)5 self.fuel_cap = fuel_cap
6if self.fuel_cap <0:---->7 raise ValueError("Fuel Capacity can never be less than zero")89 def __str__(self):ValueError: Fuel Capacity can never be less than zero
Offerto da
Dall’output sopra, puoi notare che per ora tutto funziona bene, dato che il programma solleva un ValueError se la capacità del serbatoio è sotto zero.
Tuttavia, può esserci un altro problema: e se la fuel capacity inserita fosse un valore float o una stringa? Non solo la capacità del serbatoio, ma anche la make e il model dell’auto potrebbero essere valori interi. In tutti questi casi, il programma non riuscirà a sollevare un’eccezione.
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
if self.fuel_cap <0:raise ValueError("Fuel Capacity can never be less than zero")def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)Offerto da
-40 model X7with a fuel capacity of40 ltr.Offerto da
Per gestire anche il caso sopra, potresti pensare di aggiungere un’altra condizione if o magari usare il metodo isinstance per il type checking.
Questa volta usiamo la funzione built-in isinstance per gestire l’errore.
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
ifisinstance(self.make,str):print(self.make)else:raise ValueError("Make of the car can never be an integer")if self.fuel_cap <0:raise ValueError("Fuel Capacity can never be less than zero")def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)Offerto da
----------------------------------------ValueErrorTraceback(most recent call last)<ipython-input-34-75b08cba454f>in<module>---->1 car2 =Car(-40,"X7",40)2print(car2)<ipython-input-31-175690bf3b98>in__init__(self, make, model, fuel_cap)7print(self.make)8else:---->9 raise ValueError("Make of the car can never be an integer")1011if self.fuel_cap <0:ValueError: Make of the car can never be an integer
Offerto da
Ottimo! Sei riuscito a gestire anche questo errore.
Tuttavia, cosa succede se in seguito vuoi cambiare esplicitamente l’attributo della capacità del serbatoio a meno 40? In questo caso non funzionerà, perché il type checking verrà eseguito solo una volta nel metodo __init__(). Come saprai, il metodo __init__() è un costruttore e viene chiamato una sola volta quando crei un oggetto della classe. Quindi, il controllo personalizzato del tipo fallirà in seguito.
Capciamolo con un esempio.
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
ifisinstance(self.make,str):print(self.make)else:raise ValueError("Make of the car can never be an integer")if self.fuel_cap <0:raise ValueError("Fuel Capacity can never be less than zero")def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)Offerto da
-40 model X7with a fuel capacity of40 ltr.Offerto da
Ed ecco fatto! Sei riuscito ad aggirare il controllo del tipo.
Ora pensa a questo: e se avessi molti altri attributi dell’auto, come consumi, prezzo, accessori, ecc., che richiedono anch’essi il type checking, e volessi anche funzionalità per cui alcuni di questi attributi abbiano solo accesso in lettura? Non sarebbe fastidioso?
Ebbene, per affrontare tutti i problemi sopra, Python ha i Descriptor che vengono in tuo aiuto!
Come hai imparato sopra, qualsiasi classe che implementa i metodi magici __get__(), __set()__ o __delete()__ per un oggetto del protocollo dei descriptor è detta Descriptor. Ti danno anche un controllo aggiuntivo su come dovrebbe funzionare un attributo, per esempio se dovrebbe avere accesso in lettura o scrittura.
Ora estendiamo l’esempio sopra aggiungendo i metodi dei Descriptor di Python.
classDescriptor:def__init__(self):
self.__fuel_cap =0def__get__(self, instance, owner):return self.__fuel_cap
def__set__(self, instance, value):ifisinstance(value,int):print(value)else:raise TypeError("Fuel Capacity can only be an integer")if value <0:raise ValueError("Fuel Capacity can never be less than zero")
self.__fuel_cap = value
def__delete__(self, instance):del self.__fuel_cap
Offerto da
classCar:
fuel_cap = Descriptor()def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)Offerto da
40BMW model X7with a fuel capacity of40 ltr.Offerto da
Niente panico se la classe Descriptor ti sembra oscura. Scomponiamola in piccoli pezzi e capiamo cosa fa essenzialmente ogni metodo.
Il metodo __init__() della classe Descriptor ha una variabile locale __fuel_cap impostata a zero. Il dunder, ovvero il doppio underscore all’inizio, significa che la variabile è privata. Avere un dunder all’inizio serve solo a distinguere l’attributo di capacità del serbatoio della classe Descriptor da quello della classe Car.
Come ormai sai, il metodo __get__() viene usato per recuperare gli attributi e restituisce la variabile della capacità del serbatoio. Accetta tre argomenti: l’oggetto descriptor, l’istanza della classe che contiene l’istanza del descriptor, cioè car2, e infine il proprietario, che è la classe a cui appartiene l’istanza, cioè la classe Car. In questo metodo, restituisci semplicemente l’attributo value, cioè fuel_cap, il cui valore è impostato nel metodo __set__().
Il metodo __set__() viene invocato quando si imposta il valore dell’attributo e, a differenza di __get__(), non restituisce nulla. Ha due argomenti oltre all’oggetto descriptor stesso, cioè l’istanza (come nel metodo __get__()) e l’argomento value, che è il valore che assegni all’attributo. In questo metodo verifichi se il valore che vuoi assegnare all’attributo fuel_cap è un intero o meno. In caso contrario, sollevi un’eccezione TypeError. Poi, nello stesso metodo, verifichi anche se il valore è minore di zero; se lo è, sollevi un’altra eccezione, questa volta un ValueError. Dopo i controlli, aggiorni l’attributo fuel_cap al valore passato.
Infine, il metodo __delete__(), che viene chiamato quando l’attributo viene eliminato da un oggetto e, come __set__(), non restituisce nulla.
La classe Car rimane la stessa di prima. Tuttavia, l’unico cambiamento che fai è aggiungere l’istanza fuel_cap della classe Descriptor(). Nota che, come detto in precedenza, l’istanza della classe descriptor deve essere aggiunta a una classe come attributo di classe e non come attributo di istanza.
Non appena imposti la variabile locale fuel_cap nel metodo __init__() all’istanza fuel_cap, viene invocato il metodo __set__() della classe Descriptor.
Ora cambiamo la capacità del serbatoio a un valore negativo e vediamo se il programma solleva un’eccezione ValueError oppure no.
-40----------------------------------------ValueErrorTraceback(most recent call last)<ipython-input-115-1c3d23be72f7>in<module>---->1 car2 =Car("BMW","X7",-40)2print(car2)<ipython-input-107-3e1f3e97d3c7>in__init__(self, make, model, fuel_cap)4 self.make = make
5 self.model = model
---->6 self.fuel_cap = fuel_cap
78 def __str__(self):<ipython-input-106-0b252695aeed>in__set__(self, instance, value)1112if value <0:--->13 raise ValueError("Fuel Capacity can never be less than zero")1415 self.__fuel_cap = value
ValueError: Fuel Capacity can never be less than zero
Offerto da
Se ricordi qui, il type checking non aveva avuto successo quando avevi cambiato successivamente il valore dell’attributo a un numero negativo, poiché il controllo era applicato una sola volta nel metodo __init__(). Aggiorniamo ora il valore di fuel_cap a una stringa e vediamo se produce un errore.
-1----------------------------------------ValueErrorTraceback(most recent call last)<ipython-input-120-dea9dbe96ebe>in<module>---->1 car2.fuel_cap =-1<ipython-input-106-0b252695aeed>in__set__(self, instance, value)1112if value <0:--->13 raise ValueError("Fuel Capacity can never be less than zero")1415 self.__fuel_cap = value
ValueError: Fuel Capacity can never be less than zero
Offerto da
----------------------------------------TypeErrorTraceback(most recent call last)<ipython-input-121-0b316a9872c6>in<module>---->1 car2.fuel_cap ="BMW"<ipython-input-106-0b252695aeed>in__set__(self, instance, value)8print(value)9else:--->10 raise TypeError("Fuel Capacity can only be an integer")1112if value <0:TypeError: Fuel Capacity can only be an integer
Offerto da
Perfetto! Come vedi, funziona anche quando aggiorni successivamente l’attributo della capacità del serbatoio.
C’è però un piccolo problema nei Descriptor: quando crei una nuova istanza o una seconda istanza della classe, il valore dell’istanza precedente viene sovrascritto. Il motivo è che i Descriptor sono legati alla classe e non all’istanza.
Capimolo con l’esempio qui sotto.
car3 = Car("BMW","X7",48)#created a new instance 'car3' with different valuesOfferto da
BMW model X7with a fuel capacity of48 ltr.Offerto da
Conclusione
Congratulazioni per aver terminato il tutorial.
Questo tutorial è rivolto a chi si sente a proprio agio con Python e aspira a padroneggiarlo a un livello avanzato. Come buon esercizio, potresti voler capire come risolvere il problema della sovrascrittura tra istanze discusso oggi.
Per qualsiasi domanda relativa a questo tutorial, sentiti libero di scriverla nei commenti qui sotto.
Se vuoi imparare di più su Python, segui il percorso di competenze completo di DataCamp Python Programming.
Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
I descriptor di Python, o più in generale i descriptor, ti offrono una potente tecnica per scrivere codice riutilizzabile che può essere condiviso tra classi. Possono sembrare simili al concetto di ereditarietà, ma tecnicamente non lo sono. Sono un modo generico per intercettare l’accesso agli attributi. I descriptor sono il meccanismo alla base di property, metodi statici, metodi di classe, super, ecc.
I descriptor sono stati aggiunti in Python dalla versione 2.2 e da allora sono considerati strumenti “magici” che hanno dato un nuovo stile alle classi tradizionali. Sono classi che ti permettono di gestire proprietà in un’altra classe. Nello specifico, implementano l’interfaccia per i metodi
__get__(),__set__()e__delete__(), il che li rende interessanti per molti motivi. Per esempio, un class decorator e un property decorator che avrai già visto in Python.In parole povere, una classe che implementa i metodi
__get__(),__set()__o__delete()__per un oggetto è detta "Descriptor". Citando direttamente la documentazione ufficiale di Python, un descriptor è un attributo di oggetto conbinding behavior, il cui accesso è stato ridefinito dai metodi del protocollo dei descriptor. Questi metodi sono__get__(),__set__()e__delete__()(Fonte).Il
binding behaviorrispetto ai descriptor significa vincolare il modo in cui un valore può essere impostato, interrogato (get) o eliminato per una determinata variabile, oggetto o insieme di dati. Questa interazione completa èvincolataa quel pezzo di dato perché si applica solo ai dati su cui l’hai impostata, legandola quindi solo a quella specifica parte di dati.I descriptor possono essere ulteriormente classificati in data e non-data descriptor. Se il descriptor che scrivi ha solo il metodo
__get__(), allora è un non-data descriptor; invece, un’implementazione che coinvolge i metodi__set__()e__delete__()è chiamata data descriptor. I non-data descriptor sono solo leggibili, mentre i data descriptor sono sia leggibili che scrivibili.È importante notare che i descriptor sono assegnati a una classe, non all’istanza di una classe. Modificare la classe sovrascrive o elimina il descriptor stesso, invece di attivarne il codice (IBM Developer).
Infine, la classe descriptor non è limitata ad avere solo questi tre metodi, il che significa che può anche contenere qualsiasi altro attributo oltre ai metodi
get,setedelete.Capiremo ora i metodi
get,setedeletepiù nel dettaglio, ispirandoci a questa pagina di IBM Developer:selfè l’istanza del descriptor che crei (Real Python).objectè l’istanza dell’oggetto a cui è agganciato il descriptor (Real Python).typeè il tipo dell’oggetto a cui è agganciato il descriptor (Real Python).valueè il valore assegnato all’attributo del descriptor. get(self, object, type) set(self, object, value) delete(self, object)__get__()accede all’attributo o quando vuoi estrarre qualche informazione. Restituisce il valore dell’attributo o solleva l’eccezione AttributeError se l’attributo richiesto non è presente.__set__()viene chiamato in un’operazione di assegnazione che imposta il valore di un attributo. Non restituisce nulla, ma può sollevare l’eccezione AttributeError.__delete__()controlla un’operazione di eliminazione, cioè quando vuoi eliminare l’attributo da un oggetto. Non restituisce nulla.Ora capiamo lo scopo dei Descriptor con l’aiuto di alcuni esempi!
Scopo dei descriptor
Definiamo una classe
carche ha tre attributi, ovveromake,modelefuel_cap. Userai il metodo__init__()per inizializzare gli attributi della classe. Poi userai la funzione magica__str__(), che restituirà semplicemente l’output dei tre attributi che passerai alla classe durante la creazione dell’oggetto.Nota che il metodo
__str__()restituisce la rappresentazione testuale dell’oggetto. Viene chiamato quando si invoca la funzioneprint()ostr()su un oggetto della classe.Come puoi vedere dall’output sopra, tutto sembra a posto!
Ora cambiamo la capacità del serbatoio dell’auto a meno 40.
Un attimo, c’è qualcosa che non va, vero? La capacità del serbatoio di un’auto non può mai essere negativa. Tuttavia, Python l’accetta come input senza alcun errore. Questo perché Python è un linguaggio dinamico che non supporta il type checking in modo esplicito.
Per evitare questo problema, aggiungiamo una condizione
ifnel metodo__init__()e verifichiamo se la capacità del serbatoio inserita è valida o meno. Se la capacità inserita è non valida, solleviamo un’eccezione ValueError.Dall’output sopra, puoi notare che per ora tutto funziona bene, dato che il programma solleva un
ValueErrorse la capacità del serbatoio è sotto zero.Tuttavia, può esserci un altro problema: e se la
fuel capacityinserita fosse un valore float o una stringa? Non solo la capacità del serbatoio, ma anche lamakee ilmodeldell’auto potrebbero essere valori interi. In tutti questi casi, il programma non riuscirà a sollevare un’eccezione.Per gestire anche il caso sopra, potresti pensare di aggiungere un’altra condizione
ifo magari usare il metodoisinstanceper il type checking.Questa volta usiamo la funzione built-in
isinstanceper gestire l’errore.Ottimo! Sei riuscito a gestire anche questo errore.
Tuttavia, cosa succede se in seguito vuoi cambiare esplicitamente l’attributo della capacità del serbatoio a meno 40? In questo caso non funzionerà, perché il type checking verrà eseguito solo una volta nel metodo
__init__(). Come saprai, il metodo__init__()è un costruttore e viene chiamato una sola volta quando crei un oggetto della classe. Quindi, il controllo personalizzato del tipo fallirà in seguito.Capciamolo con un esempio.
Ed ecco fatto! Sei riuscito ad aggirare il controllo del tipo.
Ora pensa a questo: e se avessi molti altri attributi dell’auto, come consumi, prezzo, accessori, ecc., che richiedono anch’essi il type checking, e volessi anche funzionalità per cui alcuni di questi attributi abbiano solo accesso in
lettura? Non sarebbe fastidioso?Ebbene, per affrontare tutti i problemi sopra, Python ha i Descriptor che vengono in tuo aiuto!
Come hai imparato sopra, qualsiasi classe che implementa i metodi magici
__get__(),__set()__o__delete()__per un oggetto del protocollo dei descriptor è detta Descriptor. Ti danno anche un controllo aggiuntivo su come dovrebbe funzionare un attributo, per esempio se dovrebbe avere accesso in lettura o scrittura.Ora estendiamo l’esempio sopra aggiungendo i metodi dei Descriptor di Python.
Niente panico se la classe Descriptor ti sembra oscura. Scomponiamola in piccoli pezzi e capiamo cosa fa essenzialmente ogni metodo.
__init__()della classe Descriptor ha una variabile locale__fuel_capimpostata a zero. Ildunder, ovvero il doppio underscore all’inizio, significa che la variabile è privata. Avere un dunder all’inizio serve solo a distinguere l’attributo di capacità del serbatoio della classe Descriptor da quello della classe Car.__get__()viene usato per recuperare gli attributi e restituisce la variabile della capacità del serbatoio. Accetta tre argomenti: l’oggetto descriptor, l’istanza della classe che contiene l’istanza del descriptor, cioècar2, e infine il proprietario, che è la classe a cui appartiene l’istanza, cioè la classeCar. In questo metodo, restituisci semplicemente l’attributo value, cioèfuel_cap, il cui valore è impostato nel metodo__set__().__set__()viene invocato quando si imposta il valore dell’attributo e, a differenza di__get__(), non restituisce nulla. Ha due argomenti oltre all’oggetto descriptor stesso, cioè l’istanza (come nel metodo__get__()) e l’argomento value, che è il valore che assegni all’attributo. In questo metodo verifichi se il valore che vuoi assegnare all’attributofuel_capè un intero o meno. In caso contrario, sollevi un’eccezioneTypeError. Poi, nello stesso metodo, verifichi anche se il valore è minore di zero; se lo è, sollevi un’altra eccezione, questa volta unValueError. Dopo i controlli, aggiorni l’attributofuel_capal valore passato.__delete__(), che viene chiamato quando l’attributo viene eliminato da un oggetto e, come__set__(), non restituisce nulla.La classe
Carrimane la stessa di prima. Tuttavia, l’unico cambiamento che fai è aggiungere l’istanzafuel_capdella classeDescriptor(). Nota che, come detto in precedenza, l’istanza della classe descriptor deve essere aggiunta a una classe come attributo di classe e non come attributo di istanza.Non appena imposti la variabile locale
fuel_capnel metodo__init__()all’istanzafuel_cap, viene invocato il metodo__set__()della classe Descriptor.Ora cambiamo la capacità del serbatoio a un valore negativo e vediamo se il programma solleva un’eccezione ValueError oppure no.
Se ricordi qui, il type checking non aveva avuto successo quando avevi cambiato successivamente il valore dell’attributo a un numero negativo, poiché il controllo era applicato una sola volta nel metodo
__init__(). Aggiorniamo ora il valore difuel_capa una stringa e vediamo se produce un errore.Perfetto! Come vedi, funziona anche quando aggiorni successivamente l’attributo della capacità del serbatoio.
C’è però un piccolo problema nei Descriptor: quando crei una nuova istanza o una seconda istanza della classe, il valore dell’istanza precedente viene sovrascritto. Il motivo è che i Descriptor sono legati alla classe e non all’istanza.
Capimolo con l’esempio qui sotto.
Quando stampi l’istanza
car2, noterai che i valori saranno stati sovrascritti dacar3.Conclusione
Congratulazioni per aver terminato il tutorial.
Questo tutorial è rivolto a chi si sente a proprio agio con Python e aspira a padroneggiarlo a un livello avanzato. Come buon esercizio, potresti voler capire come risolvere il problema della sovrascrittura tra istanze discusso oggi.
Per qualsiasi domanda relativa a questo tutorial, sentiti libero di scriverla nei commenti qui sotto.
Se vuoi imparare di più su Python, segui il percorso di competenze completo di DataCamp Python Programming.
Dai un’occhiata al nostro Tutorial sulle funzioni in Python.