In deze tutorial leer je over Python @property decorator; een pythonische manier om getters en setters te gebruiken bij objectgeoriënteerd programmeren.
Python-programmering biedt ons een ingebouwde @property
decorateur die het gebruik van getter en setters veel gemakkelijker maakt in Object-Oriented Programming.
Voordat we ingaan op wat @property
decorateur is, laten we eerst een intuïtie opbouwen 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 rekenen naar 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 uit deze klasse maken en het temperature
attribuut naar wens manipuleren :
# 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 Arithmetic Error.
Telkens wanneer we een objectkenmerk toewijzen of ophalen zoals temperature
hierboven weergegeven, zoekt Python het in het ingebouwde __dict__
woordenboekkenmerk van het object .
>>> human.__dict__ ('temperature': 37)
Daarom wordt man.temperature
intern man.__dict__('temperature')
.
Getters en Setters gebruiken
Stel dat we de bruikbaarheid van de hierboven gedefinieerde Celsius-klasse willen uitbreiden. We weten dat de temperatuur van een object niet onder de -273,15 graden Celsius kan komen (het absolute 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 om het attribuut te verbergen temperature
(privé te maken) en nieuwe getter- en setter-methoden te definiëren 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, de bovenstaande methode introduceert 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 (meest recente oproep laatste): Bestand "", regel 30, in Bestand "", regel 16, in set_temperature ValueError: Temperatuur onder -273.15 is niet mogelijk.
Deze update heeft de nieuwe beperking geïmplementeerd. We mogen de temperatuur niet meer onder de -273,15 graden Celsius instellen.
Opmerking : de privévariabelen bestaan niet echt in Python. Er zijn gewoon normen die moeten worden gevolgd. 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
naar obj.get_temperature()
en alle uitdrukkingen willen obj.temperature = val
dat obj.set_temperature(val)
.
Deze herstructurering kan problemen veroorzaken bij het omgaan met honderdduizenden regels codes.
Al met al was onze nieuwe update niet achterwaarts compatibel. Dit is waar @property
te hulp komt.
De eigenschap Class
Een pythonische manier om met het bovenstaande probleem om te gaan, is door de property
klasse te gebruiken . 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 voegden een print()
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, eigenschap voegt een code ( get_temperature
en set_temperature
) toe aan het lidattribuut accesses ( 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
Waarde instellen … Waarde ophalen … 37 Waarde ophalen … 98.60000000000001 Waarde instellen … Traceback (laatste oproep laatste): Bestand "", regel 31, in Bestand "", regel 18, in set_temperature ValueError: Temperatuur onder -273 is niet mogelijk
As we can see, any code that retrieves the value of temperature
will automatically call get_temperature()
instead of a dictionary (__dict__) look-up. Similarly, any code that assigns a value to temperature
will automatically call set_temperature()
.
We can even see above that set_temperature()
was called even when we created an object.
>>> human = Celsius(37) Setting value…
Can you guess why?
The reason is that when an object is created, the __init__()
method gets called. This method has the line self.temperature = temperature
. This expression automatically calls set_temperature()
.
Similarly, any access like c.temperature
automatically calls get_temperature()
. This is what property does. Here are a few more examples.
>>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001
By using property
, we can see that no modification is required in the implementation of the value constraint. Thus, our implementation is backward compatible.
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 attributefset
is function to set value of the attributefdel
is function to delete the attributedoc
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)
Deze twee stukjes codes zijn equivalent.
Programmeurs die bekend zijn met Python Decorators kunnen erkennen dat het bovenstaande construct kan worden geïmplementeerd als decorateurs.
We kunnen zelfs de namen niet definiëren get_temperature
en set_temperature
omdat ze niet nodig zijn en de naamruimte van de klassen vervuilen.
Hiervoor gebruiken we de temperature
naam opnieuw terwijl we onze getter- en setter-functies definiëren. 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
Waarde instellen … Waarde ophalen … 37 Waarde ophalen … 98.60000000000001 Waarde instellen … Traceback (meest recente oproep laatste): Bestand "", regel 29, in Bestand "", regel 4, in __init__ Bestand "", regel 18, in temperatuur ValueError: Temperatuur onder -273 is niet mogelijk
De bovenstaande implementatie is eenvoudig en efficiënt. Het is de aanbevolen manier om te gebruiken property
.