Python @property: Как да го използвам и защо? - Програмиране

В този урок ще научите за декоратора на Python @property; питоничен начин за използване на гетери и сетери в обектно-ориентирано програмиране.

Програмирането на Python ни предоставя вграден @propertyдекоратор, който прави използването на getter и setter много по-лесно в обектно-ориентираното програмиране.

Преди да се впуснем в подробности за това какво е @propertyдекоратор, нека първо изградим интуиция за това защо би било необходимо на първо място.

Клас без гетери и сетери

Нека приемем, че решаваме да направим клас, който съхранява температурата в градуси по Целзий. Той също така ще приложи метод за преобразуване на температурата в градуси по Фаренхайт. Един от начините да направите това е следният:

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

Можем да правим обекти от този клас и да манипулираме temperatureатрибута, както желаем:

 # 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())

Изход

 37 98,60000000000001

Допълнителните десетични знаци при конвертиране във Фаренхайт се дължат на аритметичната грешка с плаваща запетая. За да научите повече, посетете аритметична грешка с плаваща запетая на Python.

Всеки път, когато присвоим или извлечем някакъв атрибут на обект, както temperatureе показано по-горе, Python го търси във вградения __dict__атрибут на речника на обекта .

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

Следователно, man.temperatureвътрешно става man.__dict__('temperature').

Използване на Getters и Setters

Да предположим, че искаме да разширим използваемостта на дефинирания по-горе клас Целзий. Знаем, че температурата на който и да е обект не може да достигне под -273,15 градуса по Целзий (Абсолютна нула в термодинамиката)

Нека актуализираме нашия код, за да приложим това ограничение на стойността.

Очевидно решение на горното ограничение ще бъде да скриете атрибута temperature(да го направите частен) и да дефинирате нови методи за получаване и задаване, за да го манипулирате. Това може да стане по следния начин:

 # 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

Както виждаме, горният метод въвежда два нови get_temperature()и set_temperature()метода.

Освен това temperatureбеше заменен с _temperature. Подчертаване _в началото се използва за обозначаване на частни променливи в Python.

Сега, нека използваме това изпълнение:

 # 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())

Изход

 37 98.60000000000001 Traceback (последно последно обаждане): Файл "", ред 30, във файл "", ред 16, в set_temperature ValueError: Температурата под -273.15 не е възможна.

Тази актуализация успешно внедри новото ограничение. Вече нямаме право да задаваме температурата под -273,15 градуса по Целзий.

Забележка : Частните променливи всъщност не съществуват в Python. Просто има норми, които трябва да се спазват. Самият език не прилага никакви ограничения.

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

Въпреки това, по-големият проблем с горното актуализацията е, че всички програми, които изпълняват предишния нашия клас, да променят кода си от obj.temperatureдо obj.get_temperature()и всички изрази като obj.temperature = valда obj.set_temperature(val).

Това рефакториране може да създаде проблеми при работа със стотици хиляди редове кодове.

Като цяло новата ни актуализация не беше обратно съвместима. Тук @propertyидва спасяването.

Клас на имота

Питоничен начин за справяне с горния проблем е използването на propertyкласа. Ето как можем да актуализираме нашия код:

 # 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)

Добавихме print()функция вътре get_temperature()и set_temperature()за ясно наблюдение, че те се изпълняват.

Последният ред на кода прави обект на свойство temperature. Най-просто казано, свойството прикачва някакъв код ( get_temperatureи set_temperature) към атрибута на член достъп ( temperature).

Нека използваме този код за актуализация:

 # 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

Изход

 Задаване на стойност … Получаване на стойност … 37 Получаване на стойност … 98.60000000000001 Задаване на стойност … Проследяване (последно последно повикване): Файл "", ред 31, във файл "", ред 18, в set_temperature ValueError: Температура под -273 не е възможна

Както виждаме, всеки код, който извлича стойността на, temperatureавтоматично ще извика get_temperature()вместо търсене на речник (__dict__). По същия начин всеки код, който присвоява стойност, temperatureавтоматично ще извика set_temperature().

Дори можем да видим по-горе, че set_temperature()е било наречено, дори когато сме създали обект.

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

Можете ли да познаете защо?

Причината е, че когато се създаде обект, __init__()методът се извиква. Този метод има линията self.temperature = temperature. Този израз автоматично извиква set_temperature().

По същия начин всеки достъп като c.temperatureавтоматично повикване get_temperature(). Това прави имотът. Ето още няколко примера.

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

Чрез използване propertyможем да видим, че не е необходима промяна в изпълнението на ограничението на стойността. По този начин нашето изпълнение е обратно съвместимо.

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)

Тези две парчета кодове са еквивалентни.

Програмистите, запознати с Python Decorators, могат да разпознаят, че горната конструкция може да бъде внедрена като декоратори.

Дори не можем да дефинираме имената get_temperatureи set_temperatureтъй като те са ненужни и замърсяват пространството от имена на класа.

За това ние използваме повторно temperatureимето, докато дефинираме нашите функции за получаване и задаване. Нека да разгледаме как да приложим това като декоратор:

 # 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)

Изход

 Задаване на стойност … Получаване на стойност … 37 Получаване на стойност … 98.60000000000001 Задаване на стойност … Проследяване (последно последно обаждане): Файл "", ред 29, във файл "", ред 4, в __init__ Файл "", ред 18, при температура ValueError: Температура под -273 не е възможна

Горното изпълнение е просто и ефективно. Това е препоръчителният начин за използване property.

Интересни статии...