ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 03.12.2023
Просмотров: 362
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> tipJar = wizcoin.WizCoin(0, 0, 37)
>>> purse + tipJar
Traceback (most recent call last):
File "", line 1, in
TypeError: unsupported operand type(s) for +: 'WizCoin' and 'WizCoin'
Вместо того чтобы писать метод addWizCoin()
для класса
WizCoin
, вы можете ис- пользовать dunder-метод
__add__()
и заставить объекты
WizCoin работать с опера- тором
+
. Добавьте следующий фрагмент в конец файла wizcoin.py
:
370
Глава 17.ООП в Python: свойства и dunder-методы
--snip-- def __add__(self, other):
❶
"""Суммирует денежные величины двух объектов WizCoin."""
if not isinstance(other, WizCoin):
❷
return NotImplemented return WizCoin(other.galleons + self.galleons, other.sickles +
❸
self.sickles, other.knuts + self.knuts)
Когда объект
WizCoin стоит в левой части оператора
+
, Python вызывает метод
__add__()
❶
и передает значение в правой части оператора
+
в параметре other
(Параметру можно назначить любое имя, но традиционно используется имя other
.)
Помните, что методу
__add__()
можно передать объект любого типа, поэтому в ме- тод необходимо включить проверку типа
❷
. Например, бессмысленно прибавлять целое число или число с плавающей точкой к объекту
WizCoin
, потому что мы не знаем, к какому атрибуту его следует прибавить — galleons
, sickles или knuts
Метод
__add__()
создает новый объект
WizCoin с атрибутами, равными сумме galleons
, sickles и knuts объектов self и other
❸
. Так как все три атрибута содержат целые числа, их можно суммировать оператором
+
. Теперь оператор
+
для класса
WizCoin перегружен, и его можно использовать с объектами
WizCoin
Перегрузка оператора
+
позволяет создавать более понятный код. Например, вве- дите следующий фрагмент в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10) # Создает объект WizCoin.
>>> tipJar = wizcoin.WizCoin(0, 0, 37) # Создает другой объект WizCoin.
>>> purse + tipJar # Создает новый объект WizCoin с суммой.
WizCoin(2, 5, 47)
Если в other передается объект неправильного типа, dunder-метод должен не вы- давать исключение, а возвращать встроенное значение
NotImplemented
. Например, в следующем фрагменте в other передается целое число:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> purse + 42 # Объекты WizCoin и целые числа не могут суммироваться.
Traceback (most recent call last):
File "", line 1, in
TypeError: unsupported operand type(s) for +: 'WizCoin' and 'int'
Возвращение
NotImplemented приказывает Python попробовать вызвать другие ме- тоды для выполнения этой операции (за подробностями обращайтесь к подразделу
«Отраженные числовые dunder-методы» этой главы). Во внутренней реализации
Python вызывает метод
__add__()
со значением
42
для параметра other
, но этот метод тоже возвращает
NotImplemented
, что заставляет Python выдать исключение
TypeError
Dunder-методы Python
371
И хотя операции прибавления или вычитания целых чисел из объектов
WizCoin не имеют смысла, будет разумно разрешить умножение объектов
WizCoin на положи- тельные целые числа; для этого определяется dunder-метод
__mul__()
. Добавьте следующий фрагмент в конец файла wizcoin.py
:
--snip-- def __mul__(self, other):
"""Умножает количество монет на неотрицательное целое число."""
if not isinstance(other, int):
return NotImplemented if other < 0:
# Умножение на отрицательное целое число приведет
# к отрицательному количеству монет, что недопустимо.
raise WizCoinException('cannot multiply with negative integers')
return WizCoin(self.galleons * other, self.sickles * other, self.knuts * other)
Этот метод
__mul__()
позволяет умножать объекты
WizCoin на положительные це- лые числа. Если other является целым числом, то это тип данных, которые ожидает получить метод
__mul__()
, и возвращать
NotImplemented не нужно. Но если целое число — отрицательное, то умножение объекта
WizCoin на него приведет к отрица- тельному количеству монет в объекте
WizCoin
. Так как это противоречит нашему подходу к проектированию класса, мы выдаем исключение
WizCoinException с со- держательным сообщением об ошибке.
ПРИМЕЧАНИЕ
Не изменяйте объект self в математическом dunder-методе. Метод всегда должен созда- вать и возвращать новый объект. Оператор + и другие числовые операторы всегда должны давать в результате новый объект, вместо того чтобы изменять значение объекта на месте.
Чтобы увидеть dunder-метод
__mul__()
в действии, введите следующий фрагмент в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10) # Создать объект WizCoin.
>>> purse * 10 # Умножить объект WizCoin на целое число.
WizCoin(20, 50, 100)
>>> purse * -2 # Умножение на отрицательное целое число приводит к ошибке.
Traceback (most recent call last):
File "", line 1, in
File "C:\Users\Al\Desktop\wizcoin.py", line 86, in __mul__
raise WizCoinException('cannot multiply with negative integers')
wizcoin.WizCoinException: cannot multiply with negative integers
В табл. 17.1 приведен полный список числовых dunder-методов. Как правило, вам не требуется полный список этих методов для ваших классов. Вы сами решаете, какие методы для вас актуальны.
372
Глава 17.ООП в Python: свойства и dunder-методы
Таблица 17.1. Числовые dunder-методы
Dunder-метод
Операция
Оператор или встроенная
функция
__add__()
Сложение
+
__sub__()
Вычитание
-
__mul__()
Умножение
*
__matmul__()
Матричное умножение
(в Python 3.5 и выше)
@
__truediv__()
Деление
/
__floordiv__()
Целочисленное деление
//
__mod__()
Остаток
%
__divmod__()
Деление с остатком divmod()
__pow__()
Возведение в степень
**, pow()
__lshift__()
Сдвиг влево
>>
__rshift__()
Сдвиг вправо
<<
__and__()
Поразрядная операция AND
&
__or__()
Поразрядная операция OR
|
__xor__()
Поразрядная исключающая опе- рация OR
^
__neg__()
Отрицание
Унарный
-
, как в
-42
__pos__()
Тождество
Унарный
+
, как в
+42
__abs__()
Абсолютное значение (модуль)
abs()
__invert__()
Поразрядное инвертирование
__complex__()
Комплексная форма числа complex()
__int__()
Целая форма числа int()
__float__()
Форма числа с плавающей точкой float()
__bool__()
Логическая форма bool()
__round__()
Округление round()
__trunc__()
Целая часть math.trunc()
__floor__()
Округление в меньшую сторону math.floor()
__ceil__()
Округление в большую сторону math.ceil()
Dunder-методы Python
373
Некоторые из этих методов актуальны для нашего класса
WizCoin
. Попробуйте напи- сать собственные реализации методов
__sub__()
,
__pow__()
,
__int__()
,
__float__()
и
__bool__()
. Пример реализации доступен на https://autbor.com/wizcoinfull. Полное описание числовых dunder-методов в документации Python доступно на https://
docs.python.org/3/reference/datamodel.html#emulating-numeric-types.
Числовые dunder-методы позволяют использовать объекты ваших классов со встроенными математическими операторами Python. Если вы пишете методы с именами вида multiplyBy()
, convertToInt()
и т. д., описывающими задачу, которая выполняется существующим оператором или встроенной функцией, используйте числовые dunder-методы (а также отраженные dunder-методы и dunder-методы присваивания на месте, описанные в следующих двух разделах).
Отраженные числовые dunder-методы
Python вызывает числовые dunder-методы, когда объект находится в левой части математического оператора. Но если объект располагается в правой части мате- матического оператора, то вызываются отраженные числовые dunder-методы (их также называют обратными числовыми dunder-методами).
Отраженные числовые dunder-методы полезны, потому что программисты, ис- пользующие ваш класс, не всегда будут записывать объект в левой части оператора, что может привести к непредвиденному поведению. Для примера рассмотрим, что произойдет, если purse содержит объект
WizCoin
, а Python вычисляет выражение
2
*
purse
, когда purse находится в правой части оператора.
1. Так как
2
является целым числом, вызывается метод
__mul__()
класса int
, которому в параметре other передается purse
2. Метод
__mul__()
класса int не знает, как обрабатывать объекты
WizCoin
, по- этому он возвращает
NotImplemented
3. Пока Python не выдает ошибку
TypeError
. Так как purse содержит объект
WizCoin
, вызывается метод
__rmul__()
класса
WizCoin
, которому в параметре other передается
2 4. Если
__rmul__()
возвращает
NotImplemented
, Python выдает ошибку
TypeError
. В противном случае
__rmul__()
возвращает объект, полученный в результате вычисления выражения
2
*
purse
А вот выражение purse
*
2
, в котором purse находится в левой части оператора, работает иначе.
1. Так как purse содержит объект
WizCoin
, вызывается метод
__mul__()
класса
WizCoin
, которому в параметре other передается
2
374
Глава 17.ООП в Python: свойства и dunder-методы
2. Метод
__mul__()
создает новый объект
WizCoin и возвращает его.
3. Этот возвращенный объект — то, что возвращает выражение purse
*
2
Числовые dunder-методы и отраженные числовые dunder-методы имеют одина- ковый код, если они обладают свойством коммутативности. Коммутативные операции (такие как сложение) дают одинаковый результат при записи в прямом и обратном направлении:
3
+
2
— то же самое, что
2
+
3
Но существуют и другие операции, которые коммутативными не являются:
3
—
2
и
2
—
3
дают разные результаты. Любая коммутативная операция может просто вы- зывать исходный числовой dunder-метод каждый раз, когда вызывается отраженный числовой dunder-метод. Например, добавьте следующий фрагмент в конец файла wizcoin.py
, чтобы определить отраженный числовой dunder-метод для операции умножения:
--snip-- def __rmul__(self, other):
"""Умножает количество монет на неотрицательное целое число."""
return self.__mul__(other)
Умножение целого числа на объект
WizCoin коммутативно:
2
*
purse
— то же самое, что purse
*
2
. Вместо того чтобы копировать и вставлять код из
__mul__()
, мы про- сто вызываем self.__mul__()
и передаем параметр other
После обновления файла wizcoin.py проверьте, как работает отраженный dunder- метод умножения. Для этого введите следующий фрагмент в интерактивную обо- лочку:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> purse * 10 # Вызывает __mul__() с параметром `other`, равным 10.
WizCoin(20, 50, 100)
>>> 10 * purse # Вызывает __rmul__() с параметром `other`, равным 10.
WizCoin(20, 50, 100)
Помните, что в выражении
10
*
purse
Python сначала вызывает метод
__mul__()
класса int
, чтобы узнать, могут ли целые числа умножаться на объекты
WizCoin
Конечно, встроенный класс int ничего не знает о создаваемых нами классах, по- этому он возвращает
NotImplemented
. Это значение сигнализирует Python о том, чтобы следующим для обработки операции был вызван метод
__rmul__()
класса
WizCoin
(если он существует). Если вызовы
__mul__()
класса int и
__rmul__()
класса
WizCoin вернут
NotImplemented
, Python выдает исключение
TypeError
Только объекты
WizCoin способны суммироваться друг с другом. Это гарантирует, что метод
__add__()
первого объекта
WizCoin обработает операцию, поэтому реали- зовать
__radd__()
не нужно. Например, в выражении purse
+
tipJar метод
__add__()
Dunder-методы Python
375
для объекта purse вызывается с передачей tipJar в параметре other
. Так как этот вызов не возвращает
NotImplemented
, Python не пытается вызвать метод
__radd__()
объекта tipJar с передачей purse в параметре other
В табл. 17.2 приведен полный список доступных отраженных dunder-методов.
Таблица 17.2. Отраженные числовые dunder-методы
Dunder-метод
Операция
Оператор или встроенная
функция
__radd__()
Сложение
+
__rsub__()
Вычитание
-
__rmul__()
Умножение
*
__rmatmul__()
Матричное умножение
(в Python 3.5 и выше)
@
__rtruediv__()
Деление
/
__rfloordiv__()
Целочисленное деление
//
__rmod__()
Остаток
%
__rdivmod__()
Деление с остатком divmod()
__rpow__()
Возведение в степень
**, pow()
__rlshift__()
Сдвиг влево
>>
__rrshift__()
Сдвиг вправо
<<
__rand__()
Поразрядная операция AND
&
__ror__()
Поразрядная операция OR
|
__rxor__()
Поразрядная исключающая операция OR
^
Полное описание отраженных dunder-методов доступно в документации Python на
https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types.
Dunder-методы присваивания на месте (in-place)
Числовые и отраженные dunder-методы всегда создают новые объекты (вместо из- менения объектов на месте). Dunder-методы присваивания на месте, вызываемые расширенными операторами присваивания (такими как
+=
и
*=
), изменяют объект на месте без создания нового объекта. (У этого правила есть исключение, о котором я расскажу в конце раздела.) Имена таких dunder-методов начинаются с i
(напри- мер,
__iadd__()
и
__imul__()
для операторов
+=
и
*=
соответственно).
376
Глава 17.ООП в Python: свойства и dunder-методы
Например, при выполнении кода Python purse
*=
2
мы не предполагаем, что метод
__imul__()
класса
WizCoin создаст и вернет новый объект
WizCoin с вдвое большим количеством монет, а затем присвоит его переменной purse
. Вместо этого метод
__imul__()
изменяет существующий объект
WizCoin в purse
, чтобы он содержал вдвое большее количество монет. Это тонкое, но важное отличие, которое необхо- димо учитывать, если ваши классы должны перегружать расширенные операторы присваивания.
Наши объекты
WizCoin уже перегружают операторы
+
и
*
, поэтому определим dunder-методы
__iadd__()
и
__imul__()
, чтобы они также перегружали операторы
+=
и
*=
. В выражениях purse
+=
tipJar и purse
*=
2
вызываются методы
__iadd__()
и
__imul__()
, при этом в параметре other передаются tipJar и
2
соответственно.
Добавьте следующий фрагмент в конец файла wizcoin.py
:
--snip-- def __iadd__(self, other):
"""Прибавляет монеты из другого объекта WizCoin к этому объекту."""
if not isinstance(other, WizCoin):
return NotImplemented
# Объект `self` изменяется на месте:
self.galleons += other.galleons self.sickles += other.sickles self.knuts += other.knuts return self # Dunder-методы присваивания на месте
# почти всегда возвращают self.
def __imul__(self, other):
"""Умножает galleons, sickles и knuts этого объекта на неотрицательное целое число."""
if not isinstance(other, int):
return NotImplemented if other < 0:
raise WizCoinException('cannot multiply with negative integers')
# Класс WizCoin создает изменяемые объекты. НЕ СОЗДАВАЙТЕ новый
# объект, как в следующем закомментированном коде:
# return WizCoin(self.galleons * other, self.sickles * other,
# self.knuts * other)
# Объект `self` изменяется на месте:
self.galleons *= other self.sickles *= other self.knuts *= other return self # Dunder-методы присваивания на месте
# почти всегда возвращают self.
Объекты
WizCoin могут использовать оператор
+=
с другими объектами
WizCoin и оператор
*=
с положительными целыми числами. Заметим, что после проверки
Dunder-методы Python
377
параметра other методы присваивания на месте изменяют объект self
, вместо того чтобы создавать новый объект
WizCoin
. Чтобы увидеть, как расширенные опера- торы присваивания изменяют объекты
WizCoin на месте, введите следующий код в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> tipJar = wizcoin.WizCoin(0, 0, 37)
>>> purse + tipJar
❶
WizCoin(2, 5, 46)
❷
>>> purse
WizCoin(2, 5, 10)
>>> purse += tipJar
❸
>>> purse
WizCoin(2, 5, 47)
>>> purse *= 10
❹
>>> purse
WizCoin(20, 50, 470)
Оператор
+
❶
вызывает dunder-методы
__add__()
или
__radd__()
для создания и воз- вращения новых объектов
❷
. Исходные объекты, с которыми работал оператор
+
, остаются без изменений. Dunder-методы присваивания на месте
❸
и
❹
должны изменять объект на месте, при условии что объект является изменяемым (то есть это объект, значение которого может изменяться). Исключение делается для неиз- меняемых объектов, так как такие объекты по определению не могут изменяться.
В таком случае dunder-метод присваивания на месте должен создать и вернуть новый объект, как и числовые и отраженные dunder-методы.
Мы не сделали атрибуты galleons
, sickles и knuts доступными только для чтения; это означает, что они могут изменяться. Таким образом, объекты
WizCoin являются изменяемыми. Большинство классов, которые вы напишете, будут создавать изме- няемые объекты, поэтому вам стоит проектировать dunder-методы присваивания на месте, так чтобы они изменяли объект на месте.
Если вы не реализуете dunder-метод присваивания на месте, Python вызовет число- вой dunder-метод. Например, если в классе
WizCoin отсутствует метод
__imul__()
, выражение purse
*=
10
вызовет
__mul__()
и присвоит возвращаемое значение пере- менной purse
. Так как объекты
WizCoin являются изменяемыми, это неожиданное поведение способно порождать коварные ошибки.
1 ... 32 33 34 35 36 37 38 39 40
TypeError: unsupported operand type(s) for +: 'WizCoin' and 'WizCoin'
Вместо того чтобы писать метод addWizCoin()
для класса
WizCoin
, вы можете ис- пользовать dunder-метод
__add__()
и заставить объекты
WizCoin работать с опера- тором
+
. Добавьте следующий фрагмент в конец файла wizcoin.py
:
370
Глава 17.ООП в Python: свойства и dunder-методы
--snip-- def __add__(self, other):
❶
"""Суммирует денежные величины двух объектов WizCoin."""
if not isinstance(other, WizCoin):
❷
return NotImplemented return WizCoin(other.galleons + self.galleons, other.sickles +
❸
self.sickles, other.knuts + self.knuts)
Когда объект
WizCoin стоит в левой части оператора
+
, Python вызывает метод
__add__()
❶
и передает значение в правой части оператора
+
в параметре other
(Параметру можно назначить любое имя, но традиционно используется имя other
.)
Помните, что методу
__add__()
можно передать объект любого типа, поэтому в ме- тод необходимо включить проверку типа
❷
. Например, бессмысленно прибавлять целое число или число с плавающей точкой к объекту
WizCoin
, потому что мы не знаем, к какому атрибуту его следует прибавить — galleons
, sickles или knuts
Метод
__add__()
создает новый объект
WizCoin с атрибутами, равными сумме galleons
, sickles и knuts объектов self и other
❸
. Так как все три атрибута содержат целые числа, их можно суммировать оператором
+
. Теперь оператор
+
для класса
WizCoin перегружен, и его можно использовать с объектами
WizCoin
Перегрузка оператора
+
позволяет создавать более понятный код. Например, вве- дите следующий фрагмент в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10) # Создает объект WizCoin.
>>> tipJar = wizcoin.WizCoin(0, 0, 37) # Создает другой объект WizCoin.
>>> purse + tipJar # Создает новый объект WizCoin с суммой.
WizCoin(2, 5, 47)
Если в other передается объект неправильного типа, dunder-метод должен не вы- давать исключение, а возвращать встроенное значение
NotImplemented
. Например, в следующем фрагменте в other передается целое число:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> purse + 42 # Объекты WizCoin и целые числа не могут суммироваться.
Traceback (most recent call last):
File "
TypeError: unsupported operand type(s) for +: 'WizCoin' and 'int'
Возвращение
NotImplemented приказывает Python попробовать вызвать другие ме- тоды для выполнения этой операции (за подробностями обращайтесь к подразделу
«Отраженные числовые dunder-методы» этой главы). Во внутренней реализации
Python вызывает метод
__add__()
со значением
42
для параметра other
, но этот метод тоже возвращает
NotImplemented
, что заставляет Python выдать исключение
TypeError
Dunder-методы Python
371
И хотя операции прибавления или вычитания целых чисел из объектов
WizCoin не имеют смысла, будет разумно разрешить умножение объектов
WizCoin на положи- тельные целые числа; для этого определяется dunder-метод
__mul__()
. Добавьте следующий фрагмент в конец файла wizcoin.py
:
--snip-- def __mul__(self, other):
"""Умножает количество монет на неотрицательное целое число."""
if not isinstance(other, int):
return NotImplemented if other < 0:
# Умножение на отрицательное целое число приведет
# к отрицательному количеству монет, что недопустимо.
raise WizCoinException('cannot multiply with negative integers')
return WizCoin(self.galleons * other, self.sickles * other, self.knuts * other)
Этот метод
__mul__()
позволяет умножать объекты
WizCoin на положительные це- лые числа. Если other является целым числом, то это тип данных, которые ожидает получить метод
__mul__()
, и возвращать
NotImplemented не нужно. Но если целое число — отрицательное, то умножение объекта
WizCoin на него приведет к отрица- тельному количеству монет в объекте
WizCoin
. Так как это противоречит нашему подходу к проектированию класса, мы выдаем исключение
WizCoinException с со- держательным сообщением об ошибке.
ПРИМЕЧАНИЕ
Не изменяйте объект self в математическом dunder-методе. Метод всегда должен созда- вать и возвращать новый объект. Оператор + и другие числовые операторы всегда должны давать в результате новый объект, вместо того чтобы изменять значение объекта на месте.
Чтобы увидеть dunder-метод
__mul__()
в действии, введите следующий фрагмент в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10) # Создать объект WizCoin.
>>> purse * 10 # Умножить объект WizCoin на целое число.
WizCoin(20, 50, 100)
>>> purse * -2 # Умножение на отрицательное целое число приводит к ошибке.
Traceback (most recent call last):
File "
File "C:\Users\Al\Desktop\wizcoin.py", line 86, in __mul__
raise WizCoinException('cannot multiply with negative integers')
wizcoin.WizCoinException: cannot multiply with negative integers
В табл. 17.1 приведен полный список числовых dunder-методов. Как правило, вам не требуется полный список этих методов для ваших классов. Вы сами решаете, какие методы для вас актуальны.
372
Глава 17.ООП в Python: свойства и dunder-методы
Таблица 17.1. Числовые dunder-методы
Dunder-метод
Операция
Оператор или встроенная
функция
__add__()
Сложение
+
__sub__()
Вычитание
-
__mul__()
Умножение
*
__matmul__()
Матричное умножение
(в Python 3.5 и выше)
@
__truediv__()
Деление
/
__floordiv__()
Целочисленное деление
//
__mod__()
Остаток
%
__divmod__()
Деление с остатком divmod()
__pow__()
Возведение в степень
**, pow()
__lshift__()
Сдвиг влево
>>
__rshift__()
Сдвиг вправо
<<
__and__()
Поразрядная операция AND
&
__or__()
Поразрядная операция OR
|
__xor__()
Поразрядная исключающая опе- рация OR
^
__neg__()
Отрицание
Унарный
-
, как в
-42
__pos__()
Тождество
Унарный
+
, как в
+42
__abs__()
Абсолютное значение (модуль)
abs()
__invert__()
Поразрядное инвертирование
__complex__()
Комплексная форма числа complex()
__int__()
Целая форма числа int()
__float__()
Форма числа с плавающей точкой float()
__bool__()
Логическая форма bool()
__round__()
Округление round()
__trunc__()
Целая часть math.trunc()
__floor__()
Округление в меньшую сторону math.floor()
__ceil__()
Округление в большую сторону math.ceil()
Dunder-методы Python
373
Некоторые из этих методов актуальны для нашего класса
WizCoin
. Попробуйте напи- сать собственные реализации методов
__sub__()
,
__pow__()
,
__int__()
,
__float__()
и
__bool__()
. Пример реализации доступен на https://autbor.com/wizcoinfull. Полное описание числовых dunder-методов в документации Python доступно на https://
docs.python.org/3/reference/datamodel.html#emulating-numeric-types.
Числовые dunder-методы позволяют использовать объекты ваших классов со встроенными математическими операторами Python. Если вы пишете методы с именами вида multiplyBy()
, convertToInt()
и т. д., описывающими задачу, которая выполняется существующим оператором или встроенной функцией, используйте числовые dunder-методы (а также отраженные dunder-методы и dunder-методы присваивания на месте, описанные в следующих двух разделах).
Отраженные числовые dunder-методы
Python вызывает числовые dunder-методы, когда объект находится в левой части математического оператора. Но если объект располагается в правой части мате- матического оператора, то вызываются отраженные числовые dunder-методы (их также называют обратными числовыми dunder-методами).
Отраженные числовые dunder-методы полезны, потому что программисты, ис- пользующие ваш класс, не всегда будут записывать объект в левой части оператора, что может привести к непредвиденному поведению. Для примера рассмотрим, что произойдет, если purse содержит объект
WizCoin
, а Python вычисляет выражение
2
*
purse
, когда purse находится в правой части оператора.
1. Так как
2
является целым числом, вызывается метод
__mul__()
класса int
, которому в параметре other передается purse
2. Метод
__mul__()
класса int не знает, как обрабатывать объекты
WizCoin
, по- этому он возвращает
NotImplemented
3. Пока Python не выдает ошибку
TypeError
. Так как purse содержит объект
WizCoin
, вызывается метод
__rmul__()
класса
WizCoin
, которому в параметре other передается
2 4. Если
__rmul__()
возвращает
NotImplemented
, Python выдает ошибку
TypeError
. В противном случае
__rmul__()
возвращает объект, полученный в результате вычисления выражения
2
*
purse
А вот выражение purse
*
2
, в котором purse находится в левой части оператора, работает иначе.
1. Так как purse содержит объект
WizCoin
, вызывается метод
__mul__()
класса
WizCoin
, которому в параметре other передается
2
374
Глава 17.ООП в Python: свойства и dunder-методы
2. Метод
__mul__()
создает новый объект
WizCoin и возвращает его.
3. Этот возвращенный объект — то, что возвращает выражение purse
*
2
Числовые dunder-методы и отраженные числовые dunder-методы имеют одина- ковый код, если они обладают свойством коммутативности. Коммутативные операции (такие как сложение) дают одинаковый результат при записи в прямом и обратном направлении:
3
+
2
— то же самое, что
2
+
3
Но существуют и другие операции, которые коммутативными не являются:
3
—
2
и
2
—
3
дают разные результаты. Любая коммутативная операция может просто вы- зывать исходный числовой dunder-метод каждый раз, когда вызывается отраженный числовой dunder-метод. Например, добавьте следующий фрагмент в конец файла wizcoin.py
, чтобы определить отраженный числовой dunder-метод для операции умножения:
--snip-- def __rmul__(self, other):
"""Умножает количество монет на неотрицательное целое число."""
return self.__mul__(other)
Умножение целого числа на объект
WizCoin коммутативно:
2
*
purse
— то же самое, что purse
*
2
. Вместо того чтобы копировать и вставлять код из
__mul__()
, мы про- сто вызываем self.__mul__()
и передаем параметр other
После обновления файла wizcoin.py проверьте, как работает отраженный dunder- метод умножения. Для этого введите следующий фрагмент в интерактивную обо- лочку:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> purse * 10 # Вызывает __mul__() с параметром `other`, равным 10.
WizCoin(20, 50, 100)
>>> 10 * purse # Вызывает __rmul__() с параметром `other`, равным 10.
WizCoin(20, 50, 100)
Помните, что в выражении
10
*
purse
Python сначала вызывает метод
__mul__()
класса int
, чтобы узнать, могут ли целые числа умножаться на объекты
WizCoin
Конечно, встроенный класс int ничего не знает о создаваемых нами классах, по- этому он возвращает
NotImplemented
. Это значение сигнализирует Python о том, чтобы следующим для обработки операции был вызван метод
__rmul__()
класса
WizCoin
(если он существует). Если вызовы
__mul__()
класса int и
__rmul__()
класса
WizCoin вернут
NotImplemented
, Python выдает исключение
TypeError
Только объекты
WizCoin способны суммироваться друг с другом. Это гарантирует, что метод
__add__()
первого объекта
WizCoin обработает операцию, поэтому реали- зовать
__radd__()
не нужно. Например, в выражении purse
+
tipJar метод
__add__()
Dunder-методы Python
375
для объекта purse вызывается с передачей tipJar в параметре other
. Так как этот вызов не возвращает
NotImplemented
, Python не пытается вызвать метод
__radd__()
объекта tipJar с передачей purse в параметре other
В табл. 17.2 приведен полный список доступных отраженных dunder-методов.
Таблица 17.2. Отраженные числовые dunder-методы
Dunder-метод
Операция
Оператор или встроенная
функция
__radd__()
Сложение
+
__rsub__()
Вычитание
-
__rmul__()
Умножение
*
__rmatmul__()
Матричное умножение
(в Python 3.5 и выше)
@
__rtruediv__()
Деление
/
__rfloordiv__()
Целочисленное деление
//
__rmod__()
Остаток
%
__rdivmod__()
Деление с остатком divmod()
__rpow__()
Возведение в степень
**, pow()
__rlshift__()
Сдвиг влево
>>
__rrshift__()
Сдвиг вправо
<<
__rand__()
Поразрядная операция AND
&
__ror__()
Поразрядная операция OR
|
__rxor__()
Поразрядная исключающая операция OR
^
Полное описание отраженных dunder-методов доступно в документации Python на
https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types.
Dunder-методы присваивания на месте (in-place)
Числовые и отраженные dunder-методы всегда создают новые объекты (вместо из- менения объектов на месте). Dunder-методы присваивания на месте, вызываемые расширенными операторами присваивания (такими как
+=
и
*=
), изменяют объект на месте без создания нового объекта. (У этого правила есть исключение, о котором я расскажу в конце раздела.) Имена таких dunder-методов начинаются с i
(напри- мер,
__iadd__()
и
__imul__()
для операторов
+=
и
*=
соответственно).
376
Глава 17.ООП в Python: свойства и dunder-методы
Например, при выполнении кода Python purse
*=
2
мы не предполагаем, что метод
__imul__()
класса
WizCoin создаст и вернет новый объект
WizCoin с вдвое большим количеством монет, а затем присвоит его переменной purse
. Вместо этого метод
__imul__()
изменяет существующий объект
WizCoin в purse
, чтобы он содержал вдвое большее количество монет. Это тонкое, но важное отличие, которое необхо- димо учитывать, если ваши классы должны перегружать расширенные операторы присваивания.
Наши объекты
WizCoin уже перегружают операторы
+
и
*
, поэтому определим dunder-методы
__iadd__()
и
__imul__()
, чтобы они также перегружали операторы
+=
и
*=
. В выражениях purse
+=
tipJar и purse
*=
2
вызываются методы
__iadd__()
и
__imul__()
, при этом в параметре other передаются tipJar и
2
соответственно.
Добавьте следующий фрагмент в конец файла wizcoin.py
:
--snip-- def __iadd__(self, other):
"""Прибавляет монеты из другого объекта WizCoin к этому объекту."""
if not isinstance(other, WizCoin):
return NotImplemented
# Объект `self` изменяется на месте:
self.galleons += other.galleons self.sickles += other.sickles self.knuts += other.knuts return self # Dunder-методы присваивания на месте
# почти всегда возвращают self.
def __imul__(self, other):
"""Умножает galleons, sickles и knuts этого объекта на неотрицательное целое число."""
if not isinstance(other, int):
return NotImplemented if other < 0:
raise WizCoinException('cannot multiply with negative integers')
# Класс WizCoin создает изменяемые объекты. НЕ СОЗДАВАЙТЕ новый
# объект, как в следующем закомментированном коде:
# return WizCoin(self.galleons * other, self.sickles * other,
# self.knuts * other)
# Объект `self` изменяется на месте:
self.galleons *= other self.sickles *= other self.knuts *= other return self # Dunder-методы присваивания на месте
# почти всегда возвращают self.
Объекты
WizCoin могут использовать оператор
+=
с другими объектами
WizCoin и оператор
*=
с положительными целыми числами. Заметим, что после проверки
Dunder-методы Python
377
параметра other методы присваивания на месте изменяют объект self
, вместо того чтобы создавать новый объект
WizCoin
. Чтобы увидеть, как расширенные опера- торы присваивания изменяют объекты
WizCoin на месте, введите следующий код в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10)
>>> tipJar = wizcoin.WizCoin(0, 0, 37)
>>> purse + tipJar
❶
WizCoin(2, 5, 46)
❷
>>> purse
WizCoin(2, 5, 10)
>>> purse += tipJar
❸
>>> purse
WizCoin(2, 5, 47)
>>> purse *= 10
❹
>>> purse
WizCoin(20, 50, 470)
Оператор
+
❶
вызывает dunder-методы
__add__()
или
__radd__()
для создания и воз- вращения новых объектов
❷
. Исходные объекты, с которыми работал оператор
+
, остаются без изменений. Dunder-методы присваивания на месте
❸
и
❹
должны изменять объект на месте, при условии что объект является изменяемым (то есть это объект, значение которого может изменяться). Исключение делается для неиз- меняемых объектов, так как такие объекты по определению не могут изменяться.
В таком случае dunder-метод присваивания на месте должен создать и вернуть новый объект, как и числовые и отраженные dunder-методы.
Мы не сделали атрибуты galleons
, sickles и knuts доступными только для чтения; это означает, что они могут изменяться. Таким образом, объекты
WizCoin являются изменяемыми. Большинство классов, которые вы напишете, будут создавать изме- няемые объекты, поэтому вам стоит проектировать dunder-методы присваивания на месте, так чтобы они изменяли объект на месте.
Если вы не реализуете dunder-метод присваивания на месте, Python вызовет число- вой dunder-метод. Например, если в классе
WizCoin отсутствует метод
__imul__()
, выражение purse
*=
10
вызовет
__mul__()
и присвоит возвращаемое значение пере- менной purse
. Так как объекты
WizCoin являются изменяемыми, это неожиданное поведение способно порождать коварные ошибки.
1 ... 32 33 34 35 36 37 38 39 40
Dunder-методы сравнения
Метод Python sort()
и функция sorted()
используют эффективный алгоритм сортировки, для выполнения которого достаточно простого вызова. Но если вы хотите сравнивать и сортировать объекты создаваемого вами класса, необходимо сообщить Python, как должны сравниваться два таких объекта — для этого нужно
Метод Python sort()
и функция sorted()
используют эффективный алгоритм сортировки, для выполнения которого достаточно простого вызова. Но если вы хотите сравнивать и сортировать объекты создаваемого вами класса, необходимо сообщить Python, как должны сравниваться два таких объекта — для этого нужно
378
Глава 17.ООП в Python: свойства и dunder-методы реализовать dunder-методы сравнения. За кулисами Python вызывает dunder- методы сравнения каждый раз, когда ваши объекты используются в выражениях с операторами сравнения
<
,
>
,
<=
,
>=
,
==
и
!=
Прежде чем исследовать dunder-методы сравнения, рассмотрим шесть функций модуля operator
, которые выполняют те же операции, что и шесть операций срав- нения. Наши dunder-методы сравнения будут вызывать эти функции. Введите следующий фрагмент в интерактивной оболочке:
>>> import operator
>>> operator.eq(42, 42) # "Равно"; то же, что 42 == 42
True
>>> operator.ne('cat', 'dog') # "Не равно"; то же, что 'cat' != 'dog'
True
>>> operator.gt(10, 20) # "Больше"; то же, что 10 > 20
False
>>> operator.ge(10, 10) # "Больше или равно"; то же, что 10 >= 10
True
>>> operator.lt(10, 20) # "Меньше"; то же, что 10 < 20
True
>>> operator.le(10, 20) # "Меньше или равно"; то же, что 10 <= 20
True
Модуль operator предоставляет версии операторов сравнения в виде функций.
Их реализации очень просты. Например, можно написать собственную функцию operator.eq()
всего в две строки:
def eq(a, b):
return a == b
Реализация оператора сравнения в виде функции полезна, потому что, в отличие от операторов, функции можно передавать как аргументы при вызове других функций.
Мы воспользуемся этой возможностью и реализуем вспомогательный метод для наших dunder-методов сравнения.
Сначала добавьте следующий фрагмент в начало файла wizcoin.py
. Эти команды импортирования открывают доступ к функциям модуля operator и позволяют проверить, является ли аргумент other нашего метода последовательностью, для чего он сравнивается с collections.abc.Sequence
:
import collections.abc import operator
Затем добавьте следующий фрагмент в конец файла wizcoin.py
:
--snip-- def _comparisonOperatorHelper(self, operatorFunc, other):
❶
"""A helper method for our comparison dunder methods."""
Dunder-методы Python
379
if isinstance(other, WizCoin):
❷
return operatorFunc(self.total, other.total)
elif isinstance(other, (int, float)):
❸
return operatorFunc(self.total, other)
elif isinstance(other, collections.abc.Sequence):
❹
otherValue = (other[0] * 17 * 29) + (other[1] * 29) + other[2]
return operatorFunc(self.total, otherValue)
elif operatorFunc == operator.eq:
return False elif operatorFunc == operator.ne:
return True else:
return NotImplemented def __eq__(self, other): # "Равно"
return self._comparisonOperatorHelper(operator.eq, other)
❺
def __ne__(self, other): # "Не равно"
return self._comparisonOperatorHelper(operator.ne, other)
❻
def __lt__(self, other): # "Меньше"
return self._comparisonOperatorHelper(operator.lt, other)
❼
def __le__(self, other): # "Меньше или равно"
return self._comparisonOperatorHelper(operator.le, other)
❽
def __gt__(self, other): # "Больше"
return self._comparisonOperatorHelper(operator.gt, other)
❾
def __ge__(self, other): # "Больше или равно"
return self._comparisonOperatorHelper(operator.ge, other)
❿
Наши dunder-методы сравнения вызывают метод
_comparisonOperatorHelper()
❶
и передают соответствующую функцию из модуля operator для параметра operatorFunc
. При вызове operatorFunc()
вызывается функция, которая была передана в операторе operatorFunc
— eq()
❺
, ne()
❻
, lt()
❼
, le()
❽
, gt()
❾
или ge()
❿
— из модуля operator
. В противном случае нам пришлось бы продублировать код в
_comparisonOperatorHelper()
в каждом из шести dunder-методов сравнения.
ПРИМЕЧАНИЕ
Функции (или методы), получающие другие функции в аргументах (как _compa- risonOperatorHelper()), называются функциями высшего порядка.
Теперь наши объекты
WizCoin можно сравнивать с другими объектами
WizCoin
❷
, с целыми числами и числами с плавающей точкой
❸
, а также со значениями-по- следовательностями из трех чисел, представляющими значения galleons
, sickles и knuts
❹
. Чтобы увидеть эту возможность в действии, введите следующий фрагмент в интерактивной оболочке:
>>> import wizcoin
>>> purse = wizcoin.WizCoin(2, 5, 10) # Создать объект WizCoin.
>>> tipJar = wizcoin.WizCoin(0, 0, 37) # Создать другой объект WizCoin.
>>> purse.total, tipJar.total # Проверить значение в кнатах.
380
Глава 17.ООП в Python: свойства и dunder-методы
(1141, 37)
>>> purse > tipJar # Сравнение объектов WizCoin при помощи оператора.
True
>>> purse < tipJar
False
>>> purse > 1000 # Сравнение с целым числом.
True
>>> purse <= 1000
False
>>> purse == 1141
True
>>> purse == 1141.0 # Сравнение с числом с плавающей точкой.
True
>>> purse == '1141' # Объект WizCoin не равен никакому строковому значению.
False
>>> bagOfKnuts = wizcoin.WizCoin(0, 0, 1141)
>>> purse == bagOfKnuts
True
>>> purse == (2, 5, 10) # Возможно сравнение с кортежем из 3 целых чисел.
True
>>> purse >= [2, 5, 10] # Возможно сравнение со списком из 3 целых чисел.
True
>>> purse >= ['cat', 'dog'] # Должно привести к ошибке.
Traceback (most recent call last):
File "
File "C:\Users\Al\Desktop\wizcoin.py", line 265, in __ge__
return self._comparisonOperatorHelper(operator.ge, other)
File "C:\Users\Al\Desktop\wizcoin.py", line 237, in _
comparisonOperatorHelper otherValue = (other[0] * 17 * 29) + (other[1] * 29) + other[2]
IndexError: list index out of range
Наш вспомогательный метод вызывает isinstance(other,
collections.abc.
Sequence)
, чтобы проверить, имеет ли other тип данных последовательности, на- пример кортеж или список. Обеспечив возможность сравнения объектов
WizCoin с последовательностями, можно писать код вида purse
>=
[2,
5,
10]
для быстрого сравнения.
Не существует отраженных dunder-методов сравнения (таких как
__req__()
или
__rne__()
), которые вам было бы необходимо реализовать. Вместо этого
__lt__()
и
__gt__()
отражают друг друга,
__le__()
и
__ge__()
отражают друг друга, а
__eq__()
и
__ne__()
отражают сами себя. Дело в том, что следующие отношения остаются истинными независимо от значений в левой и правой части оператора:
purse
>
[2,
5,
10]
то же, что
[2,
5,
10]
<
purse purse
>=
[2,
5,
10]
то же, что
[2,
5,
10]
<=
purse purse
==
[2,
5,
10]
то же, что
[2,
5,
10]
==
purse purse
!=
[2,
5,
10]
то же, что
[2,
5,
10]
!=
purse
Dunder-методы Python
381
СРАВНЕНИЯ ПОСЛЕДОВАТЕЛЬНОСТЕЙ
При сравнении двух объектов встроенных типов последовательностей (таких, как строки, списки или кортежи) Python назначает более высокий приори- тет более ранним элементам последовательности. Другими словами, более поздние элементы сравниваются только в том случае, если более ранние элементы имеют равные значения. Например, введите следующий фрагмент в интерактивной оболочке:
>>> 'Azriel' < 'Zelda'
True
>>> (1, 2, 3) > (0, 8888, 9999)
True
Строка 'Azriel' предшествует строке 'Zelda' (то есть меньше нее), потому что 'A' предшествует 'Z'. Кортеж (1, 2, 3) следует после (0, 8888, 9999) (то есть больше него), потому что 1 больше 0. А теперь введите следующий фрагмент в интерактивной оболочке:
>>> 'Azriel' < 'Aaron'
False
>>> (1, 0, 0) > (1, 0, 9999)
False
Строка 'Azriel' не предшествует 'Aaron', потому что, хотя элемент 'A' в 'Azriel' равен 'A' в 'Aaron', следующий элемент 'z' в 'Azriel' не предшествует 'a' в 'Aaron'. То же относится к кортежам (1, 0, 0) и (1, 0, 9999): первые два элемента каждого кортежа равны, поэтому третий элемент (0 и 9999 соот- ветственно) определяет, что (1, 0, 0) предшествует (1, 0, 9999).
Таким образом, нам приходится принять проектировочное решение относи- тельно класса WizCoin. Должен ли объект WizCoin(0, 0, 9999) предшествовать объекту WizCoin(1, 0, 0) или же следовать после него? Если количество галле- онов более значимо, чем количество сиклей или кнатов, то объект WizCoin(0,
0, 9999) должен предшествовать WizCoin(1, 0, 0). А может, объекты должны сравниваться на основании их значений кнатов? Тогда объект WizCoin(0, 0,
9999) (равно 9999 кнатам) должен следовать после WizCoin(1, 0, 0) (равно
493 кнатам). В программе wizcoin.py я решил использовать значение объекта в кнатах, потому что такое поведение соответствует принципам сравнения объектов WizCoin с целыми числами и числами с плавающей точкой. Вам придется неоднократно принимать подобные решения при проектировании собственных классов.
382
Глава 17.ООП в Python: свойства и dunder-методы
После того как вы реализуете dunder-методы сравнения, функция Python sort()
автоматически использует их для сортировки объектов. Введите следующий фраг- мент в интерактивной оболочке:
>>> import wizcoin
>>> oneGalleon = wizcoin.WizCoin(1, 0, 0) # 493 кната.
>>> oneSickle = wizcoin.WizCoin(0, 1, 0) # 29 кнатов.
>>> oneKnut = wizcoin.WizCoin(0, 0, 1) # 1 кнат.
>>> coins = [oneSickle, oneKnut, oneGalleon, 100]
>>> coins.sort() # Отсортировать по возрастанию.
>>> coins
[WizCoin(0, 0, 1), WizCoin(0, 1, 0), 100, WizCoin(1, 0, 0)]
В табл. 17.3 приведен полный список доступных dunder-методов сравнения.
Таблица 17.3. Dunder-методы сравнения и функции модулей operator
Dunder-метод
Операция
Оператор сравнения Функция модуля
operator
__eq__()
Равно
==
operator.eq()
__ne__()
Не равно
!=
operator.ne()
__lt__()
Меньше
<
operator.lt()
__le__()
Меньше или равно
<=
operator.le()
__gt__()
Больше
>
operator.gt()
__ge__()
Больше или равно
>=
operator.ge()
Реализация этих методов доступна на https://autbor.com/wizcoinfull. Полное опи- сание dunder-методов сравнения доступно в документации Python на https://docs.
python.org/3/reference/datamodel.html#object.__lt__.
Dunder-методы сравнения позволяют использовать операторы сравнения Python с объектами вашего класса (вместо того, чтобы создавать ваши собственные мето- ды). Если вы создаете методы с именами вида equals()
или isGreaterThan()
, это не питонический стиль и признак того, что вам стоит использовать dunder-методы сравнения.
Итоги
В языке Python объектно-ориентированные средства реализуются не так, как в других языках (например, Java или C++). Вместо явного определения
Итоги
383
getter- и setter-методов в Python используются свойства, которые позволяют про- верять атрибуты или делать их доступными только для чтения.
Python также позволяет перегружать операторы при помощи dunder-методов, имена которых начинаются и заканчиваются двойными символами подчеркивания
__
Основные математические операторы перегружаются числовыми и отраженными числовыми dunder-методами. Эти методы дают возможность использовать встро- енные операторы Python для работы с объектами созданных вами классов. Если методы не способны обработать тип данных объекта в другой части оператора, они возвращают встроенное значение
NotImplemented
. Такие методы создают и возвра- щают новые объекты, тогда как dunder-методы присваивания на месте (которые перегружают расширенные операторы присваивания) изменяют объект на месте.
Dunder-методы сравнения не только реализуют шесть операторов сравнения Python для объектов, но и позволяют функции Python sort()
сортировать объекты ваших классов. Для реализации этих dunder-методов можно воспользоваться функциями eq()
, ne()
, lt()
, le()
, gt()
и ge()
модуля operator
Свойства и dunder-методы помогают писать классы, которые хорошо читаются и по- следовательно работают. Они избавят вас от необходимости писать значительный объем шаблонного кода, что обязательно в других языках (таких как Java). Если вам захочется больше узнать о написании питонического кода, более подробно о некоторых концепциях этой главы (и не только) рассказано в паре докладов
Рэймонда Хеттингера (Raymond Hettinger) на PyCon: «Transforming Code into
Beautiful, Idiomatic Python» (https://youtu.be/OSGv2VnC0go/) и «Beyond PEP 8 —
Best Practices for Beautiful, Intelligible Code» (https://youtu.be/wf-BqAjZb8M/).
Об эффективном использовании Python еще можно сказать очень много. В кни- гах «Fluent Python» (O’Reilly Media, 2021) Лучано Рамальо (Luciano Ramalho)
1
и «Effective Python» (Addison-Wesley Professional, 2019) Бретта Слаткина (Brett
Slatkin)
2
более подробно рассказывается о синтаксисе и передовых практиках
Python. Эти книги стоит прочитать каждому, кто хочет больше знать о Python.
1
Рамальо Л. Python. К вершинам мастерства.
2
Слаткин Б. Секреты Python. 59 рекомендаций по написанию эффективного кода.
Эл Свейгарт
Python. Чистый код для продолжающих
Перевел с английского Е. Матвеев
Руководитель дивизиона
Ю. Сергиенко
Ведущий редактор
Е. Строганова
Литературный редактор
Ю. Леонова
Художественный редактор
В. Мостипан
Корректоры
С. Беляева, М. Молчанова
Верстка
Л. Егорова
Изготовлено в России. Изэготовитель: ООО «Прогресс книга».
Место нахождения и фактический адрес: 194044, Россия, г. Санкт-Петербург,
Б. Сампсониевский пр., д. 29А, пом. 52. Тел.: +78127037373.
Дата изготовления: 07.2022. Наименование: книжная продукция. Срок годности: не ограничен.
Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12 — Книги печатные профессиональные, технические и научные.
Импортер в Беларусь: ООО «ПИТЕР М», 220020, РБ, г. Минск, ул. Тимирязева, д. 121/3, к. 214, тел./факс: 208 80 01.
Подписано в печать 27.05.22. Формат 70×100/16. Бумага офсетная. Усл. п. л. 30,960. Тираж 1000. Заказ 0000.