Уроки Питон №16/20. Объектная модель (sq = [i**2 for i in range(1, 6)]) в Питон
Урок 20. Объектная модель в Python
В рамках данного урока поговорим об объектной модели Python: в чем ее особенность и как ее можно использовать в своей работе.
Что такое объектная модель Python?
Самым лаконичным ответом на вопрос: “что такое объектная модель Python“, будет цитата из википедии, которую также приводит в своей книге Лучано Рамальо: the properties of objects in general in a specific computer programming language, technology, notation or methodology that uses them. Что можно перевести как “общие свойства объектов в конкретном языке программирования, технологии, нотации или методологии”.
Красота объектной модели в Python состоит в том, что если на уровне класса реализовать определенный функционал, то с объектами этого класса можно будет работать используя “питонический стиль” – стиль написания программного кода на Python, который отличается большей выразительностью, читаемостью и понятностью. Такой стиль называют идиоматический – использующий наиболее естественные для данного языка решения.
Для того, чтобы понять о чем идет речь, рассмотрим пример: списковое включение (list comprehensions).
Напишем код, который создает список из квадратов первых пять целых чисел:
sq = []
for i in range(1, 6):
sq.append(i**2)
Теперь напишем этот же код с использованием спискового включения:
sq = [i**2 for i in range(1, 6)]
Не правда ли, второй вариант более лаконичный, красивый и понятный? (Хотя мы не отрицаем, что возможно есть люди, которым больше нравится первый вариант).
Всю мощь объектной модели Python можно осознать через изучение назначения и возможностей специальных методов. Это методы, которые вызываются интерпретатором для выполнения различных операций над объектами. В своем коде, программисты, как правило, напрямую не вызывают эти методы. Объектная модель – это своеобразный API, который вы можете реализовать, чтобы ваши объекты можно было использовать максимально естественно для заданной экосистемы (в нашем случае – Python).
Синонимом “объектной модели” является “модель данных”. В Python документации есть целый раздел, посвященный этой теме https://docs.python.org/3/reference/datamodel.html.
Специальные методы
Рассмотрим некоторые из специальных методов (самые интересные, как нам показалось). Более подробную информацию о них вы можете найти в соответствующей документации по Python.
__repr__()
Строковое представление объекта, позволяющее восстановить объект. Например, если вы пишете игру и у вас есть объект “Персонаж”, которому при его создании присваивается имя и пол, то метод __repr__() может вернуть следующую строку “Person(“John”, “man”)”. Эта информация позволит вам заново построить объект.
__str__()
Строковое представление объекта, используемое в функциях format() и print(). В отличии от __repr__(), в ней, при реализации, мы стараемся создать наиболее информативное представление объекта.
__bytes__()
Метод возвращает объект типа bytes – представление текущего объекта в виде набора байт.
__bool__()
Возвращает True или False.
__call__()
Позволяет использовать объект как функцию, т.е. его можно вызвать.
__len__()
Чаще всего реализуется в коллекциях и сходными с ними по логике работы типами, которые позволяют хранить наборы данных. Для списка (list) __len__() возвращает количество элементов в списке, для строки – количество символов в строке. Вызывается функцией len(), встроенной в язык Python.
Функции сравнения
Функция |
Операция |
Функция |
Операция |
Функция |
Операция |
__lt__() |
< |
__le__() |
<= |
__eq__() |
== |
__ne__() |
!= |
__gt__() |
> |
__ge__() |
>= |
Эти функции используются, если объекты можно сравнивать между собой, как числа. Синтаксис работы с ними выглядит следующим образом: x.__lt__(y), что соответствует записи x < y.
Функции, эмулирующие числовые типы
Арифметические операции
Функция |
Операция |
Описание / пример |
__add__() |
+ |
Сложение |
__sub__() |
– |
Вычитание |
__mul__() |
* |
Умножение |
__truediv__() |
/ |
Деление |
__floordiv__() |
// |
Целая часть от деления |
__mod__() |
% |
Остаток от деления |
__pow__() |
** |
Возведение в степень |
Помимо указанных в таблице, в объектной модели Python, есть методы, позволяющие выполнять инверсные арифметические операции (случай, когда объекты, над которыми производится действие меняются местами), имена таких методов совпадают с перечисленными выше с добавлением символа r перед именем (примеры: __radd__(), __rsub__()). Методы для арифметических операции присваивания, таких как *=, += и т.п., имеют имена сходные с табличными с добавлением символа i перед именем (примеры: __iadd__(), __isub__()).
Унарные операции
Функция |
Операция |
Описание / пример |
__neg__() |
– |
Отрицательное значение |
__pos__() |
+ |
Положительное значение |
__abs__() |
abs() |
Модуль числа |
Битовые операции
Функция |
Операция |
Описание / пример |
__lshift__() |
<< |
Поразрядный сдвиг влево |
__rshift__() |
>> |
Поразрядный сдвиг вправо |
__and__() |
& |
Логическое И |
__or__() |
| |
Логическое ИЛИ |
__xor__() |
^ |
Исключающее ИЛИ |
__invert__() |
~ |
Инверсия |
Для битовых операций, также как для арифметических, предполагается возможность реализовывать инверсные операции и битовые операции присваивания.
Примеры
Для иллюстрации использования возможностей объектной модели Python приведем канонический пример с классом, в рамках которого реализуется поддержка операций над векторами.
class Vector():
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, v):
return Vector(self.x + v.x, self.y + v.y)
def __sub__(self, v):
return Vector(self.x - v.x, self.y - v.y)
def __repr__(self):
return "Vector({}, {})".format(self.x, self.y)
def __str__(self):
return "({}, {})".format(self.x, self.y)
def __abs__(self):
return (self.x**2 + self.y**2)**0.5
if __name__ == "__main__":
v1 = Vector(3, 4)
print("vector v1 = {}".format(v1))
v2 = Vector(5, 7)
print("vector v2 = {}".format(v2))
print("v1 + v2 = {}".format(v1 + v2))
print("v2 - v1 = {}".format(v2 - v1))
print("|v1| = {}".format(abs(v1)))
Запустив эту программу, вы увидите следующее:
>python object_model.py
vector v1 = (3, 4)
vector v2 = (5, 7)
v1 + v2 = (8, 11)
v2 - v1 = (2, 3)
|v1| = 5.0
Операции сложения и вычитании векторов реализованы в методах __add__() и __sub__(). Модуль вектора – в __abs__(). Функция print(), если ей передать объект типа Vector, в первую очередь вызовет метод __str__(), если этого метода не будет, то вызовется __repr__().
Для эксперимента можете удалить метод __str__() и получите следующий результат:
>python object_model.py
vector v1 = Vector(3, 4)
vector v2 = Vector(5, 7)
v1 + v2 = Vector(8, 11)
v2 - v1 = Vector(2, 3)
|v1| = 5.0
Вот ещё один пример, который демонстрирует работу с операторами сравнения:
class UserLevel():
us_dict = {"Admin":4, "Operator":3, "Dispatcher":2, "Guest":1}
def __init__(self, user_level):
if user_level in self.us_dict:
self.u_level = user_level
else:
print("Wrong user type!")
self.u_level = "Guest"
def diff(self, level1, level2):
return self.us_dict[level1] - self.us_dict[level2]
def __lt__(self, user):
return True if self.diff(self.u_level, user.u_level) < 0 else False
def __le__(self, user):
return True if self.diff(self.u_level, user.u_level) <= 0 else False
def __eq__(self, user):
return True if self.diff(self.u_level, user.u_level) == 0 else False
def __ge__(self, user):
return True if self.diff(self.u_level, user.u_level) >= 0 else False
def __gt__(self, user):
return True if self.diff(self.u_level, user.u_level) > 0 else False
def __repr__(self):
return "UserLevel({})".format(self.u_level)
def __str__(self):
return self.u_level
if __name__ == "__main__":
need_level = UserLevel("Operator")
print("need level: {}".format(need_level))
user_level = UserLevel("Dispatcher")
print("user: {}".format(user_level))
if user_level >= need_level:
print("Access enabled!")
else:
print("Access denied!")
В этом примере реализован класс UserLevel, который используется для хранения уровня доступа. Объекты этого типа можно сравнивать между собой, используя классические операции сравнения, поддержка этого функционала реализуется через методы, перечисленные в блоке Битовые операции раздела Специальные методы этой статьи.
Что еще почитать на эту тему?
На эту тему интересно написано у Лучано Рамальо в книге “Python. К вершинам мастерства”. Дополнительно рекомендуется обратить внимание на “Python in a Nutshell” Алекса Мартелли и “Python Essential Reference” Девида Бизли.