Магические методы python и __getattr__

Дополнение 1: Как вызывать магические методы

Магический метод Когда он вызывается (пример) Объяснение
вызывается при создании экземпляра
вызывается при создании экземпляра
, , etc. Вызывается для любого сравнения
Унарный знак плюса
Унарный знак минуса
Побитовая инверсия
Преобразование, когда объект используется как индекс
, Булевое значение объекта
Пытаются получить несуществующий атрибут
Присвоение любому атрибуту
Удаление атрибута
Получить любой атрибут
Получение элемента через индекс
Присвоение элементу через индекс
Удаление элемента через индекс
Итерация
, Проверка принадлежности с помощью
«Вызов» экземпляра
оператор менеджеров контекста
оператор менеджеров контекста
Сериализация
Сериализация

8.3.8. UserList objects¶

This class acts as a wrapper around list objects. It is a useful base class
for your own list-like classes which can inherit from them and override
existing methods or add new ones. In this way, one can add new behaviors to
lists.

The need for this class has been partially supplanted by the ability to
subclass directly from ; however, this class can be easier
to work with because the underlying list is accessible as an attribute.

class (list)

Class that simulates a list. The instance’s contents are kept in a regular
list, which is accessible via the attribute of
instances. The instance’s contents are initially set to a copy of list,
defaulting to the empty list . list can be any iterable, for
example a real Python list or a object.

In addition to supporting the methods and operations of mutable sequences,
instances provide the following attribute:

A real object used to store the contents of the
class.

Subclassing requirements: Subclasses of are expected to
offer a constructor which can be called with either no arguments or one
argument. List operations which return a new sequence attempt to create an
instance of the actual implementation class. To do so, it assumes that the
constructor can be called with a single parameter, which is a sequence object
used as a data source.

Краткий экскурс в модель данных Python

Итак, все мы знаем, что все в Python является объектом, и не секрет, что для каждого объекта существует некий класс, которым он был порожден, например:

Тип объекта или же класс, которым объект был порожден, можно определить с помощью встроенной функции , которая имеет достаточно интересную сигнатуру вызова (о ней речь пойдет немного позже). Такого же эффекта можно добиться, если вывести атрибут у любого объекта.

Итак, для создания функций служит некий встроенный класс . Посмотрим, что мы сможем сделать с его помощью. Для этого возьмем заготовку из встроенного модуля :

Как мы видим, любая функция в Python – это экземпляр описанного выше класса. Давайте теперь попробуем создать новую функцию, не прибегая к её объявлению через . Для этого нам потребуется научиться создавать объекты кода с помощью встроенной в интерпретатор функции :

Отлично! С помощью мета-инструментов мы научились создавать функции «на лету», однако на практике подобное знание используется редко. Теперь давайте взглянем, как создаются объекты-классы и объекты-экземпляры этих классов:

Вполне очевидно, что класс используется для создания экземпляра , намного интереснее посмотреть на класс , который используется для создания самого класса . Вот здесь мы и обратимся ко второму варианту вызова встроенной функции , которая по совместительству является метаклассом для любого класса в Python. Метакласс по определению – это класс, экземпляром которого является другой класс. Метаклассы позволяют нам настраивать процесс создания класса и частично управлять процессом создания экземпляра класса.

Согласно документации, второй вариант сигнатуры – возвращает новый тип данных или, если по-простому – новый класс, причем атрибут станет атрибутом у возвращенного класса, – список классов-родителей будет доступен как , ну а – dict-like объект, содержащий все атрибуты и методы класса, перейдет в . Принцип работы функции можно описать в виде простого псевдокода на Python:

Посмотрим, как можно, используя только вызов , сконструировать совершенно новый класс:

Как видим, нам не требуется использовать ключевое слово , чтобы создать новый класс, функция справляется и без этого, теперь давайте рассмотрим пример посложнее:

Как видно из примеров выше, описание классов и функций с помощью ключевых слов и – это всего лишь синтаксический сахар и любые типы объектов можно создавать обычными вызовами встроенных функций. А теперь, наконец, поговорим о том, как можно использовать динамическое создание классов в реальных проектах.

Переопределение операторов на произвольных классах

имели в виду

Магические методы сравнения

  • Самый базовый из методов сравнения. Он, в действительности, определяет поведение для всех операторов сравнения (>, ==, !=, итд.), но не всегда так, как вам это нужно (например, если эквивалентность двух экземпляров определяется по одному критерию, а то что один больше другого по какому-нибудь другому). должен вернуть отрицательное число, если , ноль, если , и положительное число в случае . Но, обычно, лучше определить каждое сравнение, которое вам нужно, чем определять их всех в . Но может быть хорошим способом избежать повторений и увеличить ясность, когда все необходимые сравнения оперерируют одним критерием.

  • Определяет поведение оператора равенства, .

  • Определяет поведение оператора неравенства, .

  • Определяет поведение оператора меньше, .

  • Определяет поведение оператора больше, .

  • Определяет поведение оператора меньше или равно, .

  • Определяет поведение оператора больше или равно, .

Унарные операторы и функции

  • Определяет поведение для унарного плюса ()

  • Определяет поведение для отрицания()

  • Определяет поведение для встроенной функции .

  • Определяет поведение для инвертирования оператором . Для объяснения что он делает смотри .

  • Определяет поведение для встроенной функции . это число знаков после запятой, до которого округлить.

  • Определяет поведение для , то есть, округления до ближайшего меньшего целого.

  • Определяет поведение для , то есть, округления до ближайшего большего целого.

  • Определяет поведение для , то есть, обрезания до целого.

Обычные арифметические операторы

  • Сложение.

  • Вычитание.

  • Умножение.

  • Целочисленное деление, оператор .

  • Деление, оператор .

  • Правильное деление. Заметьте, что это работает только когда используется .
  • Остаток от деления, оператор .

  • Определяет поведение для встроенной функции .

  • Возведение в степень, оператор .

  • Двоичный сдвиг влево, оператор .

  • Двоичный сдвиг вправо, оператор .

  • Двоичное И, оператор .

  • Двоичное ИЛИ, оператор .

  • Двоичный xor, оператор .

Отражённые арифметические операторы

  • Отражённое сложение.

  • Отражённое вычитание.

  • Отражённое умножение.

  • Отражённое целочисленное деление, оператор .

  • Отражённое деление, оператор .

  • Отражённое правильное деление. Заметьте, что работает только когда используется .

  • Отражённый остаток от деления, оператор .

  • Определяет поведение для встроенной функции , когда вызывается .

  • Отражённое возведение в степерь, оператор .

  • Отражённый двоичный сдвиг влево, оператор .

  • Отражённый двоичный сдвиг вправо, оператор .

  • Отражённое двоичное И, оператор .

  • Отражённое двоичное ИЛИ, оператор .

  • Отражённый двоичный xor, оператор .

Составное присваивание

  • Сложение с присваиванием.

  • Вычитание с присваиванием.

  • Умножение с присваиванием.

  • Целочисленное деление с присваиванием, оператор .

  • Деление с присваиванием, оператор .

  • Правильное деление с присваиванием. Заметьте, что работает только если используется .
  • Остаток от деления с присваиванием, оператор .

  • Возведение в степерь с присваиванием, оператор .

  • Двоичный сдвиг влево с присваиванием, оператор .

  • Двоичный сдвиг вправо с присваиванием, оператор .

  • Двоичное И с присваиванием, оператор .

  • Двоичное ИЛИ с присваиванием, оператор .

  • Двоичный xor с присваиванием, оператор .

Магические методы преобразования типов

  • Преобразование типа в int.

  • Преобразование типа в long.

  • Преобразование типа в float.

  • Преобразование типа в комплексное число.

  • Преобразование типа в восьмеричное число.

  • Преобразование типа в шестнадцатиричное число.

  • Преобразование типа к int, когда объект используется в срезах (выражения вида ). Если вы определяете свой числовый тип, который может использоваться как индекс списка, вы должны определить .

  • Вызывается при . Должен вернуть своё значение, обрезанное до целочисленного типа (обычно long).

  • Метод для реализации арифметики с операндами разных типов. должен вернуть если преобразование типов невозможно. Если преобразование возможно, он должен вернуть пару (кортеж из 2-х элементов) из и , преобразованные к одному типу.

Перегрузка арифметических операторов

__add__(self, other) — сложение. x + y вызывает x.__add__(y).

__sub__(self, other) — вычитание (x — y).

__mul__(self, other) — умножение (x * y).

__truediv__(self, other) — деление (x / y).

__floordiv__(self, other) — целочисленное деление (x // y).

__mod__(self, other) — остаток от деления (x % y).

__divmod__(self, other) — частное и остаток (divmod(x, y)).

__pow__(self, other) — возведение в степень (x ** y, pow(x, y)).

__lshift__(self, other) — битовый сдвиг влево (x << y).

__rshift__(self, other) — битовый сдвиг вправо (x >> y).

__and__(self, other) — битовое И (x & y).

__xor__(self, other) — битовое ИСКЛЮЧАЮЩЕЕ ИЛИ (x ^ y).

__or__(self, other) — битовое ИЛИ (x | y).

Пойдём дальше.

__radd__(self, other),

__rsub__(self, other),

__rmul__(self, other),

__rtruediv__(self, other),

__rfloordiv__(self, other),

__rmod__(self, other),

__rdivmod__(self, other),

__rpow__(self, other),

__rlshift__(self, other),

__rrshift__(self, other),

__rand__(self, other),

__rxor__(self, other),

__ror__(self, other) — делают то же самое, что и арифметические операторы, перечисленные выше, но для аргументов, находящихся справа, и только в случае, если для левого операнда не определён соответствующий метод.

Например, операция x + y будет сначала пытаться вызвать x.__add__(y), и только в том случае, если это не получилось, будет пытаться вызвать y.__radd__(x). Аналогично для остальных методов.

Идём дальше.

__iadd__(self, other) — +=.

__isub__(self, other) — -=.

__imul__(self, other) — *=.

__itruediv__(self, other) — /=.

__ifloordiv__(self, other) — //=.

__imod__(self, other) — %=.

__ipow__(self, other) — **=.

__ilshift__(self, other) — <<=.

__irshift__(self, other) — >>=.

__iand__(self, other) — &=.

__ixor__(self, other) — ^=.

__ior__(self, other) — |=.

__neg__(self) — унарный -.

__pos__(self) — унарный +.

__abs__(self) — модуль (abs()).

__invert__(self) — инверсия (~).

__complex__(self) — приведение к complex.

__int__(self) — приведение к int.

__float__(self) — приведение к float.

__round__(self) — округление.

__enter__(self), __exit__(self, exc_type, exc_value, traceback) — реализация менеджеров контекста.

Рассмотрим некоторые из этих методов на примере двухмерного вектора, для которого переопределим некоторые методы:

import math

class Vector2D
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Vector2D({}, {})'.format(self.x, self.y)

    def __str__(self):
        return '({}, {})'.format(self.x, self.y)

    def __add__(self, other):
        return Vector2D(self.x + other.x, self.y + other.y)

    def __iadd__(self, other):
        self.x += other.x
        self.y += other.y
        return self

    def __sub__(self, other):
        return Vector2D(self.x - other.x, self.y - other.y)

    def __isub__(self, other):
        self.x -= other.x
        self.y -= other.y
        return self

    def __abs__(self):
        return math.hypot(self.x, self.y)

    def __bool__(self):
        return self.x !=  or self.y != 

    def __neg__(self):
        return Vector2D(-self.x, -self.y)

>>> x = Vector2D(3, 4)
>>> x
Vector2D(3, 4)
>>> print(x)
(3, 4)
>>> abs(x)
5.0
>>> y = Vector2D(5, 6)
>>> y
Vector2D(5, 6)
>>> x + y
Vector2D(8, 10)
>>> x - y
Vector2D(-2, -2)
>>> -x
Vector2D(-3, -4)
>>> x += y
>>> x
Vector2D(8, 10)
>>> bool(x)
True
>>> z = Vector2D(, )
>>> bool(z)
False
>>> -z
Vector2D(, )

В заключение хочу сказать, что перегрузка специальных методов — вещь хорошая, но не стоит ей слишком злоупотреблять. Перегружайте их только тогда, когда вы уверены в том, что это поможет пониманию программного кода.

Использование модуля pickle на своих объектах

протокол

Сериализация собственных объектов.

  • Если вы хотите, чтобы после десериализации вашего класса был вызыван , вы можете определить , который должен вернуть кортеж аргументов, который будет отправлен в . Заметьте, что этот метод работает только с классами старого стиля.

  • Для классов нового стиля вы можете определить, какие параметры будут переданы в во время десериализации. Этот метод так же должен вернуть кортеж аргументов, которые будут отправлены в .

  • Вместо стандартного атрибута , где хранятся атрибуты класса, вы можете вернуть произвольные данные для сериализации. Эти данные будут переданы в во время десериализации.

  • Если во время десериализации определён , то данные объекта будут переданы сюда, вместо того чтобы просто записать всё в . Это парный метод для : когда оба определены, вы можете представлять состояние вашего объекта так, как вы только захотите.

  • Если вы определили свой тип (с помощью Python’s C API), вы должны сообщить Питону как его сериализовать, если вы хотите, чтобы он его сериализовал. вызывается когда сериализуется объект, в котором этот метод был определён. Он должен вернуть или строку, содержащую имя глобальной переменной, содержимое которой сериализуется как обычно, или кортеж. Кортеж может содержать от 2 до 5 элементов: вызываемый объект, который будет вызван, чтобы создать десериализованный объект, кортеж аргументов для этого вызываемого объекта, данные, которые будут переданы в (опционально), итератор списка элементов для сериализации (опционально) и итератор словаря элементов для сериализации (опционально).

  • Иногда полезно знать версию протокола, реализуя . И этого можно добиться, реализовав вместо него . Если реализован, то предпочтение при вызове отдаётся ему (вы всё-равно должны реализовать для обратной совместимости).

Динамическое создание форм и валидаторов

Иногда нам требуется провалидировать информацию от пользователя или из других внешних источников согласно заранее известной схеме данных. Например, мы хотим изменять форму логина пользователя из админки – удалять и добавлять поля, менять стратегию их валидации и т.д.

Для иллюстрации, попробуем динамически создать Django-форму, описание схемы которой хранится в следующем формате:

Теперь на основе описания выше создадим набор полей и новую форму с помощью уже известной нам функции :

Супер! Теперь можно передать созданную форму в шаблон и отрендерить ее для пользователя. Такой же подход можно использовать и с другими фреймворками для валидации и представления данных (DRF Serializers, marshmallow и другие).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector