Dekorator @property u Pythonu: slučajevi, prednosti i sintaksa

Upoznajte svojstva

Dobrodošli! U ovom ćete članku naučiti kako raditi s @propertydekoraterom u Pythonu.

Naučit ćeš:

  • Prednosti rada sa svojstvima u Pythonu.
  • Osnove funkcija dekoratora: što su i kako su povezane s @property.
  • Kako pomoću @property možete definirati getere, postavljače i brisače.

1️⃣ Prednosti svojstava u Pythonu

Krenimo s malo konteksta. Zašto biste koristili svojstva u Pythonu?

Svojstva se mogu smatrati "pitonskim" načinom rada s atributima jer:

  • Sintaksa koja se koristi za definiranje svojstava vrlo je kratka i čitljiva.
  • Atributima instance možete pristupiti točno kao da su javni atributi dok koristite "magiju" posrednika (getera i postavljača) za provjeru novih vrijednosti i za izbjegavanje izravnog pristupa ili izmjene podataka.
  • Korištenjem @property možete "ponovno upotrijebiti" ime svojstva kako biste izbjegli stvaranje novih imena za getere, postavljače i brisače.

Te prednosti čine svojstva zaista izvrsnim alatom koji će vam pomoći da napišete sažetiji i čitljiviji kôd. ?

2️⃣ Uvod u dekoratere

Funkcija dekoraciju je u osnovi koja dodaje nove funkcije na funkciju koja se prenosi kao argument. Korištenje funkcije dekoratera je poput dodavanja čokoladnih prskanja sladoledu? Omogućuje nam dodavanje nove funkcionalnosti postojećoj funkciji bez da je mijenjamo.

U donjem primjeru možete vidjeti kako izgleda tipična funkcija dekoratora u Pythonu:

def decorator(f): def new_function(): print("Extra Functionality") f() return new_function @decorator def initial_function(): print("Initial Functionality") initial_function()

Analizirajmo ove elemente detaljno:

  • Prvo pronalazimo funkciju dekoratora def decorator(f)(posipanje ✨) koja uzima funkciju fkao argument.
def decorator(f): def new_function(): print("Extra Functionality") f() return new_function
  • To dekoraciju funkcija ima ugnežđeni funkciju new_function. Primijetite kako fse zove unutra new_functionkako bi se postigla ista funkcionalnost dok se dodaje nova funkcionalnost prije poziva funkcije (mogli bismo dodati i novu funkciju nakon poziva funkcije).
  • Sama funkcija dekoratora vraća ugniježđenu funkciju new_function.
  • Zatim (dolje) pronalazimo funkciju koja će biti ukrašena (sladoled?) initial_function. Primijetite vrlo neobičnu sintaksu ( @decorator) iznad zaglavlja funkcije.
@decorator def initial_function(): print("Initial Functionality") initial_function()

Ako pokrenemo kod, vidjet ćemo ovaj izlaz:

Extra Functionality Initial Functionality

Primijetite kako funkcionira funkcija dekoracije, čak i ako samo zovemo initial_function(). Ovo je čarolija dodavanja @decorator?.

OteNapomena: Općenito bismo napisali @zamjenjujući naziv funkcije dekoratera nakon simbola @.

Znam da se možda pitate: kako je to povezano s @property? @Property je ugrađeni uređivač funkcije property () u Pythonu. Koristi se za davanje "posebne" funkcionalnosti određenim metodama kako bi se natjerale da djeluju kao geteri, postavljači ili brisači kada definiramo svojstva u klasi.

Sad kad ste upoznati s dekoraterima, pogledajmo stvarni scenarij upotrebe @property!

? Scenarij iz stvarnog svijeta: @property

Recimo da je ovaj tečaj dio vašeg programa. Modelirate kuću s Houseklasom (u ovom trenutku klasa ima definiran samo atribut instance cijene ):

class House: def __init__(self, price): self.price = price

Ovaj atribut instance je javan jer njegovo ime nema vodeću podvlaku. Budući da je atribut trenutno javan, vrlo je vjerojatno da ste vi i drugi programeri u vašem timu pristupili i modificirali atribut izravno u drugim dijelovima programa pomoću točkastih notacija, poput ove:

# Access value obj.price # Modify value obj.price = 40000

? Savjet: obj predstavlja varijablu koja upućuje na instancu House.

Zasad sve funkcionira sjajno, zar ne? Ali recimo da se od vas traži da ovaj atribut učinite zaštićenim (nejavnim) i potvrdite novu vrijednost prije nego što je dodijelite . Točnije, trebate provjeriti je li vrijednost pozitivan pomak. Kako biste to učinili? Da vidimo.

Promjena vašeg koda

U ovom trenutku, ako odlučite dodati getere i postavljače, vi i vaš tim vjerojatno ćete paničariti? To je zato što će svaki redak koda koji pristupa ili mijenja vrijednost atributa morati biti modificiran da pozove getera ili postavljača. U suprotnom, kôd će se slomiti ⚠️.

# Changed from obj.price obj.get_price() # Changed from obj.price = 40000 obj.set_price(40000)

Ali ... Svojstva dolaze u pomoć! Uz @property, vi i vaš tim nećete morati mijenjati nijedan od tih redaka, jer ćete moći dodavati getere i postavljače "iza kulisa", bez utjecaja na sintaksu koju ste koristili za pristup ili izmjenu atributa kada je bio javan.

Super, zar ne?  

? @property: Sintaksa i logika

Ako se odlučite za upotrebu @property, vaš će predmet izgledati kao u primjeru u nastavku:

class House: def __init__(self, price): self._price = price @property def price(self): return self._price @price.setter def price(self, new_price): if new_price > 0 and isinstance(new_price, float): self._price = new_price else: print("Please enter a valid price") @price.deleter def price(self): del self._price

Točnije, možete definirati tri metode za svojstvo:

  • Dobavljač - za pristup vrijednost atributa.
  • Seter - postaviti vrijednost atributa.
  • Deleter - brisanje instance atribut.

Cijena je sada "zaštićena"

Please note that the price attribute is now considered "protected" because we added a leading underscore to its name in self._price:

self._price = price

In Python, by convention, when you add a leading underscore to a name, you are telling other developers that it should not be accessed or modified directly outside of the class. It should only be accessed through intermediaries (getters and setters) if they are available.

? Getter

Here we have the getter method:

@property def price(self): return self._price

Notice the syntax:

  • @property - Used to indicate that we are going to define a property. Notice how this immediately improves readability because we can clearly see the purpose of this method.
  • def price(self) - The header. Notice how the getter is named exactly like the property that we are defining: price. This is the name that we will use to access and modify the attribute outside of the class. The method only takes one formal parameter, self, which is a reference to the instance.
  • return self._price - This line is exactly what you would expect in a regular getter. The value of the protected attribute is returned.

Here is an example of the use of the getter method:

>>> house = House(50000.0) # Create instance >>> house.price # Access value 50000.0

Notice how we access the price attribute as if it were a public attribute. We are not changing the syntax at all, but we are actually using the getter as an intermediary to avoid accessing the data directly.

? Setter

Now we have the setter method:

@price.setter def price(self, new_price): if new_price > 0 and isinstance(new_price, float): self._price = new_price else: print("Please enter a valid price")

Notice the syntax:

  • @price.setter - Used to indicate that this is the setter method for the price property. Notice that we are not using @property.setter, we are using @price.setter. The name of the property is included before .setter.
  • def price(self, new_price): - The header and the list of parameters. Notice how the name of the property is used as the name of the setter. We also have a second formal parameter (new_price), which is the new value that will be assigned to the price attribute (if it is valid).
  • Finally, we have the body of the setter where we validate the argument to check if it is a positive float and then, if the argument is valid, we update the value of the attribute. If the value is not valid, a descriptive message is printed. You can choose how to handle invalid values according the needs of your program.

This is an example of the use of the setter method with @property:

>>> house = House(50000.0) # Create instance >>> house.price = 45000.0 # Update value >>> house.price # Access value 45000.0

Notice how we are not changing the syntax, but now we are using an intermediary (the setter) to validate the argument before assigning it. The new value (45000.0) is passed as an argument to the setter :

house.price = 45000.0

If we try to assign an invalid value, we see the descriptive message. We can also check that the value was not updated:

>>> house = House(50000.0) >>> house.price = -50 Please enter a valid price >>> house.price 50000.0

? Tip: This proves that the setter method is working as an intermediary. It is being called "behind the scenes" when we try to update the value, so the descriptive message is displayed when the value is not valid.

? Deleter

Finally, we have the deleter method:

@price.deleter def price(self): del self._price

Notice the syntax:

  • @price.deleter - Used to indicate that this is the deleter method for the price property. Notice that this line is very similar to @price.setter, but now we are defining the deleter method, so we write @price.deleter.
  • def price(self): - The header. This method only has one formal parameter defined, self.
  • del self._price - The body, where we delete the instance attribute.

? Tip: Notice that the name of the property is "reused" for all three methods.

This is an example of the use of the deleter method with @property:

# Create instance >>> house = House(50000.0) # The instance attribute exists >>> house.price 50000.0 # Delete the instance attribute >>> del house.price # The instance attribute doesn't exist >>> house.price Traceback (most recent call last): File "", line 1, in  house.price File "", line 8, in price return self._price AttributeError: 'House' object has no attribute '_price'

The instance attribute was deleted successfully ?. When we try to access it again, an error is thrown because the attribute doesn't exist anymore.

? Some final Tips

You don't necessarily have to define all three methods for every property. You can define read-only properties by only including a getter method. You could also choose to define a getter and setter without a deleter.

If you think that an attribute should only be set when the instance is created or that it should only be modified internally within the class, you can omit the setter.

You can choose which methods to include depending on the context that you are working with.

? In Summary

  • You can define properties with the @property syntax, which is more compact and readable.
  • @property can be considered the "pythonic" way of defining getters, setters, and deleters.
  • By defining properties, you can change the internal implementation of a class without affecting the program, so you can add getters, setters, and deleters that act as intermediaries "behind the scenes" to avoid accessing or modifying the data directly.

I really hope you liked my article and found it helpful. To learn more about Properties and Object Oriented Programming in Python, check out my online course, which includes 6+ hours of video lectures, coding exercises, and mini projects.