Курс по Python ООП: https://stepik.org/a/116336
На этом занятии мы
рассмотрим один практический пример использования объектов-свойств (property), с которыми
познакомились на предыдущем занятии. Надеюсь, что из этого урока вы лучше
поймете, зачем они нужны и как применяются на практике.
Давайте, как и
на предыдущем занятии, объявим класс Person, который будет
представлять данные о персонале некоторого учреждения. Предположим, что отдел
кадров от нас требует хранить следующие персональные данные:
- ФИО;
- возраст (целое
число от 14 до 120);
- серию и номер
паспорта в формате xxxx xxxxxx, где x – цифра (от 0 до
9);
- вес, в кг
(вещественное число от 20 и выше).
Первое, над чем
задумывается программист, как описать эти данные на уровне программы. Я сделаю,
следующим образом:
- ФИО – список из
трех строк: фамилия, имя, отчество;
- возраст – целое
число;
- паспорт – строка
в нужном формате;
- вес –
вещественное число.
Запишем этот
класс со следующим инициализатором:
class Person:
def __init__(self, fio, old, ps, weight):
self.__fio = fio.split()
self.__old = old
self.__passport = ps
self.__weight = weight
Но пока здесь мы
не учитываем ограничения, накладываемые на данные. Давайте это сделаем. Я
пропишу несколько вспомогательных методов класса для проверки корректности
каждого переданного значения. Начнем с проверки ФИО. Для этого я пропишу
следующий метод:
@classmethod
def verify_fio(cls, fio):
if type(fio) != str:
raise TypeError("ФИО должно быть строкой")
f = fio.split()
if len(f) != 3:
raise TypeError("Неверный формат записи ФИО")
letters = ascii_letters + cls.S_RUS + cls.S_RUS_UPPER
for s in f:
if len(s) < 1:
raise TypeError("В ФИО должен быть хотя бы один символ")
if len(s.strip(letters)) != 0:
raise TypeError("В ФИО можно использовать только буквенные символы и дефис")
Дополнительно
определю атрибуты S_RUS и S_RUS_UPPER в классе Person:
S_RUS = 'абвгдеёжзийклмнопрстуфхцчшщьыъэюя-'
S_RUS_UPPER = S_RUS.upper()
И импортируем
коллекцию символов латинского алфавита:
from string import ascii_letters
Все, теперь, при
передаче ФИО нужно через пробел записывать фамилию, имя и отчество. Причем, все
они должны состоять только из буквенных символов или символа подчеркивания и
иметь длину не менее одного символа. Иначе, мы генерируем исключение TypeError с
соответствующим сообщением об ошибке.
Чтобы увидеть
работу этого метода, пропишем его в инициализаторе:
def __init__(self, fio, old, ps, weight):
self.verify_fio(fio)
self.__fio = fio.split()
self.__old = old
self.__passport = ps
self.__weight = weight
Если проверки
пройдут успешно, то исключений не возникнет и программа продолжит свою работу,
иначе она будет завершена, либо обработано соответствующее исключение. Об
обработке исключений мы с вами еще будем говорить.
Давайте
попробуем создать объект с верной записью ФИО:
p = Person('Балакирев Сергей Михайлович', 30, '1234 567890', 80.0)
Как видим,
никаких ошибок, но если указать что то неверное, то будут появляться ошибки и
программа прервет свою работу. Это то, что я бы хотел в данном случае видеть.
Отлично, первый
метод проверки мы с вами написали. Добавим еще три. Следующий будет проверять
корректность ввода возраста:
@classmethod
def verify_old(cls, old):
if type(old) != int or old < 14 or old > 120:
raise TypeError("Возраст должен быть целым числом в диапазоне [14; 120]")
Следующий метод
по аналогии будет проверять вес:
@classmethod
def verify_weight(cls, w):
if type(w) != float or w < 20:
raise TypeError("Вес должен быть вещественным числом от 20 и выше")
Наконец,
последний метод для проверки корректности серии и номера паспорта:
@classmethod
def verify_ps(cls, ps):
if type(ps) != str:
raise TypeError("Паспорт должен быть строкой")
s = ps.split()
if len(s) != 2 or len(s[0]) != 4 or len(s[1]) != 6:
raise TypeError("Неверный формат паспорта")
for p in s:
if not p.isdigit():
raise TypeError("Серия и номер паспорта должны быть числами")
Осталось вызвать
все эти методы в инициализаторе, чтобы они отрабатывали соответствующие
проверки передаваемых данных:
def __init__(self, fio, old, ps, weight):
self.verify_fio(fio)
self.verify_old(old)
self.verify_ps(ps)
self.verify_weight(weight)
self.__fio = fio.split()
self.__old = old
self.__passport = ps
self.__weight = weight
Как видите, все
достаточно просто. Теперь, программист уверен, что в приватных свойствах
экземпляров класса будут храниться корректные (с точки зрения формата) данные о
пользователях.
Следующий шаг –
это определить интерфейсы для взаимодействия с этими данными. Для этого
воспользуемся объектами-свойствами, о которых говорили на предыдущем занятии.
Первое такое
свойство будет для получения доступа к ФИО. Мы определим его через декоратор property, следующим
образом:
@property
def fio(self):
return self.__fio
В результате, у
нас будет только геттер для получения ФИО сотрудника. Изменить эти данные через
объект-свойство fio будет невозможно. Я решил так сделать в учебном примере,
чтобы еще раз показать, что мы можем довольно гибко определять эти объекты.
Следующее
свойство old будет
использоваться и для считывания и для записи возраста:
@property
def old(self):
return self.__old
@old.setter
def old(self, old):
self.verify_old(old)
self.__old = old
Обратите
внимание, в сеттере мы также делаем проверку на корректность переданных данных.
Проверку делаем с помощью ранее определенного метода verify_old.
Далее, опишем
свойство для доступа к паспортным данным:
@property
def passport(self):
return self.__passport
@passport.setter
def passport(self, ps):
self.verify_ps(ps)
self.__passport = ps
И последнее –
для веса сотрудника:
@property
def weight(self):
return self.__weight
@weight.setter
def weight(self, w):
self.verify_weight(w)
self.__weight = w
Все, мы описали
базовый функционал для хранения указанных данных о сотрудниках с проверкой
корректности передаваемых значений. Теперь мы можем воспользоваться этими
объектами-свойствами, например, так:
p = Person('Балакирев Сергей Михайлович', 20, '1234 567890', 80.0)
p.old = 100
p.passport = '4567 123456'
p.weight = 70.0
print(p.__dict__)
В данном случае,
мы передали корректные данные и они были записаны в соответствующие приватные
локальные свойства объекта p.
Кроме того,
можно непосредственно в инициализаторе воспользоваться объектами-свойствами,
чтобы упростить текст программы:
def __init__(self, fio, old, ps, weight):
self.verify_fio(fio)
self.__fio = fio.split()
self.old = old
self.passport = ps
self.weight = weight
Мы здесь сразу
через свойства осуществляем создание приватных локальных свойств в объекте и
автоматически проверяем корректность переданных данных. Кроме ФИО, т.к. мы для
него не определяли сеттер.
Я думаю, из
примера этого занятия вы теперь стали лучше себе представлять, как работать с
атрибутами класса и объектами-свойствами. На следующем занятии мы разовьем эту
тему и поговорим о дескрипторах.
Курс по Python ООП: https://stepik.org/a/116336