Python @property decorateur
Python @property decorateur
In deze tutorial leer je over Python @property decorateur; een pythonische manier om getters en setters te gebruiken in objectgeoriënteerd programmeren.
Python-programmering biedt ons een ingebouwde @property
decorateur die het gebruik van getter en setters veel gemakkelijker maakt in objectgeoriënteerd programmeren.
Voordat we ingaan op details over wat @property
decorateur is, laten we eerst een intuïtie bouwen over waarom het in de eerste plaats nodig zou zijn.
Klasse zonder getters en setters
Laten we aannemen dat we besluiten een klasse te maken die de temperatuur opslaat in graden Celsius. Het zou ook een methode implementeren om de temperatuur om te zetten in graden Fahrenheit. Een manier om dit te doen is als volgt:
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
We kunnen objecten van deze klasse maken en de temperature
. manipuleren attribuut zoals we willen:
# 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())
Uitvoer
37 98.60000000000001
De extra decimalen bij het converteren naar Fahrenheit zijn te wijten aan de rekenfout met drijvende komma. Ga voor meer informatie naar Python Floating Point Rekenfout.
Telkens wanneer we een objectattribuut toewijzen of ophalen, zoals temperature
zoals hierboven weergegeven, zoekt Python het in de ingebouwde __dict__
. van het object woordenboekkenmerk.
>>> human.__dict__
{'temperature': 37}
Daarom man.temperature
intern wordt man.__dict__['temperature']
.
getters en setters gebruiken
Stel dat we de bruikbaarheid van de Celsius . willen uitbreiden hierboven gedefinieerde klasse. We weten dat de temperatuur van elk object niet lager kan zijn dan -273,15 graden Celsius (absoluut nulpunt in de thermodynamica)
Laten we onze code bijwerken om deze waardebeperking te implementeren.
Een voor de hand liggende oplossing voor de bovenstaande beperking is het verbergen van het attribuut temperature
(maak het privé) en definieer nieuwe getter- en setter-methoden om het te manipuleren. Dit kan als volgt worden gedaan:
# 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
Zoals we kunnen zien, introduceert de bovenstaande methode twee nieuwe get_temperature()
en set_temperature()
methoden.
Verder, temperature
werd vervangen door _temperature
. Een onderstrepingsteken _
aan het begin wordt gebruikt om privévariabelen in Python aan te duiden.
Laten we nu deze implementatie gebruiken:
# 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())
Uitvoer
37 98.60000000000001 Traceback (most recent call last): File "<string>", line 30, in <module> File "<string>", line 16, in set_temperature ValueError: Temperature below -273.15 is not possible.
Deze update heeft de nieuwe beperking succesvol geïmplementeerd. We mogen de temperatuur niet meer onder de -273,15 graden Celsius zetten.
Opmerking :De privévariabelen bestaan niet echt in Python. Er zijn gewoon normen waaraan moet worden voldaan. De taal zelf kent geen beperkingen.
>>> human._temperature = -300
>>> human.get_temperature()
-300
Het grotere probleem met de bovenstaande update is echter dat alle programma's die onze vorige klasse hebben geïmplementeerd hun code moeten wijzigen van obj.temperature
tot obj.get_temperature()
en alle uitdrukkingen zoals obj.temperature = val
tot obj.set_temperature(val)
.
Deze refactoring kan problemen veroorzaken bij het omgaan met honderdduizenden regels codes.
Al met al was onze nieuwe update niet achterwaarts compatibel. Dit is waar @property
komt te hulp.
De eigenschapsklasse
Een pythonische manier om met het bovenstaande probleem om te gaan, is door de property
. te gebruiken klas. Hier is hoe we onze code kunnen updaten:
# 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)
We hebben een print()
. toegevoegd functie binnen get_temperature()
en set_temperature()
om duidelijk te zien dat ze worden uitgevoerd.
De laatste regel van de code maakt een eigenschapsobject temperature
. Simpel gezegd, eigendom voegt een code toe (get_temperature
en set_temperature
) naar de lidattribuuttoegangen (temperature
).
Laten we deze updatecode gebruiken:
# 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
Uitvoer
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "<string>", line 31, in <module> File "<string>", line 18, in set_temperature ValueError: Temperature below -273 is not possible
Zoals we kunnen zien, kan elke code die de waarde van temperature
belt automatisch get_temperature()
in plaats van een woordenboek (__dict__) look-up. Evenzo, elke code die een waarde toekent aan temperature
belt automatisch set_temperature()
.
We kunnen hierboven zelfs zien dat set_temperature()
werd aangeroepen, zelfs toen we een object maakten.
>>> human = Celsius(37)
Setting value...
Kun je raden waarom?
De reden is dat wanneer een object wordt gemaakt, de __init__()
methode wordt aangeroepen. Deze methode heeft de regel self.temperature = temperature
. Deze uitdrukking roept automatisch set_temperature()
. aan .
Evenzo kan elke toegang zoals c.temperature
belt automatisch get_temperature()
. Dit is wat eigendom doet. Hier zijn nog een paar voorbeelden.
>>> human.temperature
Getting value
37
>>> human.temperature = 37
Setting value
>>> c.to_fahrenheit()
Getting value
98.60000000000001
Door property
. te gebruiken , kunnen we zien dat er geen wijziging nodig is bij de implementatie van de waardebeperking. Onze implementatie is dus achterwaarts compatibel.
Opmerking :De werkelijke temperatuurwaarde wordt opgeslagen in de privé _temperature
variabel. De temperature
attribuut is een eigenschapsobject dat een interface biedt voor deze privévariabele.
De @property Decorateur
In Python, property()
is een ingebouwde functie die een property
. maakt en retourneert object. De syntaxis van deze functie is:
property(fget=None, fset=None, fdel=None, doc=None)
waar,
fget
is functie om de waarde van het attribuut te krijgenfset
is functie om de waarde van het attribuut in te stellenfdel
is functie om het attribuut te verwijderendoc
is een string (zoals een opmerking)
Zoals uit de implementatie blijkt, zijn deze functieargumenten optioneel. Een eigenschapsobject kan dus eenvoudig als volgt worden aangemaakt.
>>> property()
<property object at 0x0000000003239B38>
Een eigenschapsobject heeft drie methoden, getter()
, setter()
, en deleter()
om fget
op te geven , fset
en fdel
op een later moment. Dit betekent de regel:
temperature = property(get_temperature,set_temperature)
kan worden onderverdeeld als:
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)
Deze twee stukjes codes zijn equivalent.
Programmeurs die bekend zijn met Python Decorators kunnen herkennen dat de bovenstaande constructie kan worden geïmplementeerd als decorateurs.
We kunnen de namen get_temperature
zelfs niet definiëren en set_temperature
omdat ze niet nodig zijn en de klassenaamruimte vervuilen.
Hiervoor hergebruiken we de temperature
naam bij het definiëren van onze getter- en setterfuncties. Laten we eens kijken hoe we dit als decorateur kunnen implementeren:
# 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)
Uitvoer
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "", line 29, in File " ", line 4, in __init__ File " ", line 18, in temperature ValueError: Temperature below -273 is not possible
De bovenstaande implementatie is eenvoudig en efficiënt. Het is de aanbevolen manier om property
. te gebruiken .
Python