Класс property в Python — использование и примеры
В этом руководстве вы узнаете о классе property в Python и о том, как использовать его для определения свойств класса.
Что такое класс property в Python?
Ниже определяется класс Person, который имеет два атрибута name и age, и создается новый экземпляр класса Person:
class Person: def __init__(self, name, age): self.name = name self.age = age john = Person('John', 18)
Поскольку возраст является атрибутом экземпляра класса Person, вы можете присвоить ему новое значение следующим образом:
john.age = 19
Следующее присвоение также технически допустимо:
john.age = -1
Однако возраст семантически неверен.
Чтобы гарантировать, что возраст не равен нулю и не является отрицательным, вы используете оператор if для добавления проверки следующим образом:
age = -1 if age <= 0: raise ValueError('The age must be positive') else: john.age = age
И вам нужно делать это каждый раз, когда вы хотите присвоить значение атрибуту возраста. Это повторяется и его трудно поддерживать.
Чтобы избежать повторения, вы можете определить пару методов, называемых getter и setter.
Геттер и сеттер
Методы getter и setter предоставляют интерфейс для доступа к атрибуту экземпляра:
- Геттер возвращает значение атрибута.
- Сеттер устанавливает новое значение для атрибута.
В нашем примере вы можете сделать атрибут age частным (по соглашению) и определить методы получения и установки для управления атрибутом возраста.
Ниже показан новый класс Person с геттером и сеттером для атрибута age:
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._age
Как это работает.
В классе Person set_age() является сеттером, а get_age() — геттером. По соглашению методы получения и установки имеют следующие имена: get_() и set_().
В методе set_age() мы вызываем ValueError, если возраст меньше или равен нулю. В противном случае мы присваиваем аргумент age атрибуту _age:
def set_age(self, age): if age <= 0: raise ValueError('The age must be positive') self._age = age
Метод get_age() возвращает значение атрибута _age:
def get_age(self): return self._age
В методе __init__() мы вызываем метод установки set_age() для инициализации атрибута _age:
def __init__(self, name, age): self.name = name self.set_age(age)
Следующие попытки присвоить недопустимое значение атрибуту возраста:
john = Person('John', 18) john.set_age(-19)
И Python, как и ожидалось, выдал ValueError.
ValueError: The age must be positive
Этот код работает отлично. Но у него есть проблема с обратной совместимостью.
Предположим, вы на какое-то время выпустили класс Person и его уже используют другие разработчики. И теперь, когда вы добавляете геттер и сеттер, весь код, использующий Person, больше не будет работать.
Чтобы определить методы получения и установки при обеспечении обратной совместимости, вы можете использовать класс property().
Класс property в Python
Класс property возвращает объект свойства. Класс property() имеет следующий синтаксис:
property(fget=None, fset=None, fdel=None, doc=None)
Класс имеет следующие параметры:
- fget — это функция для получения значения атрибута или метод получения.
- fset — функция для установки значения атрибута или метода установки.
- fdel — функция удаления атрибута.
- doc — это строка документации, т. е. комментарий.
В следующем примере используется функция property() для определения свойства age для класса Person.
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)
В классе Person мы создаем новый объект свойства, вызывая метод property() и присваивая объект свойства атрибуту age. Обратите внимание, что возраст является атрибутом класса, а не атрибутом экземпляра.
Ниже показано, что Person.age является объектом свойства:
print(Person.age)
Выход:
<property object at 0x000001F5F5149180>
Следующий пример создает новый экземпляр класса Person и получает доступ к атрибуту age:
john = Person('John', 18)
john.__dict__ хранит атрибуты экземпляра объекта john. Ниже показано содержимое john.__dict__ :
print(john.__dict__)
Выход:
{'_age': 18, 'name': 'John'}
Как ясно видно из вывода, john.__dict__ не имеет атрибута age.
Следующий код присваивает значение атрибуту age объекта john:
john.age = 19
В этом случае Python сначала ищет атрибут age в john.__dict__. Поскольку Python не находит атрибут age в john.__dict__, он находит атрибут age в Person.__dict__.
Person.__dict__ хранит атрибуты класса Person. Ниже показано содержимое Person.__dict__:
pprint(Person.__dict__)
Выход:
mappingproxy({'__dict__': <attribute '__dict__' of 'Person' objects>, '__doc__': None, '__init__': <function Person.__init__ at 0x000002242F5B2670>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Person' objects>, 'age': <property object at 0x000002242EE39180>, 'get_age': <function Person.get_age at 0x000002242F5B2790>, 'set_age': <function Person.set_age at 0x000002242F5B2700>})
Поскольку Python находит атрибут age в Person.__dict__, он вызывает объект свойства age.
Когда вы присваиваете значение объекту age:
john.age = 19
Python вызовет функцию, назначенную аргументу fset, то есть set_age().
Аналогично, когда вы читаете объект свойства age, Python выполнит функцию, назначенную аргументу fget, которая является методом get_age().
Используя класс property(), мы можем добавить свойство в класс, сохраняя при этом обратную совместимость. На практике сначала вы определите атрибуты. Позже при необходимости вы сможете добавить свойство в класс.
Собираем все вместе.
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__)