Python Property
Summary: in this tutorial, you’ll learn about the Python property class and how to use it to define properties for a class.
Introduction to class properties
The following defines a Person class that has two attributes name and age , and create a new instance of the Person class:
class Person: def __init__(self, name, age): self.name = name self.age = age john = Person('John', 18)
Code language: Python (python)
Since age is the instance attribute of the Person class, you can assign it a new value like this:
john.age = 19
Code language: Python (python)
The following assignment is also technically valid:
john.age = -1
Code language: Python (python)
However, the age is semantically incorrect.
To ensure that the age is not zero or negative, you use the if statement to add a check as follows:
age = -1 if age 0
: raise ValueError('The age must be positive') else: john.age = ageCode language: Python (python)
And you need to do this every time you want to assign a value to the age attribute. This is repetitive and difficult to maintain.
To avoid this repetition, you can define a pair of methods called getter and setter.
Getter and setter
The getter and setter methods provide an interface for accessing an instance attribute:
- The getter returns the value of an attribute
- The setter sets a new value for an attribute
In our example, you can make the age attribute private (by convention) and define a getter and a setter to manipulate the age attribute.
The following shows the new Person class with a getter and setter for the age attribute:
class Person: def __init__(self, name, age): self.name = name self.set_age(age) def set_age(self, age): if age 0
: raise ValueError('The age must be positive') self._age = age def get_age(self): return self._ageCode language: Python (python)
In the Person class, the set_age() is the setter and the get_age() is the getter. By convention the getter and setter have the following name: get_() and set_() .
In the set_age() method, we raise a ValueError if the age is less than or equal to zero. Otherwise, we assign the age argument to the _age attribute:
def set_age(self, age): if age 0
: raise ValueError('The age must be positive') self._age = ageCode language: Python (python)
The get_age() method returns the value of the _age attribute:
def get_age(self): return self._age
Code language: Python (python)
In the __init__() method, we call the set_age() setter method to initialize the _age attribute:
def __init__(self, name, age): self.name = name self.set_age(age)
Code language: Python (python)
The following attempts to assign an invalid value to the age attribute:
john = Person('John', 18) john.set_age(-19)
Code language: Python (python)
And Python issued a ValueError as expected.
ValueError: The age must be positive
Code language: Python (python)
This code works just fine. But it has a backward compatibility issue.
Suppose you released the Person class for a while and other developers have been already using it. And now you add the getter and setter, all the code that uses the Person won’t work anymore.
To define a getter and setter method while achieving backward compatibility, you can use the property() class.
The Python property class
The property class returns a property object. The property() class has the following syntax:
property(fget=None, fset=None, fdel=None, doc=None)
Code language: Python (python)
The property() has the following parameters:
- fget is a function to get the value of the attribute, or the getter method.
- fset is a function to set the value of the attribute, or the setter method.
- fdel is a function to delete the attribute.
- doc is a docstring i.e., a comment.
The following uses the property() function to define the age property for the Person class.
class Person: def __init__(self, name, age): self.name = name self.age = age def set_age(self, age): if age 0
: raise ValueError('The age must be positive') self._age = age def get_age(self): return self._age age = property(fget=get_age, fset=set_age)Code language: Python (python)
In the Person class, we create a new property object by calling the property() and assign the property object to the age attribute. Note that the age is a class attribute, not an instance attribute.
The following shows that the Person.age is a property object:
print(Person.age)
Code language: Python (python)
0x000001F5F5149180
>Code language: Python (python)
The following creates a new instance of the Person class and access the age attribute:
john = Person('John', 18)
Code language: Python (python)
The john.__dict__ stores the instance attributes of the john object. The following shows the contents of the john.__dict__ :
print(john.__dict__)
Code language: Python (python)
'_age'
: 18, 'name': 'John'>Code language: Python (python)
As you can see clearly from the output, the john.__dict__ doesn’t have the age attribute.
The following assigns a value to the age attribute of the john object:
john.age = 19
Code language: Python (python)
In this case, Python looks up the age attribute in the john.__dict__ first. Because Python doesn’t find the age attribute in the john.__dict__ , it’ll then find the age attribute in the Person.__dict__ .
The Person.__dict__ stores the class attributes of the Person class. The following shows the contents of the Person.__dict__ :
pprint(Person.__dict__)
Code language: Python (python)
mappingproxy('__dict__'
: '__dict__' of 'Person' objects>, '__doc__': None, '__init__': 0x000002242F5B2670>, '__module__': '__main__', '__weakref__': '__weakref__' of 'Person' objects>, 'age': 0x000002242EE39180>, 'get_age': 0x000002242F5B2790>, 'set_age': 0x000002242F5B2700>>)Code language: Python (python)
Because Python finds the age attribute in the Person.__dict__ , it’ll call the age property object.
When you assign a value to the age object:
john.age = 19
Code language: Python (python)
Python will call the function assigned to the fset argument, which is the set_age() .
Similarly, when you read from the age property object, Python will execute the function assigned to the fget argument, which is the get_age() method.
By using the property() class, we can add a property to a class while maintaining backward compatibility. In practice, you will define the attributes first. Later, you can add the property to the class if needed.
from pprint import pprint class Person: def __init__(self, name, age): self.name = name self.age = age def set_age(self, age): if age 0
: raise ValueError('The age must be positive') self._age = age def get_age(self): return self._age age = property(fget=get_age, fset=set_age) print(Person.age) john = Person('John', 18) pprint(john.__dict__) john.age = 19 pprint(Person.__dict__)Code language: Python (python)
Summary
Использование property в Python
Руководство с примерами использования функции property в языке программирования Python.
Введение
Функция property — создает вычисляемое свойство, которое позволяет использовать методы класса в качестве свойств объектов. Свойство — это аттрибут класса, который может считывать или записывать информацию. Использование property создает дескриптор, который позволяет создавать свойства объекту.
Проблема
Давайте рассмотрим пример. Существует класс человек, возраст которого не может быть меньше 0 и больше 120. Типичная ООП программа будет выглядеть примерно так:
class Human: """Человек, возраст которого не может быть больше 120 и меньше 0""" def __init__(self, age=0): self.set_age(age) def get_age(self): return self.age def set_age(self, age): if age < 120 and age >= 0: self.age = age else: self.age = 0
Как мы видим в этой реализации присутствуют некоторые проблемы и давайте их рассмотрим внимательно.
Создадим экземпляр 30 летнего человека, и проверим корректность введенных данных.
h = Human(age=30) print(h.get_age()) 30
Число 30 входит в рамки человеческой жизни и проблем у нас не возникло.
Попробуем ввести возраст вне рамок человеческой жизни
h.set_age(150) print(h.get_age()) 0
В данном примере мы обратились к методу класса, который проверил входные данные, из-за чего нам не удалось выставить некорректный возраст.
Но мы можем напрямую обратиться к аттрибуту self.age и указать абсолютно любой возраст.
Таким способом мы нарушаем алгоритм работы нашего класса, что является серьезной проблемой.
Решение
Для решения этой проблемы существуют два способа. Первый способ подразумевает использование декоратора функции, а второй способ указывает в функции property getter, setter и deleter.
Использование декоратора
@property — это декоратор, который обрабатывает получение, установку и удаление переменных класса так, как это было задумано в Python. Код для вышеописанного сценария будет выглядеть теперь так:
class Human: """Человек, возраст которого не может быть больше 120 и меньше 0""" def __init__(self, age=0): self.age = age @property def age(self): return self.__age @age.setter def age(self, age): if age < 120 and age >= 0: self.__age = age else: self.__age = 0
Наш код стал более лаконичным, но давайте разберемся по пунктам что же все таки изменилось и на что это повлияло.
- Мы использовали декоратор @property, который создает для декорируемой функции геттер.
- Затем мы использовали декоратор @age.setter, который создает для декорируемой функции сеттер. Сеттер определяет, что делать при установке значения переменной.
Обратите внимание, что обе функции имеют одинаковое имя. Это необходимо для работы программы.
Попробуем установить некорректное значение и посмотрим что получится:
h = Human(age=30) h.age = 150 print(h.age) 0
Используя декоратор @property мы ограничиваем возможность указания значений переменным в классе.
Прямое указание функций
Во втором варианте использование property мы описываем функцию для установки значения и получения значения.
class Human: """Человек, возраст которого не может быть больше 120 и меньше 0""" def __init__(self, age=0): self.set_age(age) def get_age(self): return self.__age def set_age(self, age): if age < 120 and age >= 0: self.__age = age else: self.__age = 0 age = property(get_age, set_age)
Как видите, нам просто нужно было изменить переменные и добавить специальную строку age = property(get_age, set_age), которая залатала дыры в нашем исходном коде и сэкономила нам кучу времени!
h = Human(age=30) h.age = 150 print(h.age) 0
Все работает, как и ожидалось.
Вывод
В данном руководстве мы рассмотрели, как и для чего использовать property, воспользовались двумя вариантами реализации и проверили оба. Я не упомянул в статье о так называемых deleter, предлагаю вам сделать это самостоятельно.
Программирую на Python с 2017 года. Люблю создавать контент, который помогает людям понять сложные вещи. Не представляю жизнь без непрерывного цикла обучения, спорта и чувства юмора.
Ссылка на мой github есть в шапке. Залетай.