112 Глава 4 •
Классы и ООП
Отличия Python 2 .x: __unicode__
В Python 3 имеется один тип данных на все случаи жизни для представ-
ления текста:
str
. Он содержит символы Юникода и может представлять
большинство систем письменности в мире.
В Python 2.
x для строковых данных используется другая модель дан-
ных
1
. Для представления текста служат два типа:
str
, который ограничен
на бором символов ASCII, и
unicode
, который эквивалентен типу
str
Python 3.
Вследствие этой разницы в Python 2 существует еще один дандер-ме-
тод в составе методов управления преобразованием строк:
__unicode__
.
В Python 2
__str__
возвращает
байты, тогда как
__unicode__
возвращает
символы.
По своим замыслу и целям метод
__unicode__
является более новым
и предпочтительным методом управления преобразованием строк. Кроме
того, имеется сопровождающая его встроенная функция
unicode()
. Она
вызывает соответствующий дандер-метод подобно тому, как работают
функции
str()
и
repr()
.
Чем дальше, тем лучше. Но все станет намного причудливее, когда вы
посмотрите на правила вызова методов
__str__
и
__unicode__
в Python 2.
Инструкция
print
и функция
str()
вызывают метод
__str__
. Встро-
енная в Python 2 функция
unicode()
вызывает метод
__unicode__
, если
он существует; в противном случае отыгрывает назад к методу
__str__
и декодирует результат в системную кодировку текста.
По сравнению с Python 3 эти особые случаи несколько усложняют пра-
вила преобразования текста. Но есть способ все снова упростить в прак-
тическом плане. Юникод является предпочтительным и перспективным
способом работы с текстом в программах Python.
Поэтому в Python 2.
x я в целом рекомендовал бы размещать весь свой
код форматирования строк внутрь метода
__unicode__
, а затем создавать
1
См. документацию Python 2 «Модель данных»:
https://docs .python .org/2/reference/
datamodel .html
4 .2 . Преобразование строк (каждому классу по __repr__)
113
реализацию заглушки
__str__
, которая возвращает представление в виде
Юникода в кодировке UTF-8:
def __str__(self):
return unicode(self).encode('utf-8')
Заглушка
__str__
будет одинаковой для большинства классов, ее вы про-
сто можете копипастить повсюду, где это необходимо (либо разместить ее
в базовом классе, где это имеет смысл). Тогда весь ваш код преобразова-
ния строк, который предназначен для использования не разработчиками,
будет лежать в методе
__unicode__
.
Приведем законченный пример для Python 2.
x:
class Car(object):
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __repr__(self):
return '{}({!r}, {!r})'.format(
self.__class__.__name__,
self.color, self.mileage)
def __unicode__(self):
return u'{self.color} автомобиль'.format(
self=self)
def __str__(self):
return unicode(self).encode('utf-8')
Ключевые выводы
Управлять преобразованием строк в своих собственных классах можно,
используя
дандер-методы
__str__
и
__repr__
.
Результат метода
__str__
должен быть удобочитаемым. Результат ме то-
да
__repr__
должен быть однозначным.
В свои классы всегда следует добавлять метод
__repr__
. По умолчанию
реализация метода
__str__
просто вызывает метод
__repr__
.
В Python 2 вместо метода
__str__
следует использовать метод
__uni-
code__
.
114 Глава 4 • Классы и ООП
4 .3 . Определение
своих собственных
классов-исключений
Когда я начал использовать Python, то не решался в своем программном
коде писать собственные классы-исключения. Вместе с тем определение
собственных типов ошибок может быть очень ценным. Вы четко выделите
потенциальные случаи ошибок, и, как результат, ваши функции и модули
станут более удобными в сопровождении. Вы также сможете использовать
собственные типы ошибок, которые обеспечат дополнительную отладоч-
ную информацию.
Все это улучшит ваш программный код и облегчит его понимание. Он
станет легче для отладки и удобнее в сопровождении. Задача определения
ваших собственных классов-исключений не будет такой сложной, когда
вы разобьете ее на несколько простых примеров. В этой главе я проведу
вас по основным пунктам, которые необходимо помнить.
Допустим, что вы хотели бы выполнить валидацию входного строкового
значения, которое в вашем приложении представляет имя человека. Игру-
шечный пример функции валидации имени может выглядеть следующим
образом:
def validate(name):
if len(name) < 10:
raise
ValueError
Если валидация терпит неудачу, она вызывает исключение
ValueError
.
Это кажется вполне уместным и выглядит по-питоновски. Пока что все
идет неплохо.
Вместе с тем в использовании универсального класса-исключения «вы-
сокого уровня» типа
ValueError
есть оборотная сторона. Предположим,
что один из ваших коллег вызывает эту функцию как составную часть
библиотеки и не очень разбирается в ее внутреннем устройстве. Когда
не удается выполнить валидацию имени, отчет об обратной трассировке
будет выглядеть примерно так:
>>> validate('джо')
Traceback (most recent call last):