Класс 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__)

Похожие посты
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *