По умолчанию атрибуты в классах являются общедоступными, а это значит, что из любого места программы мы можем получить атрибут объекта и изменить его. Например:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | class Person:def __init__(self, name):self.name = name # устанавливаем имяself.age = 1 # устанавливаем возраст def display_info(self):print("Имя:", self.name, "\tВозраст:", self.age)tom = Person("Tom")tom.name = "Человек-паук" # изменяем атрибут nametom.age = -129 # изменяем атрибут agetom.display_info() # Имя: Человек-паук Возраст: -129 |
Но в данном случае мы можем, к примеру, присвоить возрасту или имени человека некорректное значение, например, указать отрицательный возраст. Подобное поведение нежелательно, поэтому встает вопрос о контроле за доступом к атрибутам объекта.
С данной проблемой тесно связано понятие инкапсуляции. Инкапсуляция является фундаментальной концепцией объектно-ориентированного программирования. Она предотвращает прямой доступ к атрибутам объект из вызывающего кода.
Касательно инкапсуляции непосредственно в языке программирования Python скрыть атрибуты класса можно сделав их приватными или закрытыми и ограничив доступ к ним через специальные методы, которые еще называются свойствами.
Изменим выше определенный класс, определив в нем свойства:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class Person:def __init__(self, name):self.__name = name # устанавливаем имяself.__age = 1 # устанавливаем возраст def set_age(self, age):if age in range(1, 100):self.__age = ageelse:print("Недопустимый возраст") def get_age(self):return self.__age def get_name(self):return self.__name def display_info(self):print("Имя:", self.__name, "\tВозраст:", self.__age)tom = Person("Tom")tom.display_info() # Имя: Tom Возраст: 1tom.set_age(-3486) # Недопустимый возрастtom.set_age(25)tom.display_info() # Имя: Tom Возраст: 25 |
Для создания приватного атрибута в начале его наименования ставится двойной прочерк: self.__name. К такому атрибуту мы сможем обратиться только из того же класса. Но не сможем обратиться вне этого класса. Например, присвоение значения этому атрибуту ничего не даст:
| 1 | tom.__age = 43 |
Потому что в данном случае просто определяется динамически новый атрибут __age, но это он не имеет ничего общего с атрибутом self.__age.
А попытка получить его значение приведет к ошибке выполнения (если ранее не была определена переменная __age):
| 1 | print(tom.__age) |
Однако все же нам может потребоваться устанавливать возраст пользователя из вне. Для этого создаются свойства. Используя одно свойство, мы можем получить значение атрибута:
| 1 2 | def get_age(self):return self.__age |
Данный метод еще часто называют геттер или аксессор.
Для изменения возраста определено другое свойство:
| 1 2 3 4 5 | def set_age(self, value):if value in range(1, 100):self.__age = valueelse:print("Недопустимый возраст") |
Здесь мы уже можем решить в зависимости от условий, надо ли переустанавливать возраст. Данный метод еще называют сеттер или мьютейтор (mutator).
Необязательно создавать для каждого приватного атрибута подобную пару свойств. Так, в примере выше имя человека мы можем установить только из конструктора. А для получение определен метод get_name.
Аннотации свойств
Выше мы рассмотрели, как создавать свойства. Но Python имеет также еще один – более элегантный способ определения свойств. Этот способ предполагает использование аннотаций, которые предваряются символом @.
Для создания свойства-геттера над свойством ставится аннотация @property.
Для создания свойства-сеттера над свойством устанавливается аннотация имя_свойства_геттера.setter.
Перепишем класс Person с использованием аннотаций:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | class Person:def __init__(self, name):self.__name = name # устанавливаем имяself.__age = 1 # устанавливаем возраст @propertydef age(self):return self.__age @age.setterdef age(self, age):if age in range(1, 100):self.__age = ageelse:print("Недопустимый возраст") @propertydef name(self):return self.__namedef display_info(self):print("Имя:", self.__name, "\tВозраст:", self.__age)tom = Person("Tom") tom.display_info() # Имя: Tom Возраст: 1tom.age = -3486 # Недопустимый возрастprint(tom.age) # 1tom.age = 36tom.display_info() # Имя: Tom Возраст: 36 |
Во-первых, стоит обратить внимание, что свойство-сеттер определяется после свойства-геттера.
Во-вторых, и сеттер, и геттер называются одинаково – age. И поскольку геттер называется age, то над сеттером устанавливается аннотация @age.setter.
После этого, что к геттеру, что к сеттеру, мы обращаемся через выражение tom.age.