Python @property: come usarlo e perché? - Programiz

In questo tutorial imparerai a conoscere Python @property decorator; un modo pitonico per usare getter e setter nella programmazione orientata agli oggetti.

La programmazione Python ci fornisce un @propertydecoratore integrato che rende molto più semplice l'uso di getter e setter nella programmazione orientata agli oggetti.

Prima di entrare nei dettagli su cosa sia il @propertydecoratore, costruiamo prima un'intuizione sul motivo per cui sarebbe necessario in primo luogo.

Classe senza Getters e Setter

Supponiamo di decidere di creare una classe che memorizzi la temperatura in gradi Celsius. Implementerebbe anche un metodo per convertire la temperatura in gradi Fahrenheit. Un modo per farlo è il seguente:

 class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32

Possiamo creare oggetti da questa classe e manipolare l' temperatureattributo come desideriamo:

 # Basic method of setting and getting attributes in Python class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # Create a new object human = Celsius() # Set the temperature human.temperature = 37 # Get the temperature attribute print(human.temperature) # Get the to_fahrenheit method print(human.to_fahrenheit())

Produzione

 37 98.60000000000001

Le cifre decimali extra durante la conversione in Fahrenheit sono dovute all'errore aritmetico in virgola mobile. Per saperne di più, visita Python Floating Point Arithmetic Error.

Ogni volta che assegniamo o recuperiamo un attributo di oggetto temperaturecome mostrato sopra, Python lo cerca nell'attributo di __dict__dizionario incorporato nell'oggetto .

 >>> human.__dict__ ('temperature': 37)

Pertanto, man.temperatureinternamente diventa man.__dict__('temperature').

Utilizzo di Getters e Setters

Supponiamo di voler estendere l'usabilità della classe Celsius definita sopra. Sappiamo che la temperatura di qualsiasi oggetto non può raggiungere meno di -273,15 gradi Celsius (zero assoluto in termodinamica)

Aggiorniamo il nostro codice per implementare questo vincolo di valore.

Una soluzione ovvia alla restrizione di cui sopra sarà nascondere l'attributo temperature(renderlo privato) e definire nuovi metodi getter e setter per manipolarlo. Questo può essere fatto come segue:

 # Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value

Come possiamo vedere, il metodo sopra introduce due nuovi metodi get_temperature()e set_temperature().

Inoltre, è temperaturestato sostituito con _temperature. Un carattere di sottolineatura _all'inizio viene utilizzato per indicare le variabili private in Python.

Ora usiamo questa implementazione:

 # Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value # Create a new object, set_temperature() internally called by __init__ human = Celsius(37) # Get the temperature attribute via a getter print(human.get_temperature()) # Get the to_fahrenheit method, get_temperature() called by the method itself print(human.to_fahrenheit()) # new constraint implementation human.set_temperature(-300) # Get the to_fahreheit method print(human.to_fahrenheit())

Produzione

 37 98.60000000000001 Traceback (chiamata più recente per ultima): File "", riga 30, in File "", riga 16, in set_temperature ValueError: La temperatura inferiore a -273.15 non è possibile.

Questo aggiornamento ha implementato con successo la nuova restrizione. Non siamo più autorizzati a impostare la temperatura sotto i -273,15 gradi Celsius.

Nota : le variabili private in realtà non esistono in Python. Ci sono semplicemente delle norme da seguire. La lingua stessa non applica alcuna restrizione.

 >>> human._temperature = -300 >>> human.get_temperature() -300

Tuttavia, il problema più grande con l'aggiornamento di cui sopra è che tutti i programmi che hanno implementato la nostra classe precedente devono modificare il loro codice da obj.temperaturea obj.get_temperature()e tutte le espressioni come obj.temperature = vala obj.set_temperature(val).

Questo refactoring può causare problemi durante la gestione di centinaia di migliaia di righe di codici.

Tutto sommato, il nostro nuovo aggiornamento non era compatibile con le versioni precedenti. Qui è dove @propertyviene il salvataggio.

La proprietà Class

Un modo pitonico per affrontare il problema di cui sopra è usare la propertyclasse. Ecco come possiamo aggiornare il nostro codice:

 # using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value… ") return self._temperature # setter def set_temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature)

Abbiamo aggiunto una print()funzione all'interno get_temperature()e set_temperature()per osservare chiaramente che vengono eseguiti.

L'ultima riga del codice crea un oggetto proprietà temperature. In poche parole, la proprietà allega del codice ( get_temperaturee set_temperature) all'attributo membro accesses ( temperature).

Usiamo questo codice di aggiornamento:

 # using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value… ") return self._temperature # setter def set_temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature) human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) human.temperature = -300

Produzione

 Impostazione del valore … Acquisizione del valore … 37 Acquisizione del valore … 98.60000000000001 Impostazione del valore … Traceback (ultima chiamata più recente): File "", riga 31, in File "", riga 18, in set_temperature ValueError: Temperatura inferiore a -273 non è possibile

Come possiamo vedere, qualsiasi codice che recupera il valore di temperaturechiamerà automaticamente get_temperature()invece di una ricerca nel dizionario (__dict__). Allo stesso modo, qualsiasi codice che assegna un valore a temperatureverrà chiamato automaticamente set_temperature().

Possiamo anche vedere sopra che è set_temperature()stato chiamato anche quando abbiamo creato un oggetto.

 >>> human = Celsius(37) Setting value… 

Riuscite a indovinare perché?

Il motivo è che quando viene creato un oggetto, il __init__()metodo viene chiamato. Questo metodo ha la linea self.temperature = temperature. Questa espressione chiama automaticamente set_temperature().

Allo stesso modo, qualsiasi accesso come c.temperaturechiamate automaticamente get_temperature(). Questo è ciò che fa la proprietà. Ecco alcuni altri esempi.

 >>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001

Utilizzando property, possiamo vedere che non è richiesta alcuna modifica nell'implementazione del vincolo di valore. Pertanto, la nostra implementazione è compatibile con le versioni precedenti.

Note: The actual temperature value is stored in the private _temperature variable. The temperature attribute is a property object which provides an interface to this private variable.

The @property Decorator

In Python, property() is a built-in function that creates and returns a property object. The syntax of this function is:

 property(fget=None, fset=None, fdel=None, doc=None)

where,

  • fget is function to get value of the attribute
  • fset is function to set value of the attribute
  • fdel is function to delete the attribute
  • doc is a string (like a comment)

As seen from the implementation, these function arguments are optional. So, a property object can simply be created as follows.

 >>> property() 

A property object has three methods, getter(), setter(), and deleter() to specify fget, fset and fdel at a later point. This means, the line:

 temperature = property(get_temperature,set_temperature)

can be broken down as:

 # make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature)

Questi due codici sono equivalenti.

I programmatori che hanno familiarità con i decoratori Python possono riconoscere che il costrutto sopra può essere implementato come decoratori.

Non possiamo nemmeno definire i nomi get_temperaturee set_temperaturepoiché non sono necessari e inquinano lo spazio dei nomi della classe.

Per questo, riutilizziamo il temperaturenome mentre definiamo le nostre funzioni getter e setter. Diamo un'occhiata a come implementarlo come decoratore:

 # Using @property decorator class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 @property def temperature(self): print("Getting value… ") return self._temperature @temperature.setter def temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273 is not possible") self._temperature = value # create an object human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) coldest_thing = Celsius(-300)

Produzione

 Impostazione del valore … Acquisizione del valore … 37 Acquisizione del valore … 98.60000000000001 Impostazione del valore … Traceback (ultima chiamata più recente): File "", riga 29, in File "", riga 4, in __init__ File "", riga 18, in temperatura ValueError: Una temperatura inferiore a -273 non è possibile

L'implementazione di cui sopra è semplice ed efficiente. È il modo consigliato di utilizzare property.

Articoli interessanti...