I denne vejledning lærer du om Python @property decorator; en pythonisk måde at bruge getters og settere i objektorienteret programmering.
Python-programmering giver os en indbygget @property
dekoratør, der gør brugen af getter og setter meget lettere i Objektorienteret programmering.
Før vi går i detaljer om, hvad @property
dekoratør er, lad os først bygge en intuition på, hvorfor det i første omgang ville være nødvendigt.
Klasse Uden Getters og Setters
Lad os antage, at vi beslutter at lave en klasse, der lagrer temperaturen i grader Celsius. Det ville også implementere en metode til at konvertere temperaturen til grader Fahrenheit. En måde at gøre dette på er som følger:
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32
Vi kan lave objekter ud af denne klasse og manipulere temperature
attributten, som vi ønsker:
# 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())
Produktion
37 98,60000000000001
De ekstra decimaler ved konvertering til Fahrenheit skyldes den flydende aritmetiske fejl. Hvis du vil vide mere, skal du besøge Python Floating Point Arithmetic Error.
Når vi tildeler eller henter en objektattribut temperature
som vist ovenfor, søger Python den i objektets indbyggede __dict__
ordbogattribut.
>>> human.__dict__ ('temperature': 37)
Derfor bliver man.temperature
internt man.__dict__('temperature')
.
Brug af Getters og Setters
Antag, at vi vil udvide anvendeligheden af Celsius-klassen defineret ovenfor. Vi ved, at temperaturen på ethvert objekt ikke kan nå under -273,15 grader Celsius (Absolut nul inden for termodynamik)
Lad os opdatere vores kode for at implementere denne værdibegrænsning.
En åbenbar løsning på ovenstående begrænsning vil være at skjule attributten temperature
(gøre den privat) og definere nye getter- og settermetoder til at manipulere den. Dette kan gøres som følger:
# 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
Som vi kan se, introducerer ovenstående metode to nye get_temperature()
og set_temperature()
metoder.
Desuden temperature
blev erstattet med _temperature
. En understregning _
i begyndelsen bruges til at betegne private variabler i Python.
Lad os nu bruge denne implementering:
# 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())
Produktion
37 98.60000000000001 Traceback (seneste opkald sidst): Fil "", linje 30, i fil "", linje 16, i set_temperature ValueError: Temperatur under -273.15 er ikke mulig.
Denne opdatering implementerede den nye begrænsning med succes. Vi har ikke længere lov til at indstille temperaturen til under -273,15 grader Celsius.
Bemærk : De private variabler findes faktisk ikke i Python. Der er simpelthen normer, der skal følges. Selve sproget anvender ingen begrænsninger.
>>> human._temperature = -300 >>> human.get_temperature() -300
Men det større problem med ovenstående opdatering er, at alle de programmer, der gennemførte vores tidligere klasse nødt til at ændre deres kode fra obj.temperature
til obj.get_temperature()
og alle udtryk som obj.temperature = val
til obj.set_temperature(val)
.
Denne refactoring kan forårsage problemer i forbindelse med hundreder af tusinder af linjer med koder.
Alt i alt var vores nye opdatering ikke bagudkompatibel. Det er her, der @property
kommer for at redde.
Ejendomsklassen
En pythonisk måde at håndtere ovenstående problem er at bruge property
klassen. Sådan opdaterer vi vores kode:
# 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)
Vi tilføjede en print()
funktion indeni get_temperature()
og set_temperature()
for tydeligt at observere, at de udføres.
Den sidste linje i koden udgør et ejendomsobjekt temperature
. Kort sagt, egenskab vedhæfter nogle kode ( get_temperature
og set_temperature
) til medlemsattributadgangene ( temperature
).
Lad os bruge denne opdateringskode:
# 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
Produktion
Indstillingsværdi … Få værdi … 37 Få værdi … 98.60000000000001 Indstillingsværdi … Traceback (seneste opkald sidst): Fil "", linje 31, i fil "", linje 18, i set_temperature ValueError: Temperatur under -273 er ikke mulig
Som vi kan se, vil enhver kode, der henter værdien af temperature
, automatisk kalde op i get_temperature()
stedet for en ordbog (__dict__) -opslag. Tilsvarende temperature
kalder enhver kode, der tildeler en værdi , automatisk set_temperature()
.
Vi kan endda se ovenfor, der set_temperature()
blev kaldt, selv da vi oprettede et objekt.
>>> human = Celsius(37) Setting value…
Kan du gætte hvorfor?
Årsagen er, at når et objekt oprettes, __init__()
kaldes metoden. Denne metode har linjen self.temperature = temperature
. Dette udtryk kalder automatisk set_temperature()
.
Tilsvarende enhver adgang som c.temperature
automatisk opkald get_temperature()
. Dette er hvad ejendom gør. Her er et par flere eksempler.
>>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001
Ved at bruge property
kan vi se, at der ikke kræves nogen ændringer i implementeringen af værdibegrænsningen. Således er vores implementering bagudkompatibel.
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)
Disse to stykker koder er ækvivalente.
Programmører, der er fortrolige med Python Decorators, kan genkende, at ovenstående konstruktion kan implementeres som dekoratører.
Vi kan endda ikke definere navnene, get_temperature
og set_temperature
da de er unødvendige og forurener klassens navneområde.
Til dette genbruger vi temperature
navnet, mens vi definerer vores getter- og setterfunktioner. Lad os se på, hvordan man implementerer dette som dekoratør:
# 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)
Produktion
Indstillingsværdi … Få værdi … 37 Få værdi … 98.60000000000001 Indstillingsværdi … Traceback (seneste opkald sidst): File "", line 29, in File "", line 4, in __init__ File "", line 18, in temperature ValueError: Temperatur under -273 er ikke mulig
Ovenstående implementering er enkel og effektiv. Det er den anbefalede måde at bruge property
.