ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 03.12.2023
Просмотров: 366
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Использование методов setter для проверки данных
Свойства чаще всего используются для проверки самих данных или формата их хранения. Допустим, вы не хотите, чтобы код за пределами класса мог присвоить атрибуту произвольное значение; некорректные значения способны вызвать ошиб- ки. В свойствах можно добавить проверки, которые присваивают атрибутам только проверенные значения. Такие проверки помогают перехватывать ошибки на более ранней стадии разработки, потому что при присваивании некорректного значения выдается исключение.
Обновим файл wizcoin.py из главы 15 и преобразуем атрибуты galleons
, sickles и knuts в свойства. Мы заменим setter-методы этих свойств так, чтобы допустимыми были только положительные целые числа. Наши объекты
WizCoin представляют набор монет, а количество монет не может быть дробным или отрицательным. Если код за пределами класса попытается задать свойствам galleons
, sickles или knuts недопустимое значение, будет выдано исключение
WizCoinException
Откройте файл wizcoin.py
, сохраненный в главе 15, и приведите его к следующему виду:
class WizCoinException(Exception):
❶
"""Выдается модулем wizcoin при некорректном использовании."""
❷
pass class WizCoin:
Свойства
363
def __init__(self, galleons, sickles, knuts):
"""Создание нового объекта WizCoin по значениям galleons, sickles и knuts."""
self.galleons = galleons
❸
self.sickles = sickles self.knuts = knuts
# ВНИМАНИЕ: методы __init__() НИКОГДА не содержат команду return.
--snip--
@property def galleons(self):
❹
"""Возвращает количество галлеонов в объекте."""
return self._galleons
@galleons.setter def galleons(self, value):
❺
if not isinstance(value, int):
❻
raise WizCoinException('galleons attr must be set to an int, not a
❼
' + value.__class__.__qualname__)
if value < 0:
❽
raise WizCoinException('galleons attr must be a positive int, not
' + value.__class__.__qualname__)
self._galleons = value
--snip--
С новыми изменениями добавляется класс
WizCoinException
, наследующий от встроенного класса Python
Exception
. Doc-строка класса описывает, как он ис- пользуется в модуле wizcoin
❷
. Такой подход считается правильным для моду- лей Python: объекты класса
WizCoin выдают это исключение при неправильном использовании. Если объект
WizCoin выдает исключения любых других классов
(например,
ValueError или
TypeError
), это с большой вероятностью указывает на наличие ошибки в классе
WizCoin
В методе
__init__()
свойствам self.galleons
, self.sickles и self.knuts присва- иваются соответствующие параметры
❸
В конце файла после методов total()
и weight()
добавляется getter-метод
❹
и setter- метод
❺
для атрибута self._galleons
. Getter-метод просто возвращает значение self._galleons
. Setter-метод проверяет, является ли значение, присваиваемое свой- ству galleons
, целым
❻
и положительным
❽
числом. Если хотя бы одна из проверок завершается неудачей, выдается исключение
WizCoinException с сообщением об ошибке. Эта проверка предотвращает возможность того, что
_galleons когда-либо будет присвоено недопустимое значение — при условии, что в коде всегда исполь- зуется свойство galleons
Все объекты Python автоматически получают атрибут
__class__
, который со- держит ссылку на объект класса для заданного объекта. Другими словами, value.__class__
содержит тот же объект класса, который возвращается вызовом
364
Глава 17.ООП в Python: свойства и dunder-методы type(value)
. Этот объект класса содержит атрибут с именем
__qualname__
, кото- рый содержит строку с именем класса (а именно полное имя класса, включающее имена любых классов, в которые вложен объект класса; о вложенных классах я в этой книге не рассказываю, а применяются они не так часто). Например, если в value хранится объект date
, возвращенный вызовом datetime.date(2021,
1,
1)
, то value.__class__.__qualname__
будет содержать строку 'date'
Сообщения исключений используют value.__class__.__qualname__
❼
для полу- чения строки с именем объекта значения. С именем класса сообщение об ошибке становится более полезным для программиста, который его читает, потому что оно указывает не только на то, что аргумент value имеет неправильный тип, но и на то, к какому типу он относится и какой тип должен иметь.
Коды getter- и setter-методов для
_galleons также необходимо скопировать, чтобы они использовались для атрибутов
_sickles и
_knuts
. Эти коды остаются теми же, не считая того, что в качестве резервных переменных они используют атрибуты
_sickles и
_knuts вместо
_galleons
Свойства, доступные только для чтения
Вашим объектам могут понадобиться свойства, доступные только для чтения, которым нельзя присвоить новые значения оператором
=
. Чтобы сделать свойство доступным только для чтения, исключите setter- и deleter-методы.
Например, метод total()
в классе
WizCoin возвращает ценность объекта в кнатах.
Из обычного метода его можно преобразовать в свойство, доступное только для чтения, потому что не существует разумного способа задать ценность объекта
WizCoin
. В конце концов, если вы присвоили total целое значение
1000
, что это означает — 1000 кнатов? Или 1 галлеон и 493 кната? Или какую-нибудь другую комбинацию? По этой причине мы сделаем total свойством, доступным только для чтения, для чего в файл wizcoin.py добавим код, выделенный жирным шрифтом:
@property
def total(self):
"""Общая ценность (в кнатах) всех монет в объекте WizCoin."""
return (self.galleons * 17 * 29) + (self.sickles * 29) + (self.knuts)
# Обратите внимание на отсутствие setter- и deleter-методов для `total`.
После добавления декоратора функции
@property перед total()
Python будет вызывать метод total()
при каждом обращении к total
. Из-за отсутствия setter- и deleter-методов Python выдаст исключение
AttributeError
, если какой-либо код попытается изменить или удалить свойство total
, используя его в присваивании или в команде del соответственно. Обратите внимание: значение свойства total зависит от значений свойств galleons
, sickles и knuts
; это свойство не базируется
Свойства
365
на резервной переменной с именем
_total
. Введите следующий фрагмент в инте- рактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> purse.total
1141
>>> purse.total = 1000
Traceback (most recent call last):
File "", line 1, in
AttributeError: can't set attribute
Возможно, вам не понравится, что при попытке изменения свойства, доступного только для чтения, программа немедленно аварийно завершается, но лучше дей- ствовать так. Если ваша программа способна менять свойство, доступное только для чтения, это наверняка приведет к ошибке в какой-то момент выполнения ва- шей программы. Если ошибка произойдет намного позднее изменения свойства, это усложнит поиск исходной причины. Немедленный фатальный сбой поможет быстрее обнаружить проблему.
Не путайте свойства, доступные только для чтения, с константами. Имена констант записываются в верхнем регистре, но программист сам отвечает за то, чтобы они не изменялись. Предполагается, что их значение остается неизменным на протяжении одного запуска программы. Свойство, доступное только для чтения, как и любой атрибут, связывается с объектом. Свойство, доступное только для чтения, невоз- можно напрямую присвоить или удалить. С другой стороны, значение, которое будет получено при обращении к нему, может изменяться. Свойство total класса
WizCoin изменяется с изменением свойств galleons
, sickles и knuts
Когда использовать свойства
Как было показано в предыдущих разделах, свойства расширяют возможности управления использованием атрибутов класса; использование свойств — питони- ческий стиль написания кода. Такие имена методов, как getSomeAttribute()
или setSomeAttribute()
, сигнализируют, что вместо них с большой вероятностью стоит использовать свойства.
Это не означает, что каждый метод, начинающийся с get или set
, нужно немедленно заменить свойством. В некоторых ситуациях следует использовать метод даже в том случае, если его имя начинается с get или set
. Вот несколько примеров.
Медленные операции, занимающие более одной или двух секунд, например загрузка или отправка файла.
Операции с побочными эффектами, например изменения других атрибутов или объектов.
366
Глава 17.ООП в Python: свойства и dunder-методы
Операции, требующие дополнительных аргументов get- или set-операциям, например вызовы методов вида emailObj.getFileAttachment(filename)
Программисты часто рассматривают методы как глаголы (в том смысле, что методы выполняют некоторое действие), а атрибуты и свойства — как существительные
(в том смысле, что они представляют некоторый элемент или объект). Если ваш код не просто читает или задает некоторое значение, а выполняет действие, возможно, лучше использовать getter- или setter-метод. В конечном итоге решение зависит от того, что кажется более правильным вам как программисту.
У свойств Python есть одно огромное преимущество: их не обязательно исполь- зовать при создании класса. Вы можете задать обычные атрибуты, а если потом решите переключиться на свойства, атрибуты можно преобразовать в свойства без нарушения работоспособности какого-либо кода за пределами класса. При создании свойства, имя которого совпадает с именем атрибута, можно переименовать атрибут и добавить в него префикс
_
; программа будет работать так же, как и прежде.
Dunder-методы Python
Python содержит ряд особых методов, имена которых начинаются и заканчивают- ся двойным подчеркиванием
__
(dunder). Такие методы также иногда называются
магическими. Вам уже знаком dunder-метод
__init__()
, но в Python есть и другие.
Они часто используются для перегрузки операторов — то есть добавления нестан- дартного поведения, позволяющего использовать объекты ваших классов с опера- торами Python, такими как
+
или
>=
. Другие dunder-методы позволяют объектам классов работать со встроенными функциями Python, такими как len()
или repr()
Как методы
__init__()
или getter-, setter- и deleter-методы свойств, специальные методы почти никогда не должны вызываться напрямую. Python вызывает их ав- томатически при использовании объектов с операторами или встроенными функ- циями. Например, если создать метод
__len__()
или
__repr__()
для вашего класса, он будет автоматически вызываться при передаче объекта этого класса функциям len()
или repr()
соответственно. Такие методы описаны в официальной докумен- тации Python на https://docs.python.org/3/reference/datamodel.html.
Далее мы рассмотрим разные виды dunder-методов, расширим наш класс
WizCoin и используем в нем эти методы.
Dunder-методы строкового представления
Dunder-методы
__repr__()
и
__str__()
используются для создания строковых представлений объектов, которые обычно неизвестны Python. Обычно Python создает строковые представления объектов двумя способами. Встроенная функция
Dunder-методы Python
367
repr()
возвращает код Python, при выполнении которого создается копия объекта.
Встроенная функция str()
возвращает строку, понятную для человека и содер- жащую ясную, полезную информацию об объекте. Например, чтобы просмотреть два представления объекта datetime.date
, введите следующий фрагмент в инте- рактивной оболочке:
>>> import datetime
>>> newyears = datetime.date(2021, 1, 1)
❶
>>> repr(newyears)
'datetime.date(2021, 1, 1)'
❷
>>> str(newyears)
'2021-01-01'
❸
>>> newyears
❹
datetime.date(2021, 1, 1)
В этом примере строковое представление 'datetime.date(2021,
1,
1)'
объекта datetime.date
❷
содержит строку кода Python, создающую копию этого объекта
❶
Копия является точным представлением объекта. С другой стороны, строка '2021-
01-01'
❸
выдает значение объекта в том виде, в котором оно хорошо воспринимается человеком. Если просто ввести объект в интерактивной оболочке
❹
, будет выведена repr-строка. Str-строка объекта обычно выводится для пользователей, тогда как repr-строка используется в техническом контексте, например в сообщениях об ошибках и журнальных файлах.
Python знает, как выводить объекты встроенных типов, например целые числа и строки. Однако Python не знает, как следует выводить объекты тех классов, которые создаете вы. Если функция repr()
не знает, как создать строку с пред- ставлением объекта, по умолчанию такая строка содержит адрес памяти и имя класса в угловых скобках:
'0x00000212B4148EE0>'
Чтобы создать такую строку для объекта
WizCoin
, введите следующий фрагмент в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> str(purse)
''
>>> repr(purse)
''
>>> purse
Эти строки плохо читаются и не содержат полезной информации для пользователя.
Чтобы указать Python, какую строку следует использовать, можно реализовать dunder-методы
__repr__()
и
__str__()
. Метод
__repr__()
указывает, какую строку должен возвращать Python для объекта, переданного встроенной функции repr()
Метод
__str__()
указывает, какую строку должен возвращать Python для объекта,
368
Глава 17.ООП в Python: свойства и dunder-методы переданного встроенной функции str()
. Добавьте следующий фрагмент в конец файла wizcoin.py
:
--snip-- def __repr__(self):
"""Возвращает строку с выражением, создающим объект."""
return f'{self.__class__.__qualname__}({self.galleons}, {self.sickles},
{self.knuts})'
def __str__(self):
"""Возвращает строковое представление объекта, понятное для человека."""
return f'{self.galleons}g, {self.sickles}s, {self.knuts}k'
Когда мы передаем purse функциям repr()
и str()
, Python вызывает dunder-методы
__repr__()
и
__str__()
. В нашем коде dunder-методы не вызываются.
Обратите внимание: f-строки, содержащие объект в фигурных скобках, неявно вы- зывают str()
для получения строкового представления объекта. Например, введите следующий фрагмент в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> repr(purse) # Автоматически вызывает __repr__() класса WizCoin.
'WizCoin(2, 5, 10)'
>>> str(purse) # Автоматически вызывает __str__() класса WizCoint.
'2g, 5s, 10k'
>>> print(f'My purse contains {purse}.') # Вызывает __str__() класса WizCoin.
My purse contains 2g, 5s, 10k.
Когда мы передаем объект
WizCoin в переменной purse функциям repr()
и str()
, во внутренней реализации Python вызывает методы
__repr__()
и
__str__()
клас- са
WizCoin
. Мы запрограммировали эти методы так, чтобы они возвращали более понятные и содержательные строки. Если ввести в интерактивной оболочке текст repr-строки 'WizCoin(2,
5,
10)'
, будет создан объект
WizCoin с такими же атрибу- тами, как у объекта в purse
. Str-строка содержит представление значения объекта, более понятное для человека:
'2g,
5s,
10k'
. Если вы используете объект
WizCoin в f-строке, Python использует str-представление объекта.
Если объекты
WizCoin настолько сложны, что создать их копию одним вызовом функции-конструктора невозможно, repr-строка заключается в угловые скобки — это показывает, что текст не рассматривается как код Python. Именно это проис- ходит со строками обобщенного представления вроде '0x00000212B4148EE0>'
. При вводе этой строки в интерактивной оболочке выдается ошибка
SyntaxError
, так что ее невозможно спутать с кодом Python, создающим копию объекта.
Внутри метода
__repr__()
мы используем self.__class__.__qualname__
вместо того, чтобы жестко фиксировать строку 'WizCoin'
; при субклассировании
WizCoin
Dunder-методы Python
369
унаследованный метод
__repr__()
будет использовать имя субкласса, а не 'WizCoin'
Кроме того, если класс
WizCoin будет переименован, метод
__repr__()
автоматиче- ски использует обновленное имя.
Но str-строка объекта
WizCoin выдает значения атрибута в удобной, компактной форме. Я настоятельно рекомендую реализовать методы
__repr__()
и
__str__()
во всех ваших классах.
КОНФИДЕНЦИАЛЬНАЯ ИНФОРМАЦИЯ В REPR-СТРОКАХ
Как упоминалось ранее, str-строки обычно выводятся для пользователей, а repr- строки используются в техническом контексте (например, в журналах). Но repr-строка может создать проблемы безопасности, если создаваемый объект содержит конфиденциальную информацию: пароли, медицинские сведения или данные личного порядка. В таком случае убедитесь, что метод
__repr__()
не включает эту информацию в возвращаемую строку. На случай, если в программе произойдет фатальный сбой, полезно включать переменные в файл журнала для упрощения отладки. Часто журналы не рассматриваются как конфиден- циальная информация. В нескольких инцидентах, связанных с нарушением безопасности, в общедоступных журналах случайно были опубликованы паро- ли, номера кредитных карт, домашние адреса и другая закрытая информация.
Помните об этом, когда будете писать методы
__repr__()
для вашего класса.
Числовые dunder-методы
Числовые dunder-методы, также называемые математическими dunder-методами, перегружают математические операторы Python:
+
,
-
,
*
,
/
и т. д. В настоящее время мы не можем выполнить такую операцию, как сложение двух объектов
WizCoin
, опе- ратором
+
. Если вы попытаетесь это сделать, Python выдаст исключение
TypeError
, потому что не знает, как суммировать объекты
WizCoin
. Чтобы убедиться в этом, введите следующий фрагмент в интерактивной оболочке:
>>>
Свойства чаще всего используются для проверки самих данных или формата их хранения. Допустим, вы не хотите, чтобы код за пределами класса мог присвоить атрибуту произвольное значение; некорректные значения способны вызвать ошиб- ки. В свойствах можно добавить проверки, которые присваивают атрибутам только проверенные значения. Такие проверки помогают перехватывать ошибки на более ранней стадии разработки, потому что при присваивании некорректного значения выдается исключение.
Обновим файл wizcoin.py из главы 15 и преобразуем атрибуты galleons
, sickles и knuts в свойства. Мы заменим setter-методы этих свойств так, чтобы допустимыми были только положительные целые числа. Наши объекты
WizCoin представляют набор монет, а количество монет не может быть дробным или отрицательным. Если код за пределами класса попытается задать свойствам galleons
, sickles или knuts недопустимое значение, будет выдано исключение
WizCoinException
Откройте файл wizcoin.py
, сохраненный в главе 15, и приведите его к следующему виду:
class WizCoinException(Exception):
❶
"""Выдается модулем wizcoin при некорректном использовании."""
❷
pass class WizCoin:
Свойства
363
def __init__(self, galleons, sickles, knuts):
"""Создание нового объекта WizCoin по значениям galleons, sickles и knuts."""
self.galleons = galleons
❸
self.sickles = sickles self.knuts = knuts
# ВНИМАНИЕ: методы __init__() НИКОГДА не содержат команду return.
--snip--
@property def galleons(self):
❹
"""Возвращает количество галлеонов в объекте."""
return self._galleons
@galleons.setter def galleons(self, value):
❺
if not isinstance(value, int):
❻
raise WizCoinException('galleons attr must be set to an int, not a
❼
' + value.__class__.__qualname__)
if value < 0:
❽
raise WizCoinException('galleons attr must be a positive int, not
' + value.__class__.__qualname__)
self._galleons = value
--snip--
С новыми изменениями добавляется класс
WizCoinException
, наследующий от встроенного класса Python
Exception
. Doc-строка класса описывает, как он ис- пользуется в модуле wizcoin
❷
. Такой подход считается правильным для моду- лей Python: объекты класса
WizCoin выдают это исключение при неправильном использовании. Если объект
WizCoin выдает исключения любых других классов
(например,
ValueError или
TypeError
), это с большой вероятностью указывает на наличие ошибки в классе
WizCoin
В методе
__init__()
свойствам self.galleons
, self.sickles и self.knuts присва- иваются соответствующие параметры
❸
В конце файла после методов total()
и weight()
добавляется getter-метод
❹
и setter- метод
❺
для атрибута self._galleons
. Getter-метод просто возвращает значение self._galleons
. Setter-метод проверяет, является ли значение, присваиваемое свой- ству galleons
, целым
❻
и положительным
❽
числом. Если хотя бы одна из проверок завершается неудачей, выдается исключение
WizCoinException с сообщением об ошибке. Эта проверка предотвращает возможность того, что
_galleons когда-либо будет присвоено недопустимое значение — при условии, что в коде всегда исполь- зуется свойство galleons
Все объекты Python автоматически получают атрибут
__class__
, который со- держит ссылку на объект класса для заданного объекта. Другими словами, value.__class__
содержит тот же объект класса, который возвращается вызовом
364
Глава 17.ООП в Python: свойства и dunder-методы type(value)
. Этот объект класса содержит атрибут с именем
__qualname__
, кото- рый содержит строку с именем класса (а именно полное имя класса, включающее имена любых классов, в которые вложен объект класса; о вложенных классах я в этой книге не рассказываю, а применяются они не так часто). Например, если в value хранится объект date
, возвращенный вызовом datetime.date(2021,
1,
1)
, то value.__class__.__qualname__
будет содержать строку 'date'
Сообщения исключений используют value.__class__.__qualname__
❼
для полу- чения строки с именем объекта значения. С именем класса сообщение об ошибке становится более полезным для программиста, который его читает, потому что оно указывает не только на то, что аргумент value имеет неправильный тип, но и на то, к какому типу он относится и какой тип должен иметь.
Коды getter- и setter-методов для
_galleons также необходимо скопировать, чтобы они использовались для атрибутов
_sickles и
_knuts
. Эти коды остаются теми же, не считая того, что в качестве резервных переменных они используют атрибуты
_sickles и
_knuts вместо
_galleons
Свойства, доступные только для чтения
Вашим объектам могут понадобиться свойства, доступные только для чтения, которым нельзя присвоить новые значения оператором
=
. Чтобы сделать свойство доступным только для чтения, исключите setter- и deleter-методы.
Например, метод total()
в классе
WizCoin возвращает ценность объекта в кнатах.
Из обычного метода его можно преобразовать в свойство, доступное только для чтения, потому что не существует разумного способа задать ценность объекта
WizCoin
. В конце концов, если вы присвоили total целое значение
1000
, что это означает — 1000 кнатов? Или 1 галлеон и 493 кната? Или какую-нибудь другую комбинацию? По этой причине мы сделаем total свойством, доступным только для чтения, для чего в файл wizcoin.py добавим код, выделенный жирным шрифтом:
@property
def total(self):
"""Общая ценность (в кнатах) всех монет в объекте WizCoin."""
return (self.galleons * 17 * 29) + (self.sickles * 29) + (self.knuts)
# Обратите внимание на отсутствие setter- и deleter-методов для `total`.
После добавления декоратора функции
@property перед total()
Python будет вызывать метод total()
при каждом обращении к total
. Из-за отсутствия setter- и deleter-методов Python выдаст исключение
AttributeError
, если какой-либо код попытается изменить или удалить свойство total
, используя его в присваивании или в команде del соответственно. Обратите внимание: значение свойства total зависит от значений свойств galleons
, sickles и knuts
; это свойство не базируется
Свойства
365
на резервной переменной с именем
_total
. Введите следующий фрагмент в инте- рактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> purse.total
1141
>>> purse.total = 1000
Traceback (most recent call last):
File "
AttributeError: can't set attribute
Возможно, вам не понравится, что при попытке изменения свойства, доступного только для чтения, программа немедленно аварийно завершается, но лучше дей- ствовать так. Если ваша программа способна менять свойство, доступное только для чтения, это наверняка приведет к ошибке в какой-то момент выполнения ва- шей программы. Если ошибка произойдет намного позднее изменения свойства, это усложнит поиск исходной причины. Немедленный фатальный сбой поможет быстрее обнаружить проблему.
Не путайте свойства, доступные только для чтения, с константами. Имена констант записываются в верхнем регистре, но программист сам отвечает за то, чтобы они не изменялись. Предполагается, что их значение остается неизменным на протяжении одного запуска программы. Свойство, доступное только для чтения, как и любой атрибут, связывается с объектом. Свойство, доступное только для чтения, невоз- можно напрямую присвоить или удалить. С другой стороны, значение, которое будет получено при обращении к нему, может изменяться. Свойство total класса
WizCoin изменяется с изменением свойств galleons
, sickles и knuts
Когда использовать свойства
Как было показано в предыдущих разделах, свойства расширяют возможности управления использованием атрибутов класса; использование свойств — питони- ческий стиль написания кода. Такие имена методов, как getSomeAttribute()
или setSomeAttribute()
, сигнализируют, что вместо них с большой вероятностью стоит использовать свойства.
Это не означает, что каждый метод, начинающийся с get или set
, нужно немедленно заменить свойством. В некоторых ситуациях следует использовать метод даже в том случае, если его имя начинается с get или set
. Вот несколько примеров.
Медленные операции, занимающие более одной или двух секунд, например загрузка или отправка файла.
Операции с побочными эффектами, например изменения других атрибутов или объектов.
366
Глава 17.ООП в Python: свойства и dunder-методы
Операции, требующие дополнительных аргументов get- или set-операциям, например вызовы методов вида emailObj.getFileAttachment(filename)
Программисты часто рассматривают методы как глаголы (в том смысле, что методы выполняют некоторое действие), а атрибуты и свойства — как существительные
(в том смысле, что они представляют некоторый элемент или объект). Если ваш код не просто читает или задает некоторое значение, а выполняет действие, возможно, лучше использовать getter- или setter-метод. В конечном итоге решение зависит от того, что кажется более правильным вам как программисту.
У свойств Python есть одно огромное преимущество: их не обязательно исполь- зовать при создании класса. Вы можете задать обычные атрибуты, а если потом решите переключиться на свойства, атрибуты можно преобразовать в свойства без нарушения работоспособности какого-либо кода за пределами класса. При создании свойства, имя которого совпадает с именем атрибута, можно переименовать атрибут и добавить в него префикс
_
; программа будет работать так же, как и прежде.
Dunder-методы Python
Python содержит ряд особых методов, имена которых начинаются и заканчивают- ся двойным подчеркиванием
__
(dunder). Такие методы также иногда называются
магическими. Вам уже знаком dunder-метод
__init__()
, но в Python есть и другие.
Они часто используются для перегрузки операторов — то есть добавления нестан- дартного поведения, позволяющего использовать объекты ваших классов с опера- торами Python, такими как
+
или
>=
. Другие dunder-методы позволяют объектам классов работать со встроенными функциями Python, такими как len()
или repr()
Как методы
__init__()
или getter-, setter- и deleter-методы свойств, специальные методы почти никогда не должны вызываться напрямую. Python вызывает их ав- томатически при использовании объектов с операторами или встроенными функ- циями. Например, если создать метод
__len__()
или
__repr__()
для вашего класса, он будет автоматически вызываться при передаче объекта этого класса функциям len()
или repr()
соответственно. Такие методы описаны в официальной докумен- тации Python на https://docs.python.org/3/reference/datamodel.html.
Далее мы рассмотрим разные виды dunder-методов, расширим наш класс
WizCoin и используем в нем эти методы.
Dunder-методы строкового представления
Dunder-методы
__repr__()
и
__str__()
используются для создания строковых представлений объектов, которые обычно неизвестны Python. Обычно Python создает строковые представления объектов двумя способами. Встроенная функция
Dunder-методы Python
367
repr()
возвращает код Python, при выполнении которого создается копия объекта.
Встроенная функция str()
возвращает строку, понятную для человека и содер- жащую ясную, полезную информацию об объекте. Например, чтобы просмотреть два представления объекта datetime.date
, введите следующий фрагмент в инте- рактивной оболочке:
>>> import datetime
>>> newyears = datetime.date(2021, 1, 1)
❶
>>> repr(newyears)
'datetime.date(2021, 1, 1)'
❷
>>> str(newyears)
'2021-01-01'
❸
>>> newyears
❹
datetime.date(2021, 1, 1)
В этом примере строковое представление 'datetime.date(2021,
1,
1)'
объекта datetime.date
❷
содержит строку кода Python, создающую копию этого объекта
❶
Копия является точным представлением объекта. С другой стороны, строка '2021-
01-01'
❸
выдает значение объекта в том виде, в котором оно хорошо воспринимается человеком. Если просто ввести объект в интерактивной оболочке
❹
, будет выведена repr-строка. Str-строка объекта обычно выводится для пользователей, тогда как repr-строка используется в техническом контексте, например в сообщениях об ошибках и журнальных файлах.
Python знает, как выводить объекты встроенных типов, например целые числа и строки. Однако Python не знает, как следует выводить объекты тех классов, которые создаете вы. Если функция repr()
не знает, как создать строку с пред- ставлением объекта, по умолчанию такая строка содержит адрес памяти и имя класса в угловых скобках:
'
Чтобы создать такую строку для объекта
WizCoin
, введите следующий фрагмент в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> str(purse)
'
>>> repr(purse)
'
>>> purse
Эти строки плохо читаются и не содержат полезной информации для пользователя.
Чтобы указать Python, какую строку следует использовать, можно реализовать dunder-методы
__repr__()
и
__str__()
. Метод
__repr__()
указывает, какую строку должен возвращать Python для объекта, переданного встроенной функции repr()
Метод
__str__()
указывает, какую строку должен возвращать Python для объекта,
368
Глава 17.ООП в Python: свойства и dunder-методы переданного встроенной функции str()
. Добавьте следующий фрагмент в конец файла wizcoin.py
:
--snip-- def __repr__(self):
"""Возвращает строку с выражением, создающим объект."""
return f'{self.__class__.__qualname__}({self.galleons}, {self.sickles},
{self.knuts})'
def __str__(self):
"""Возвращает строковое представление объекта, понятное для человека."""
return f'{self.galleons}g, {self.sickles}s, {self.knuts}k'
Когда мы передаем purse функциям repr()
и str()
, Python вызывает dunder-методы
__repr__()
и
__str__()
. В нашем коде dunder-методы не вызываются.
Обратите внимание: f-строки, содержащие объект в фигурных скобках, неявно вы- зывают str()
для получения строкового представления объекта. Например, введите следующий фрагмент в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> repr(purse) # Автоматически вызывает __repr__() класса WizCoin.
'WizCoin(2, 5, 10)'
>>> str(purse) # Автоматически вызывает __str__() класса WizCoint.
'2g, 5s, 10k'
>>> print(f'My purse contains {purse}.') # Вызывает __str__() класса WizCoin.
My purse contains 2g, 5s, 10k.
Когда мы передаем объект
WizCoin в переменной purse функциям repr()
и str()
, во внутренней реализации Python вызывает методы
__repr__()
и
__str__()
клас- са
WizCoin
. Мы запрограммировали эти методы так, чтобы они возвращали более понятные и содержательные строки. Если ввести в интерактивной оболочке текст repr-строки 'WizCoin(2,
5,
10)'
, будет создан объект
WizCoin с такими же атрибу- тами, как у объекта в purse
. Str-строка содержит представление значения объекта, более понятное для человека:
'2g,
5s,
10k'
. Если вы используете объект
WizCoin в f-строке, Python использует str-представление объекта.
Если объекты
WizCoin настолько сложны, что создать их копию одним вызовом функции-конструктора невозможно, repr-строка заключается в угловые скобки — это показывает, что текст не рассматривается как код Python. Именно это проис- ходит со строками обобщенного представления вроде '
. При вводе этой строки в интерактивной оболочке выдается ошибка
SyntaxError
, так что ее невозможно спутать с кодом Python, создающим копию объекта.
Внутри метода
__repr__()
мы используем self.__class__.__qualname__
вместо того, чтобы жестко фиксировать строку 'WizCoin'
; при субклассировании
WizCoin
Dunder-методы Python
369
унаследованный метод
__repr__()
будет использовать имя субкласса, а не 'WizCoin'
Кроме того, если класс
WizCoin будет переименован, метод
__repr__()
автоматиче- ски использует обновленное имя.
Но str-строка объекта
WizCoin выдает значения атрибута в удобной, компактной форме. Я настоятельно рекомендую реализовать методы
__repr__()
и
__str__()
во всех ваших классах.
КОНФИДЕНЦИАЛЬНАЯ ИНФОРМАЦИЯ В REPR-СТРОКАХ
Как упоминалось ранее, str-строки обычно выводятся для пользователей, а repr- строки используются в техническом контексте (например, в журналах). Но repr-строка может создать проблемы безопасности, если создаваемый объект содержит конфиденциальную информацию: пароли, медицинские сведения или данные личного порядка. В таком случае убедитесь, что метод
__repr__()
не включает эту информацию в возвращаемую строку. На случай, если в программе произойдет фатальный сбой, полезно включать переменные в файл журнала для упрощения отладки. Часто журналы не рассматриваются как конфиден- циальная информация. В нескольких инцидентах, связанных с нарушением безопасности, в общедоступных журналах случайно были опубликованы паро- ли, номера кредитных карт, домашние адреса и другая закрытая информация.
Помните об этом, когда будете писать методы
__repr__()
для вашего класса.
Числовые dunder-методы
Числовые dunder-методы, также называемые математическими dunder-методами, перегружают математические операторы Python:
+
,
-
,
*
,
/
и т. д. В настоящее время мы не можем выполнить такую операцию, как сложение двух объектов
WizCoin
, опе- ратором
+
. Если вы попытаетесь это сделать, Python выдаст исключение
TypeError
, потому что не знает, как суммировать объекты
WizCoin
. Чтобы убедиться в этом, введите следующий фрагмент в интерактивной оболочке:
>>>
1 ... 32 33 34 35 36 37 38 39 40