Руководство по магическим методам в питоне

Руководство по магическим методам в Питоне

Время на прочтение
28 мин

Количество просмотров 482K

Это перевод 1.17 версии руководства от Rafe Kettler.

Содержание

  1. Вступление
  2. Конструирование и инициализация
  3. Переопределение операторов на произвольных классах
    • Магические методы сравнения
    • Числовые магический методы
  4. Представление своих классов
  5. Контроль доступа к атрибутам
  6. Создание произвольных последовательностей
  7. Отражение
  8. Вызываемые объекты
  9. Менеджеры контекста
  10. Абстрактные базовые классы
  11. Построение дескрипторов
  12. Копирование
  13. Использование модуля pickle на своих объектах
  14. Заключение
  15. Приложение 1: Как вызывать магические методы
  16. Приложение 2: Изменения в Питоне 3

Вступление

Что такое магические методы? Они всё в объектно-ориентированном Питоне. Это специальные методы, с помощью которых вы можете добавить в ваши классы «магию». Они всегда обрамлены двумя нижними подчеркиваниями (например, __init__ или __lt__). Ещё, они не так хорошо документированны, как хотелось бы. Все магические методы описаны в документации, но весьма беспорядочно и почти безо всякой организации. Поэтому, чтобы исправить то, что я воспринимаю как недостаток документации Питона, я собираюсь предоставить больше информации о магических методах, написанной на понятном языке и обильно снабжённой примерами. Надеюсь, это руководство вам понравится. Используйте его как обучающий материал, памятку или полное описание. Я просто постарался как можно понятнее описать магические методы.

Конструирование и инициализация.

Всем известен самый базовый магический метод, __init__. С его помощью мы можем инициализировать объект. Однако, когда я пишу x = SomeClass(), __init__ не самое первое, что вызывается. На самом деле, экземпляр объекта создаёт метод __new__, а затем аргументы передаются в инициализатор. На другом конце жизненного цикла объекта находится метод __del__. Давайте подробнее рассмотрим эти три магических метода:

  • __new__(cls, [...)
    Это первый метод, который будет вызван при инициализации объекта. Он принимает в качестве параметров класс и потом любые другие аргументы, которые будут переданы в __init__. __new__ используется весьма редко, но иногда бывает полезен, в частности, когда класс наследуется от неизменяемого (immutable) типа, такого как кортеж (tuple) или строка. Я не намерен очень детально останавливаться на __new__, так как он не то чтобы очень часто нужен, но этот метод очень хорошо и детально описан в документации.
  • __init__(self, [...)
    Инициализатор класса. Ему передаётся всё, с чем был вызван первоначальный конструктор (так, например, если мы вызываем x = SomeClass(10, 'foo'), __init__ получит 10 и 'foo' в качестве аргументов. __init__ почти повсеместно используется при определении классов.
  • __del__(self)
    Если __new__ и __init__ образуют конструктор объекта, __del__ это его деструктор. Он не определяет поведение для выражения del x (поэтому этот код не эквивалентен x.__del__()). Скорее, он определяет поведение объекта в то время, когда объект попадает в сборщик мусора. Это может быть довольно удобно для объектов, которые могут требовать дополнительных чисток во время удаления, таких как сокеты или файловыве объекты. Однако, нужно быть осторожным, так как нет гарантии, что __del__ будет вызван, если объект продолжает жить, когда интерпретатор завершает работу. Поэтому __del__ не может служить заменой для хороших программистских практик (всегда завершать соединение, если закончил с ним работать и тому подобное). Фактически, из-за отсутствия гарантии вызова, __del__ не должен использоваться почти никогда; используйте его с осторожностью!

    Замечание от переводчика: svetlov отмечает, что здесь автор ошибается, на самом деле __del__ всегда вызывается по завершении работы интерпретатора.

Соединим всё вместе, вот пример __init__ и __del__ в действии:

from os.path import join

class FileObject:
    '''Обёртка для файлового объекта, чтобы быть уверенным в том, что файл будет закрыт при удалении.'''

    def __init__(self, filepath='~', filename='sample.txt'):
        # открыть файл filename в filepath в режиме чтения и записи
        self.file = open(join(filepath, filename), 'r+')

    def __del__(self):
        self.file.close()
        del self.file

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

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

if instance.equals(other_instance):
    # do something

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

if instance == other_instance:
    #do something

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

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

В Питоне уйма магических методов, созданных для определения интуитивного сравнения между объектами используя операторы, а не неуклюжие методы. Кроме того, они предоставляют способ переопределить поведение Питона по-умолчанию для сравнения объектов (по ссылке). Вот список этих методов и что они делают:

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

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

class Word(str):
    '''Класс для слов, определяющий сравнение по длине слов.'''

    def __new__(cls, word):
        # Мы должны использовать __new__, так как тип str неизменяемый
        # и мы должны инициализировать его раньше (при создании)
        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')] # Теперь Word это все символы до первого пробела
        return str.__new__(cls, word)

    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)

Теперь мы можем создать два Word (при помощи Word('foo') и Word('bar')) и сравнить их по длине. Заметьте, что мы не определяли __eq__ и __ne__, так как это приведёт к странному поведению (например, Word('foo') == Word('bar') будет расцениваться как истина). В этом нет смысла при тестировании на эквивалентность, основанную на длине, поэтому мы оставляем стандартную проверку на эквивалентность от str.
Сейчас, кажется, удачное время упомянуть, что вы не должны определять каждый из магических методов сравнения, чтобы полностью охватить все сравнения. Стандартная библиотека любезно предоставляет нам класс-декторатор в модуле functools, который и определит все сравнивающие методы, от вас достаточно определить только __eq__ и ещё один (__gt__, __lt__ и т.п.) Эта возможность доступна начиная с 2.7 версии Питона, но если это вас устраивает, вы сэкономите кучу времени и усилий. Для того, чтобы задействовать её, поместите @total_ordering над вашим определением класса.

Числовые магические методы

Точно так же, как вы можете определить, каким образом ваши объекты будут сравниваться операторами сравнения, вы можете определить их поведение для числовых операторов. Приготовтесь, друзья, их много. Для лучшей организации, я разбил числовые магические методы на 5 категорий: унарные операторы, обычные арифметические операторы, отражённые арифметические операторы (подробности позже), составные присваивания и преобразования типов.

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

Унарные операторы и функции имеют только один операнд — отрицание, абсолютное значение, и так далее.

  • __pos__(self)
    Определяет поведение для унарного плюса (+some_object)
  • __neg__(self)
    Определяет поведение для отрицания(-some_object)
  • __abs__(self)
    Определяет поведение для встроенной функции abs().
  • __invert__(self)
    Определяет поведение для инвертирования оператором ~. Для объяснения что он делает смотри статью в Википедии о бинарных операторах.
  • __round__(self, n)
    Определяет поведение для встроенной функции round(). n это число знаков после запятой, до которого округлить.
  • __floor__(self)
    Определяет поведение для math.floor(), то есть, округления до ближайшего меньшего целого.
  • __ceil__(self)
    Определяет поведение для math.ceil(), то есть, округления до ближайшего большего целого.
  • __trunc__(self)
    Определяет поведение для math.trunc(), то есть, обрезания до целого.

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

Теперь рассмотрим обычные бинарные операторы (и ещё пару функций): +, -, * и похожие. Они, по большей части, отлично сами себя описывают.

  • __add__(self, other)
    Сложение.
  • __sub__(self, other)
    Вычитание.
  • __mul__(self, other)
    Умножение.
  • __floordiv__(self, other)
    Целочисленное деление, оператор //.
  • __div__(self, other)
    Деление, оператор /.
  • __truediv__(self, other)
    Правильное деление. Заметьте, что это работает только когда используется from __future__ import division.
  • __mod__(self, other)
    Остаток от деления, оператор %.
  • __divmod__(self, other)
    Определяет поведение для встроенной функции divmod().
  • __pow__
    Возведение в степень, оператор **.
  • __lshift__(self, other)
    Двоичный сдвиг влево, оператор <<.
  • __rshift__(self, other)
    Двоичный сдвиг вправо, оператор >>.
  • __and__(self, other)
    Двоичное И, оператор &.
  • __or__(self, other)
    Двоичное ИЛИ, оператор |.
  • __xor__(self, other)
    Двоичный xor, оператор ^.

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

Помните как я сказал, что собираюсь остановиться на отражённой арифметике подробнее? Вы могли подумать, что это какая-то большая, страшная и непонятная концепция. На самом деле всё очень просто. Вот пример:

some_object + other

Это «обычное» сложение. Единственное, чем отличается эквивалентное отражённое выражение, это порядок слагаемых:

other + some_object

Таким образом, все эти магические методы делают то же самое, что и их обычные версии, за исключением выполнения операции с other в качестве первого операнда и self в качестве второго. В большинстве случаев, результат отражённой операции такой же, как её обычный эквивалент, поэтому при определении __radd__ вы можете ограничиться вызовом __add__ да и всё. Заметьте, что объект слева от оператора (other в примере) не должен иметь обычной неотражённой версии этого метода. В нашем примере, some_object.__radd__ будет вызван только если в other не определён __add__.

  • __radd__(self, other)
    Отражённое сложение.
  • __rsub__(self, other)
    Отражённое вычитание.
  • __rmul__(self, other)
    Отражённое умножение.
  • __rfloordiv__(self, other)
    Отражённое целочисленное деление, оператор //.
  • __rdiv__(self, other)
    Отражённое деление, оператор /.
  • __rtruediv__(self, other)
    Отражённое правильное деление. Заметьте, что работает только когда используется from __future__ import division.
  • __rmod__(self, other)
    Отражённый остаток от деления, оператор %.
  • __rdivmod__(self, other)
    Определяет поведение для встроенной функции divmod(), когда вызывается divmod(other, self).
  • __rpow__
    Отражённое возведение в степерь, оператор **.
  • __rlshift__(self, other)
    Отражённый двоичный сдвиг влево, оператор <<.
  • __rrshift__(self, other)
    Отражённый двоичный сдвиг вправо, оператор >>.
  • __rand__(self, other)
    Отражённое двоичное И, оператор &.
  • __ror__(self, other)
    Отражённое двоичное ИЛИ, оператор |.
  • __rxor__(self, other)
    Отражённый двоичный xor, оператор ^.

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

В Питоне широко представлены и магические методы для составного присваивания. Вы скорее всего уже знакомы с составным присваиванием, это комбинация «обычного» оператора и присваивания. Если всё ещё непонятно, вот пример:

x = 5
x += 1 # другими словами x = x + 1

Каждый из этих методов должен возвращать значение, которое будет присвоено переменной слева (например, для a += b, __iadd__ должен вернуть a + b, что будет присвоено a). Вот список:

  • __iadd__(self, other)
    Сложение с присваиванием.
  • __isub__(self, other)
    Вычитание с присваиванием.
  • __imul__(self, other)
    Умножение с присваиванием.
  • __ifloordiv__(self, other)
    Целочисленное деление с присваиванием, оператор //=.
  • __idiv__(self, other)
    Деление с присваиванием, оператор /=.
  • __itruediv__(self, other)
    Правильное деление с присваиванием. Заметьте, что работает только если используется from __future__ import division.
  • __imod_(self, other)
    Остаток от деления с присваиванием, оператор %=.
  • __ipow__
    Возведение в степерь с присваиванием, оператор **=.
  • __ilshift__(self, other)
    Двоичный сдвиг влево с присваиванием, оператор <<=.
  • __irshift__(self, other)
    Двоичный сдвиг вправо с присваиванием, оператор >>=.
  • __iand__(self, other)
    Двоичное И с присваиванием, оператор &=.
  • __ior__(self, other)
    Двоичное ИЛИ с присваиванием, оператор |=.
  • __ixor__(self, other)
    Двоичный xor с присваиванием, оператор ^=.

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

Кроме того, в Питоне множество магических методов, предназначенных для определния поведения для встроенных функций преобразования типов, таких как float(). Вот они все:

  • __int__(self)
    Преобразование типа в int.
  • __long__(self)
    Преобразование типа в long.
  • __float__(self)
    Преобразование типа в float.
  • __complex__(self)
    Преобразование типа в комплексное число.
  • __oct__(self)
    Преобразование типа в восьмеричное число.
  • __hex__(self)
    Преобразование типа в шестнадцатиричное число.
  • __index__(self)
    Преобразование типа к int, когда объект используется в срезах (выражения вида [start:stop:step]). Если вы определяете свой числовый тип, который может использоваться как индекс списка, вы должны определить __index__.
  • __trunc__(self)
    Вызывается при math.trunc(self). Должен вернуть своё значение, обрезанное до целочисленного типа (обычно long).
  • __coerce__(self, other)
    Метод для реализации арифметики с операндами разных типов. __coerce__ должен вернуть None если преобразование типов невозможно. Если преобразование возможно, он должен вернуть пару (кортеж из 2-х элементов) из self и other, преобразованные к одному типу.

Представление своих классов

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

  • __str__(self)
    Определяет поведение функции str(), вызванной для экземпляра вашего класса.
  • __repr__(self)
    Определяет поведение функции repr(), вызыванной для экземпляра вашего класса. Главное отличие от str() в целевой аудитории. repr() больше предназначен для машинно-ориентированного вывода (более того, это часто должен быть валидный код на Питоне), а str() предназначен для чтения людьми.
  • __unicode__(self)
    Определяет поведение функции unicode(), вызыванной для экземпляра вашего класса. unicode() похож на str(), но возвращает строку в юникоде. Будте осторожны: если клиент вызывает str() на экземпляре вашего класса, а вы определили только __unicode__(), то это не будет работать. Постарайтесь всегда определять __str__() для случая, когда кто-то не имеет такой роскоши как юникод.
  • __format__(self, formatstr)
    Определяет поведение, когда экземпляр вашего класса используется в форматировании строк нового стиля. Например, "Hello, {0:abc}!".format(a) приведёт к вызову a.__format__("abc"). Это может быть полезно для определения ваших собственных числовых или строковых типов, которым вы можете захотеть предоставить какие-нибудь специальные опции форматирования.
  • __hash__(self)
    Определяет поведение функции hash(), вызыванной для экземпляра вашего класса. Метод должен возвращать целочисленное значение, которое будет использоваться для быстрого сравнения ключей в словарях. Заметьте, что в таком случае обычно нужно определять и __eq__ тоже. Руководствуйтесь следующим правилом: a == b подразумевает hash(a) == hash(b).
  • __nonzero__(self)
    Определяет поведение функции bool(), вызванной для экземпляра вашего класса. Должна вернуть True или False, в зависимости от того, когда вы считаете экземпляр соответствующим True или False.
  • __dir__(self)
    Определяет поведение функции dir(), вызванной на экземпляре вашего класса. Этот метод должен возвращать пользователю список атрибутов. Обычно, определение __dir__ не требуется, но может быть жизненно важно для интерактивного использования вашего класса, если вы переопределили __getattr__ или __getattribute__ (с которыми вы встретитесь в следующей части), или каким-либо другим образом динамически создаёте атрибуты.
  • __sizeof__(self)
    Определяет поведение функции sys.getsizeof(), вызыванной на экземпляре вашего класса. Метод должен вернуть размер вашего объекта в байтах. Он главным образом полезен для классов, определённых в расширениях на C, но всё-равно полезно о нём знать.

Мы почти закончили со скучной (и лишённой примеров) частью руководства по магическим методам. Теперь, когда мы рассмотрели самые базовые магические методы, пришло время перейти к более продвинутому материалу.

Контроль доступа к атрибутам

Многие люди, пришедшие в Питон из других языков, жалуются на отсутствие настоящей инкапсуляции для классов (например, нет способа определить приватные атрибуты с публичными методами доступа). Это не совсем правда: просто многие вещи, связанные с инкапсуляцией, Питон реализует через «магию», а не явными модификаторами для методов и полей. Смотрите:

  • __getattr__(self, name)
    Вы можете определить поведение для случая, когда пользователь пытается обратиться к атрибуту, который не существует (совсем или пока ещё). Это может быть полезным для перехвата и перенаправления частых опечаток, предупреждения об использовании устаревших атрибутов (вы можете всё-равно вычислить и вернуть этот атрибут, если хотите), или хитро возвращать AttributeError, когда это вам нужно. Правда, этот метод вызывается только когда пытаются получить доступ к несуществующему атрибуту, поэтому это не очень хорошее решение для инкапсуляции.
  • __setattr__(self, name, value)
    В отличии от __getattr__, __setattr__ решение для инкапсуляции. Этот метод позволяет вам определить поведение для присвоения значения атрибуту, независимо от того существует атрибут или нет. То есть, вы можете определить любые правила для любых изменений значения атрибутов. Впрочем, вы должны быть осторожны с тем, как использовать __setattr__, смотрите пример нехорошего случая в конце этого списка.
  • __delattr__
    Это то же, что и __setattr__, но для удаления атрибутов, вместо установки значений. Здесь требуются те же меры предосторожности, что и в __setattr__ чтобы избежать бесконечной рекурсии (вызов del self.name в определении __delattr__ вызовет бесконечную рекурсию).
  • __getattribute__(self, name)
    __getattribute__ выглядит к месту среди своих коллег __setattr__ и __delattr__, но я бы не рекомендовал вам его использовать. __getattribute__ может использоваться только с классами нового типа (в новых версиях Питона все классы нового типа, а в старых версиях вы можете получить такой класс унаследовавшись от object). Этот метод позволяет вам определить поведение для каждого случая доступа к атрибутам (а не только к несуществующим, как __getattr__(self, name)). Он страдает от таких же проблем с бесконечной рекурсией, как и его коллеги (на этот раз вы можете вызывать __getattribute__ у базового класса, чтобы их предотвратить). Он, так же, главным образом устраняет необходимость в __getattr__, который в случае реализации __getattribute__ может быть вызван только явным образом или в случае генерации исключения AttributeError. Вы конечно можете использовать этот метод (в конце концов, это ваш выбор), но я бы не рекомендовал, потому что случаев, когда он действительно полезен очень мало (намного реже нужно переопределять поведение при получении, а не при установке значения) и реализовать его без возможных ошибок очень сложно.

Вы можете запросто получить проблему при определении любого метогда, управляющего доступом к атрибутам. Рассмотрим пример:

def __setattr__(self, name, value):
    self.name = value
    # это рекурсия, так как всякий раз, когда любому атрибуту присваивается значение,
    # вызывается  __setattr__().
    # тоесть, на самом деле это равнозначно self.__setattr__('name', value). 
    # Так как метод вызывает сам себя, рекурсия продолжится бесконечно, пока всё не упадёт

def __setattr__(self, name, value):
    self.__dict__[name] = value # присваивание в словарь переменных класса
    # дальше определение произвольного поведения

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

Итак, что мы узнали об управлении доступом к атрибутам? Их не нужно использовать легкомысленно. На самом деле, они имеют склонность к чрезмерной мощи и нелогичности. Причина, по которой они всё-таки существуют, в удволетворении определённого желания: Питон склонен не запрещать плохие штуки полностью, а только усложнять их использование. Свобода первостепенна, поэтому вы на самом деле можете делать всё, что хотите. Вот пример использования методов контроля доступа (заметьте, что мы используем super, так как не все классы имеют атрибут __dict__):

class AccessCounter(object):
    '''Класс, содержащий атрибут value и реализующий счётчик доступа к нему.
    Счётчик увеличивается каждый раз, когда меняется value.'''

    def __init__(self, val):
        super(AccessCounter, self).__setattr__('counter', 0)
        super(AccessCounter, self).__setattr__('value', val)

    def __setattr__(self, name, value):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
        # Не будем делать здесь никаких условий.
        # Если вы хотите предотвратить изменение других атрибутов,
        # выбросьте исключение AttributeError(name)
        super(AccessCounter, self).__setattr__(name, value)

    def __delattr__(self, name):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
        super(AccessCounter, self).__delattr__(name)]

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

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

Протоколы

Теперь, когда речь зашла о создании собственных последовательностей в Питоне, пришло время поговорить о протоколах. Протоколы немного похожи на интерфейсы в других языках тем, что они предоставляют набор методов, которые вы должны реализовать. Однако, в Питоне протоколы абсолютно ни к чему не обязывают и не требуют обязательно реализовать какое-либо объявление. Наверное, они больше похожи на руководящие указания.

Почему мы заговорили о протоколах? Потому, что реализация произвольных контейнерных типов в Питоне влечёт за собой использование некоторых из них. Во-первых, протокол для определения неизменяемых контейнеров: чтобы создать неизменяемый контейнер, вы должны только определить __len__ и __getitem__ (продробнее о них дальше). Протокол изменяемого контейнера требует того же, что и неизменяемого контейнера, плюс __setitem__ и __delitem__. И, наконец, если вы хотите, чтобы ваши объекты можно было перебирать итерацией, вы должны определить __iter__, который возвращает итератор. Этот итератор должен соответствовать протоколу итератора, который требует методов __iter__(возвращает самого себя) и next.

Магия контейнеров

Без дальнейшего промедления, вот магические методы, используемые контейнерами:

  • __len__(self)
    Возвращает количество элементов в контейнере. Часть протоколов для изменяемого и неизменяемого контейнеров.
  • __getitem__(self, key)
    Определяет поведение при доступе к элементу, используя синтаксис self[key]. Тоже относится и к протоколу изменяемых и к протоколу неизменяемых контейнеров. Должен выбрасывать соответствующие исключения: TypeError если неправильный тип ключа и KeyError если ключу не соответствует никакого значения.
  • __setitem__(self, key, value)
    Определяет поведение при присваивании значения элементу, используя синтаксис self[nkey] = value. Часть протокола изменяемого контейнера. Опять же, вы должны выбрасывать KeyError и TypeError в соответсвующих случаях.
  • __delitem__(self, key)
    Определяет поведение при удалении элемента (то есть del self[key]). Это часть только протокола для изменяемого контейнера. Вы должны выбрасывать соответствующее исключение, если ключ некорректен.
  • __iter__(self)
    Должен вернуть итератор для контейнера. Итераторы возвращаются в множестве ситуаций, главным образом для встроенной функции iter() и в случае перебора элементов контейнера выражением for x in container:. Итераторы сами по себе объекты и они тоже должны определять метод __iter__, который возвращает self.
  • __reversed__(self)
    Вызывается чтобы определить поведения для встроенной функции reversed(). Должен вернуть обратную версию последовательности. Реализуйте метод только если класс упорядоченный, как список или кортеж.
  • __contains__(self, item)
    __contains__ предназначен для проверки принадлежности элемента с помощью in и not in. Вы спросите, почему же это не часть протокола последовательности? Потому что когда __contains__ не определён, Питон просто перебирает всю последовательность элемент за элементом и возвращает True если находит нужный.
  • __missing__(self, key)
    __missing__ используется при наследовании от dict. Определяет поведение для для каждого случая, когда пытаются получить элемент по несуществующему ключу (так, например, если у меня есть словарь d и я пишу d["george"] когда "george" не является ключом в словаре, вызывается d.__missing__("george")).

Пример

Для примера, давайте посмотрим на список, который реализует некоторые функциональные конструкции, которые вы могли встретить в других языках (Хаскеле, например).

class FunctionalList:
    '''Класс-обёртка над списком с добавлением некоторой функциональной магии: head,
    tail, init, last, drop, take.'''

    def __init__(self, values=None):
        if values is None:
            self.values = []
        else:
            self.values = values

    def __len__(self):
        return len(self.values)

    def __getitem__(self, key):
        # если значение или тип ключа некорректны, list выбросит исключение
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]

    def __iter__(self):
        return iter(self.values)

    def __reversed__(self):
        return FunctionalList(reversed(self.values))

    def append(self, value):
        self.values.append(value)
    def head(self):
        # получить первый элемент
        return self.values[0]
    def tail(self):
        # получить все элементы после первого
        return self.values[1:]
    def init(self):
        # получить все элементы кроме последнего
        return self.values[:-1]
    def last(self):
        # получить последний элемент
        return self.values[-1]
    def drop(self, n):
        # все элементы кроме первых n
        return self.values[n:]
    def take(self, n):
        # первые n элементов
        return self.values[:n]

Теперь у вас есть полезный (относительно) пример реализации своей собственной последовательности. Существуют, конечно, и куда более практичные реализации произвольных последовательностей, но большое их число уже реализовано в стандартной библиотеке (с батарейками в комплекте, да?), такие как Counter, OrderedDict, NamedTuple.

Отражение

Вы можете контролировать и отражение, использующее встроенные функции isinstance() и issubclass(), определив некоторые магические методы. Вот они:

  • __instancecheck__(self, instance)
    Проверяет, является ли экземлпяр членом вашего класса (isinstance(instance, class), например.
  • __subclasscheck__(self, subclass)
    Проверяет, является наследуется ли класс от вашего класса (issubclass(subclass, class)).

Может показаться, что вариантов полезного использования этих магических методов немного и, возможно, это на самом деле так. Я не хочу тратить слишком много времени на магические методы отражения, не особо они и важные, но они отражают кое-что важное об объектно-ориентированном программировании в Питоне и о Питоне вообще: почти всегда существует простой способ что-либо сделать, даже если надобность в этом «что-либо» возникает очень редко. Эти магические методы могут не выглядеть полезными, но если они вам когда-нибудь понадобятся, вы будете рады вспомнить, что они есть (и для этого вы читаете настоящее руководство!).

Вызываемые объекты

Как вы наверное уже знаете, в Питоне функции являются объектами первого класса. Это означает, что они могут быть переданы в функции или методы так же, как любые другие объекты. Это невероятно мощная особенность.

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

  • __call__(self, [args...])
    Позволяет любому экземпляру вашего класса быть вызванным как-будто он функция. Главным образом это означает, что x() означает то же, что и x.__call__(). Заметьте, __call__ принимает произвольное число аргументов; то есть, вы можете определить __call__ так же как любую другую функцию, принимающую столько аргументов, сколько вам нужно.

__call__, в частности, может быть полезен в классах, чьи экземпляры часто изменяют своё состояние. «Вызвать» экземпляр может быть интуитивно понятным и элегантным способом изменить состояние объекта. Примером может быть класс, представляющий положение некоторого объекта на плоскости:

class Entity:
    '''Класс, описывающий объект на плоскости. "Вызываемый", чтобы обновить позицию объекта.'''

    def __init__(self, size, x, y):
        self.x, self.y = x, y
        self.size = size

    def __call__(self, x, y):
        '''Изменить положение объекта.'''
        self.x, self.y = x, y

    # чик...

Менеджеры контекста

В Питоне 2.5 было представлено новое ключевое слово вместе с новым способом повторно использовать код, ключевое слово with. Концепция менеджеров контекста не являлась новой для Питона (она была реализована раньше как часть библиотеки), но в PEP 343 достигла статуса языковой конструкции. Вы могли уже видеть выражения с with:

with open('foo.txt') as bar:
    # выполнение каких-нибудь действий с bar

Менеджеры контекста позволяют выполнить какие-то действия для настройки или очистки, когда создание объекта обёрнуто в оператор with. Поведение менеджера контекста определяется двумя магическими методами:

  • __enter__(self)
    Определяет, что должен сделать менеджер контекста в начале блока, созданного оператором with. Заметьте, что возвращаемое __enter__ значение и есть то значение, с которым производится работа внутри with.
  • __exit__(self, exception_type, exception_value, traceback)
    Определяет действия менеджера контекста после того, как блок будет выполнен (или прерван во время работы). Может использоваться для контроллирования исключений, чистки, любых действий которые должны быть выполнены незамедлительно после блока внутри with. Если блок выполнен успешно, exception_type, exception_value, и traceback будут установлены в None. В другом случае вы сами выбираете, перехватывать ли исключение или предоставить это пользователю; если вы решили перехватить исключение, убедитесь, что __exit__ возвращает True после того как всё сказано и сделано. Если вы не хотите, чтобы исключение было перехвачено менеджером контекста, просто позвольте ему случиться.

__enter__ и __exit__ могут быть полезны для специфичных классов с хорошо описанным и распространённым поведением для их настройки и очистки ресурсов. Вы можете использовать эти методы и для создания общих менеджеров контекста для разных объектов. Вот пример:

class Closer:
    '''Менеджер контекста для автоматического закрытия объекта вызовом метода close 
    в with-выражении.'''

    def __init__(self, obj):
        self.obj = obj

    def __enter__(self):
        return self.obj # привязка к активному объекту with-блока

    def __exit__(self, exception_type, exception_val, trace):
        try:
           self.obj.close()
        except AttributeError: # у объекта нет метода close
           print 'Not closable.'
           return True # исключение перехвачено

Пример использования Closer с FTP-соединением (сокет, имеющий метод close):

>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...     conn.dir()
...
# output omitted for brevity
>>> conn.dir()
# long AttributeError message, can't use a connection that's closed
>>> with Closer(int(5)) as i:
...     i += 1
...
Not closable.
>>> i
6

Видите, как наша обёртка изящно управляется и с правильными и с неподходящими объектами. В этом сила менеджеров контекста и магических методов. Заметьте, что стандартная библиотека Питона включает модуль contextlib, который включает в себя contextlib.closing() — менеджер контекста, который делает приблизительно то же (без какой-либо обработки случая, когда объект не имеет метода close()).

Абстрактные базовые классы

Смотри http://docs.python.org/2/library/abc.html.

Построение дескрипторов

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

Чтобы класс стал дескриптором, он должен реализовать по крайней мере один метод из __get__, __set__ или __delete__. Давайте рассмотрим эти магические методы:

  • __get__(self, instance, instance_class)
    Определяет поведение при возвращении значения из дескриптора. instance это объект, для чьего атрибута-дескриптора вызывается метод. owner это тип (класс) объекта.
  • __set__(self, instance, value)
    Определяет поведение при изменении значения из дескриптора. instance это объект, для чьего атрибута-дескриптора вызывается метод. value это значение для установки в дескриптор.
  • __delete__(self, instance)
    Определяет поведение для удаления значения из дескриптора. instance это объект, владеющий дескриптором.

Теперь пример полезного использования дескрипторов: преобразование единиц измерения.

class Meter(object):
    '''Дескриптор для метра.'''

    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)

class Foot(object):
    '''Дескриптор для фута.'''

    def __get__(self, instance, owner):
        return instance.meter * 3.2808
    def __set__(self, instance, value):
        instance.meter = float(value) / 3.2808

class Distance(object):
    '''Класс, описывающий расстояние, содержит два дескриптора для футов и
    метров.'''
    meter = Meter()
    foot = Foot()

Копирование

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

  • __copy__(self)
    Определяет поведение copy.copy() для экземпляра вашего класса. copy.copy() возвращает поверхностную копию вашего объекта — это означает, что хоть сам объект и создан заново, все его данные ссылаются на данные оригинального объекта. И при изменении данных нового объекта, изменения будут происходить и в оригинальном.
  • __deepcopy__(self, memodict={})
    Определяет поведение copy.deepcopy() для экземпляров вашего класса. copy.deepcopy() возвращает глубокую копию вашего объекта — копируются и объект и его данные. memodict это кэш предыдущих скопированных объектов, он предназначен для оптимизации копирования и предотвращения бесконечной рекурсии, когда копируются рекурсивные структуры данных. Когда вы хотите полностью скопировать какой-нибудь конкретный атрибут, вызовите на нём copy.deepcopy() с первым параметром memodict.

Когда использовать эти магические методы? Как обычно — в любом случае, когда вам необходимо больше, чем стандартное поведение. Например, если вы пытаетесь скопировать объект, который содержит кэш как словарь (возможно, очень большой словарь), то может быть вам и не нужно копировать весь кэш, а обойтись всего одним в общей памяти объектов.

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

Pickle это модуль для сериализации структур данных Питона и он может быть невероятно полезен, когда вам нужно сохранить состояние какого-либо объекта и восстановить его позже (обычно, в целях кэширования). Кроме того, это ещё и отличный источник переживаний и путаницы.

Сериализация настолько важна, что кроме своего модуля (pickle) имеет и свой собственный протокол и свои магические методы. Но для начала о том, как сериализовать с помощью pickle уже существующие типы данных (спокойно пропускайте, если вы уже знаете).

Вкратце про сериализацию

Давайте погрузимся в сериализацию. Допустим, у вас есть словарь, который вы хотите сохранить и восстановить позже. Вы должны записать его содержимое в файл, тщательно убедившись, что пишете с правильным синтаксисом, потом восстановить его, или выполнив exec(), или прочитав файл. Но это в лучшем случае рискованно: если вы храните важные данные в тексте, он может быть повреждён или изменён множеством способов, с целью обрушить вашу программу или, вообще, запустить какой-нибудь опасный код на вашем компьютере. Лучше использовать pickle:

import pickle

data = {'foo': [1, 2, 3],
        'bar': ('Hello', 'world!'),
        'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # записать сериализованные данные в jar
jar.close()

И вот, спустя несколько часов, нам снова нужен наш словарь:

import pickle

pkl_file = open('data.pkl', 'rb') # открываем
data = pickle.load(pkl_file) # сохраняем в переменную
print data
pkl_file.close()

Что произошло? Точно то, что и ожидалось. data как-будто всегда тут и была.

Теперь, немного об осторожности: pickle не идеален. Его файлы легко испортить случайно или преднамеренно. Pickle, может быть, безопаснее чем текстовые файлы, но он всё ещё может использоваться для запуска вредоносного кода. Кроме того, он несовместим между разными версиями Питона, поэтому если вы будете распространять свои объекты с помощью pickle, не ожидайте что все люди смогут их использовать. Тем не менее, модуль может быть мощным инструментом для кэширования и других распространённых задач с сериализацией.

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

Модуль pickle не только для встроенных типов. Он может использоваться с каждым классом, реализующим его протокол. Этот протокол содержит четыре необязательных метода, позволяющих настроить то, как pickle будет с ними обращаться (есть некоторые различия для расширений на C, но это за рамками нашего руководства):

  • __getinitargs__(self)
    Если вы хотите, чтобы после десериализации вашего класса был вызыван __init__, вы можете определить __getinitargs__, который должен вернуть кортеж аргументов, который будет отправлен в __init__. Заметьте, что этот метод работает только с классами старого стиля.
  • __getnewargs__(self)
    Для классов нового стиля вы можете определить, какие параметры будут переданы в __new__ во время десериализации. Этот метод так же должен вернуть кортеж аргументов, которые будут отправлены в __new__.
  • __getstate__(self)
    Вместо стандартного атрибута __dict__, где хранятся атрибуты класса, вы можете вернуть произвольные данные для сериализации. Эти данные будут переданы в __setstate__ во время десериализации.
  • __setstate__(self, state)
    Если во время десериализации определён __setstate__, то данные объекта будут переданы сюда, вместо того чтобы просто записать всё в __dict__. Это парный метод для __getstate__: когда оба определены, вы можете представлять состояние вашего объекта так, как вы только захотите.
  • __reduce__(self)
    Если вы определили свой тип (с помощью Python’s C API), вы должны сообщить Питону как его сериализовать, если вы хотите, чтобы он его сериализовал. __reduce__() вызывается когда сериализуется объект, в котором этот метод был определён. Он должен вернуть или строку, содержащую имя глобальной переменной, содержимое которой сериализуется как обычно, или кортеж. Кортеж может содержать от 2 до 5 элементов: вызываемый объект, который будет вызван, чтобы создать десериализованный объект, кортеж аргументов для этого вызываемого объекта, данные, которые будут переданы в __setstate__ (опционально), итератор списка элементов для сериализации (опционально) и итератор словаря элементов для сериализации (опционально).
  • __reduce_ex__(self, protocol)
    Иногда полезно знать версию протокола, реализуя __reduce__. И этого можно добиться, реализовав вместо него __reduce_ex__. Если __reduce_ex__ реализован, то предпочтение при вызове отдаётся ему (вы всё-равно должны реализовать __reduce__ для обратной совместимости).

Пример

Для примера опишем грифельную доску (Slate), которая запоминает что и когда было на ней записано. Впрочем, конкретно эта доска становится чистой каждый раз, когда она сериализуется: текущее значение не сохраняется.

import time

class Slate:
    '''Класс, хранящий строку и лог изменений. И забывающий своё значение после 
    сериализации.'''

    def __init__(self, value):
        self.value = value
        self.last_change = time.asctime()
        self.history = {}

    def change(self, new_value):
        # Изменить значение. Зафиксировать последнее значение в истории. 
        self.history[self.last_change] = self.value
        self.value = new_value
        self.last_change = time.asctime()

    def print_changes(self):
        print 'Changelog for Slate object:'
        for k, v in self.history.items():
            print '%st %s' % (k, v)

    def __getstate__(self):
        # Намеренно не возвращаем self.value or self.last_change.
        # Мы хотим "чистую доску" после десериализации.
        return self.history

    def __setstate__(self, state):
        self.history = state
        self.value, self.last_change = None, None

Заключение

Цель этого руководства донести что-нибудь до каждого, кто его читает, независимо от его опыта в Питоне или объектно-ориентированном программировании. Если вы новичок в Питоне, вы получили ценные знания об основах написания функциональных, элегантных и простых для использования классов. Если вы программист среднего уровня, то вы, возможно, нашли несколько новых приятных идей и стратегий, несколько хороших способов уменьшить количество кода, написанного вами и вашими клиентами. Если же вы Питонист-эксперт, то вы обновили некоторые свои, возможно, подзабытые знания, а может и нашли парочку новых трюков. Независимо от вашего уровня, я надеюсь, что это путешествие через специальные питоновские методы было поистине магическим (не смог удержаться).

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

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

Магический метод Когда он вызывается (пример) Объяснение
__new__(cls [,...]) instance = MyClass(arg1, arg2) __new__ вызывается при создании экземпляра
__init__(self [,...]) instance = MyClass(arg1, arg2) __init__ вызывается при создании экземпляра
__cmp__(self, other) self == other, self > other, etc. Вызывается для любого сравнения
__pos__(self) +self Унарный знак плюса
__neg__(self) -self Унарный знак минуса
__invert__(self) ~self Побитовая инверсия
__index__(self) x[self] Преобразование, когда объект используется как индекс
__nonzero__(self) bool(self), if self: Булевое значение объекта
__getattr__(self, name) self.name # name не определено Пытаются получить несуществующий атрибут
__setattr__(self, name, val) self.name = val Присвоение любому атрибуту
__delattr__(self, name) del self.name Удаление атрибута
__getattribute__(self, name) self.name Получить любой атрибут
__getitem__(self, key) self[key] Получение элемента через индекс
__setitem__(self, key, val) self[key] = val Присвоение элементу через индекс
__delitem__(self, key) del self[key] Удаление элемента через индекс
__iter__(self) for x in self Итерация
__contains__(self, value) value in self, value not in self Проверка принадлежности с помощью in
__call__(self [,...]) self(args) «Вызов» экземпляра
__enter__(self) with self as x: with оператор менеджеров контекста
__exit__(self, exc, val, trace) with self as x: with оператор менеджеров контекста
__getstate__(self) pickle.dump(pkl_file, self) Сериализация
__setstate__(self) data = pickle.load(pkl_file) Сериализация

Надеюсь, эта таблица избавит вас от любых вопросов о том, что за синтаксис вызова магических методов.

Дополнение 2: Изменения в Питоне 3

Опишем несколько главных случаев, когда Питон 3 отличается от 2.x в терминах его объектной модели:

  • Так как в Питоне 3 различий между строкой и юникодом больше нет, __unicode__ исчез, а появился __bytes__ (который ведёт себя так же как __str__ и __unicode__ в 2.7) для новых встроенных функций построения байтовых массивов.
  • Так как деление в Питоне 3 теперь по-умолчанию «правильное деление», __div__ больше нет.
  • __coerce__ больше нет, из-за избыточности и странного поведения.
  • __cmp__ больше нет, из-за избыточности.
  • __nonzero__ было переименовано в __bool__.
  • next у итераторов был переименован в __next__.

Магические методы в языке программирования Python нужны, чтобы реализовывать свойства объектов при их взаимодействии.
В статье раскроем их «магию» и остановимся на каждом из методов подробно.

Андрей Мальчук

Андрей Мальчук


Бэкенд разработчик группы частных облаков КРОК.

Создание и удаление объектов

Любое описание объекта в объектно-ориентированном программировании начинается с создания объекта и его удаления. Давайте подробнее остановимся на каждом из них:

__new__(cls[, ...]) — метод создания типа класса. Он принимает первым аргументом тип класса, в котором он вызывается, и, как правило, возвращает этот же тип. В основном используется, чтобы настраивать создание экземпляра класса тех объектов, которые наследуются от неизменяемых типов (например, int, str, или tuple).

__init__(self[, ...]) — конструктор класса. Используется при определении объектов.

__init_subclass__(cls) — позволяет переопределить создание подклассов объекта. Например, добавлять дополнительные атрибуты:

>>> class Test:
>>>     def __init_subclass__(cls, /, test_param, **kwargs):
>>>         super().__init_subclass__(**kwargs)
>>>         cls.test_param = test_param
>>>
>>> class AnotherTest(Test, test_param="Hello World"):
>>>     pass

__del__(self) — деструктор класса. Вызывается автоматически сборщиком мусора, практически никогда не используется, за исключением, когда пользователя необходимо предупредить о незакрытых дескрипторах.

Общие свойства объектов

Любой объект может содержать дополнительную информацию, полезную при отладке или приведении типов. Например:

__repr__(self) — информационная строка об объекте. Выводится при вызове функции repr(...) или в момент отладки. Для последнего этот метод и предназначен. Например:

>>> class Test:
>>>     def __repr__(self):
>>>         return ""
>>>
>>> Test()
... ''

__str__(self) — вызывается при вызове функции str(...), возвращает строковый объект. Например:

>>> class Test:
>>>     def __str__(self):
>>>         return "Hello World"
>>>
>>> test = Test()
>>> str(test)
... 'Hello World'

__bytes__(self) — аналогично __str__(self), только возвращается набор байт.

__format__(self, format_spec) — вызывается при вызове функции format(...) и используется для форматировании строки с использованием строковых литералов.

Методы сравнения объектов между собой

__lt__(self, other) — определяет поведение оператора сравнения «меньше», <.

__le__(self, other) — определяет поведение оператора сравнения «меньше или равно», <=.

__eq__(self, other) — определяет поведение оператора «равенства», ==.

__ne__(self, other) — определяет поведение оператора «неравенства», !=.

__gt__(self, other) — определяет поведение оператора сравнения «больше», >.

__ge__(self, other) — определяет поведение оператора сравнения «больше или равно», >=.

__hash__(self) — вызывается функцией hash(...) и используется для определения контрольной суммы объекта, чтобы доказать его уникальность. Например, чтобы добавить объект в set, frozenset, или использовать в качестве ключа в словаре dict.

__bool__(self) — вызывается функцией bool(...) и возвращает True или False в соответствии с реализацией. Если данный метод не реализован в объекте, и объект является какой-либо последовательностью (списком, кортежем и т.д.), вместо него вызывается метод __len__. Используется, в основном, в условиях if, например:

>>> class Test:
>>>     def __bool__(self):
>>>         return True
>>>
>>> test = Test()
>>>
>>> if test:
>>>    print("Hello World")
>>>
... 'Hello World'

Доступ к атрибутам объекта

Доступ ко всем свойствам объекта также контролируются отдельными методами:

__getattr__(self, name) — вызывается методом getattr(...) или при обращении к атрибуту объекта через x.y, где x — объект, а y — атрибут.

__setattr__(self, name, value) — вызывается методом setattr(...)или при обращении к атрибуту объекта с последующим определением значения переданного атрибута. Например: x.y = 1, где x — объект, y — атрибут, а 1 — значение атрибута.

__delattr__(self, name) — вызывается методом delattr(...)или при ручном удалении атрибута у объекта с помощью del x.y, где  x — объект, а y — атрибут.

__dir__(self) — вызывается методом dir(...) и выводит список доступных атрибутов объекта.

Создание последовательностей

Любой объект может реализовать методы встроенных последовательностей (словари, кортежи, списки, строки и так далее). Доступ к значениям последовательности переопределяется следующими методами:

__len__(self) — вызывается методом len(...) и возвращает количество элементов в последовательности.

__getitem__(self, key) — вызывается при обращении к элементу в последовательности по его ключу (индексу). Метод должен выбрасывать исключение TypeError, если используется некорректный тип ключа, KeyError, если данному ключу не соответствует ни один элемент в последовательности. Например:

>>> list_object = [1, 2, 3, 4, 5]
>>> print(list_object[0])
... 1
>>>
>>> string_object = "hello world"
>>> print(string_object[0:5])
... 'hello'
>>>
>>> dict_object = {"key0": True, "key1": False}
>>> print(dict_object["key0"])
... True

__setitem__(self, key, value) — вызывается при присваивании какого-либо значения элементу в последовательности. Также может выбрасывать исключения TypeError и KeyError. Например:

>>> list_object = [1, 2, 3, 4, 5]
>>> list_object[0] = 78
>>> print(list_object)
... [78, 2, 3, 4, 5]
>>>
>>> dict_object = {"key0": True, "key1": False}
>>> dict_object["key0"] = False
>>> print(dict_object)
... {"key0": False, "key1": False}

__delitem__(self, key) — вызывается при удалении значения в последовательности по его индексу (ключу) с помощью синтаксиса ключевого слова del.

__missing__(self, key) — вызывается в случаях, когда значения в последовательности не существует.

__iter__(self) — вызывается методом iter(...) и возвращает итератор последовательности, например, для использования объекта в цикле:

>>> class Test:
>>>     def __iter__(self):
>>>         return (1, 2, 3)
>>>
>>> for value in Test():
>>>     print(value)
... 1
... 2
... 3

__reversed__(self) — вызывается методом reversed(...) и аналогично методу __iter__ возвращает тот же итератор, только в обратном порядке.

__contains__(self, item) — вызывается при проверке принадлежности элемента к последовательности с помощью in или not in.

Числовые магические методы

Данные методы делятся на несколько групп: унарные операторы, обычные арифметические, отражённые арифметические операторы, составные присваивания и преобразования типов.

Унарные операторы

__neg__(self) — определяет поведение для отрицания (-a)

__pos__(self) — определяет поведение для унарного плюса (+a)

__abs__(self) — определяет поведение для встроенной функции abs(...)

__invert__(self) — определяет поведение для инвертирования оператором ~

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

__add__(self, other) — сложение, оператор +

__sub__(self, other) — вычитание, оператор -

__mul__(self, other) — умножение, оператор *

__matmul__(self, other) — умножение матриц, оператор @

__truediv__(self, other) — деление, оператор /

__floordiv__(self, other) — целочисленное деление, оператор //

__mod__(self, other) — остаток от деления, оператор %

__divmod__(self, other) — деление с остатком, определяет поведение для встроенной функции divmod(...)

__pow__(self, other[, modulo]) — возведение в степень, оператор **

__lshift__(self, other) — двоичный сдвиг влево, оператор <<

__rshift__(self, other) — двоичный сдвиг вправо, оператор >>

__and__(self, other) — двоичное И, оператор &

__xor__(self, other) — исключающее ИЛИ, оператор ^

__or__(self, other) — двоичное ИЛИ, оператор |

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

Если в обычной арифметике между объектами a и b, объектом, который мы изменяем, является a, и объектом, с которым мы работаем, являетсяb, то в отражённой арифметике наоборот — b является изменяемым, a — объектом, с которым мы работаем, и который передается в качестве аргумента. Например:

# Сложение, используется обычная арифметика и метод __add__ в объекте left:
>>> left + right
# Сложение, используется отражённая арифметика и метод __radd__ в объекте left:
>>> right + left

Список методов похож на тот, что используется в обычной арифметике, за исключением того, что добавляется префикс «r» ко всем методам:

__radd__(self, other) — сложение, оператор +

__rsub__(self, other) — вычитание, оператор -

__rmul__(self, other) — умножение, оператор *

__rmatmul__(self, other) — умножение матриц, оператор @

__rtruediv__(self, other) — деление, оператор /

__rfloordiv__(self, other) — целочисленное деление, оператор //

__rmod__(self, other) — остаток от деления, оператор %

__rdivmod__(self, other) — деление с остатком

__rpow__(self, other[, modulo]) — возведение в степень, оператор **

__rlshift__(self, other) — двоичный сдвиг влево, оператор <<

__rrshift__(self, other) — двоичный сдвиг вправо, оператор >>

__rand__(self, other) — двоичное И, оператор &

__rxor__(self, other) — исключающее ИЛИ, оператор ^

__ror__(self, other) — двоичное ИЛИ, оператор |

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

Эти методы — комбинация «обычного» оператора и присваивания. Возвращают тот же тип объекта, который будет присвоен переменной слева. Например:

x = 3
x += 2 # другими словами x = x + 2

__iadd__(self, other) — сложение с присваиванием, оператор +=

__isub__(self, other) — вычитание с присваиванием, оператор -=

__imul__(self, other) — умножение с присваиванием, оператор *=

__imatmul__(self, other) — умножение матриц с присваиванием, оператор @=

__itruediv__(self, other) — деление с присваиванием, оператор /=

__ifloordiv__(self, other) — целочисленное деление с присваиванием, оператор //=

__imod__(self, other) — остаток от деления с присваиванием, оператор %=

__ipow__(self, other[, modulo]) — возведение в степень с присваиванием, оператор **=

__ilshift__(self, other) — двоичный сдвиг влево с присваиванием, оператор <<=

__irshift__(self, other) — двоичный сдвиг вправо с присваиванием, оператор >>=

__iand__(self, other) — двоичное И с присваиванием, оператор &=

__ixor__(self, other) — исключающее ИЛИ с присваиванием, оператор ^=

__ior__(self, other) — двоичное ИЛИ с присваиванием, оператор |=

Преобразования типов

Помимо всего прочего, в Python множество методов, которые позволяют переопределять поведение встроенных функций преобразования типов, таких как int(...), float(...) и т.д. Например:

__complex__(self) — преобразование типа в комплексное число

__int__(self) — преобразование типа к int

__float__(self) — преобразование типа к float

__index__(self) — преобразование типа к int, когда объект используется в срезах (выражения вида [start:stop:step])

__round__(self[, ndigits]) — округление числа с помощью функции round(...)

__trunc__(self) — вызывается методом math.trunc(...)

__floor__(self) — вызывается методом math.floor(...)

__ceil__(self) — вызывается методом math.ceil(...)

Вызываемые объекты

__call__(self[, args...]) — позволяет любому экземпляру класса вести себя как обычная функция. Например:

>>> class Test:
>>>     def __call__(self, message):
>>>         print(message)
>>>         return True
>>>
>>> test = Test()
>>> test("Hello World")
... 'Hello World'
... True

__await__(self) — возвращает итератор, превращая класс в корутину, результат выполнения которой можно получить с помощью await. Подробнее об этом можно узнать в PEP 492.

Контекстные менеджеры

Любой объект может быть представлен как контекстный менеджер, который вызывается с помощью with или async with. Данная конструкция позволяет выполнить какие-либо действия по настройке объекта и при выходе из контекстного менеджера, произвести какие-либо действия по очистке, не смотря на то, было ли вызвано исключение в блоке контекстного менеджера.

__enter__(self) — определяет начало блока контекстного менеджера, вызванного с помощью with

__exit__(self, exc_type, exc_value, traceback) — определяет конец блока контекстного менеджера. Может использоваться для контролирования исключений, очистки, или любых действий, которые должны быть выполнены после блока внутри with. Если блок выполнился успешно, то все три аргумента (exc_type, exc_value и traceback) будут установлены в значение None.

Например:

>>> class ContextManager:
>>>     def __enter__(self):
>>>         log("entering context")
>>>
>>>     def __exit__(self, exc_type, exc_value, traceback):
>>>         log("exiting context")
>>>
>>> with ContextManager():
...     print("in context manager")
...
... 'entering context'
... 'in context manager'
... 'exiting context'

__aenter__(self) — аналогично __enter__, только функция возвращает корутину (результат которой можно получить с помощью await)

__aexit__(self, exc_type, exc_value, traceback) — аналогично __exit__, только функция возвращает корутину (результат которой можно получить с помощью await)

Например:

>>> class AsyncContextManager:
>>>     async def __aenter__(self):
>>>         await log("entering context")
>>>
>>>     async def __aexit__(self, exc_type, exc_value, traceback):
>>>         await log("exiting context")
>>>
>>> async with AsyncContextManager():
...     print("in context manager")
...
... 'entering context'
... 'in context manager'
... 'exiting context'

Неиспользуемые методы

Некоторые методы, после полного перехода с Python 2 на Python 3 стали устаревшими и больше не используются.

__unicode__ — полностью исчез в версии Python 3, вместо него используются отдельные методы __str__и __bytes__

__div__ — так как в Python 3 теперь по умолчанию «правильное деление», данного метода не существует

__cmp__ — более не существует, вместо него используются __lt__, __le__, __eq__, __ne__, __gt__и __ge__

__nonzero__ — переименован в __bool__

Упрощение работы с магическими методами

Большая часть из вышеописанных методов реализуется библиотеками, они позволяют использовать так называемые «обрёртки» над классами, в которых будут реализованы необходимые магические методы.

Пример таких библиотек:

  • dataclasses
  • attrs

Принципы и идеология этих (и других библиотек) схожи в одном – они позволяют реализовать всю необходимую логику работы с объектом, не дублируя код для каждого отдельного объекта.

Например:

>>> from dataclasses import dataclass
>>>
>>> @dataclass(init=True, repr=True, eq=True, order=True, unsafe_hash=True)
>>> class Person:
>>>     first_name: str
>>>     last_name: str
>>>     age: int

Данный объект будет иметь в себе:

  • Три атрибута first_name, last_name и age, которые также будут передаваться в конструктор класса;
  • Будет реализован метод __repr__ для вывода информации для отладки;
  • Будут реализованы все магические методы сравнения (такие как __lt__, __eq__ и так далее), а также метод __hash__.

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

Rafe Kettler

Copyright © 2012 Rafe Kettler

Version 1.17

A PDF version of this guide can be obtained from my site or Github. The magic methods guide has a git repository at http://www.github.com/RafeKettler/magicmethods. Any issues can be reported
there, along with comments, (or even contributions!).

Table of Contents

  1. Introduction
  2. Construction and Initialization
  3. Making Operators Work on Custom Classes
    • Comparison magic methods
    • Numeric magic methods
  4. Representing your Classes
  5. Controlling Attribute Access
  6. Making Custom Sequences
  7. Reflection
  8. Abstract Base Classes
  9. Callable Objects
  10. Context Managers
  11. Building Descriptor Objects
  12. Copying
  13. Pickling your Objects
  14. Conclusion
  15. Appendix 1: How to Call Magic Methods
  16. Appendix 2: Changes in Python 3

Introduction

This guide is the culmination of a few months’ worth of blog posts. The subject is magic methods.

What are magic methods? They’re everything in object-oriented Python. They’re special methods that you can define to add «magic» to your classes. They’re always surrounded by double underscores (e.g. __init__ or __lt__). They’re also not as well documented as they need to be. All of the magic methods for Python appear in the same section in the Python docs, but they’re scattered about and only loosely organized. There’s hardly an example to be found in that section (and that may very well be by design, since they’re all detailed in the language reference, along with boring syntax descriptions, etc.).

So, to fix what I perceived as a flaw in Python’s documentation, I set out to provide some more plain-English, example-driven documentation for Python’s magic methods. I started out with weekly blog posts, and now that I’ve finished with those, I’ve put together this guide.

I hope you enjoy it. Use it as a tutorial, a refresher, or a reference; it’s just intended to be a user-friendly guide to Python’s magic methods.

Construction and Initialization

Everyone knows the most basic magic method, __init__. It’s the way that we can define the initialization behavior of an object. However, when I call x = SomeClass(), __init__ is not the first thing to get called. Actually, it’s a method called __new__, which actually creates the instance, then passes any arguments at creation on to the initializer. At the other end of the object’s lifespan, there’s __del__. Let’s take a closer look at these 3 magic methods:

__new__(cls, [...)
__new__ is the first method to get called in an object’s instantiation. It takes the class, then any other arguments that it will pass along to __init__. __new__ is used fairly rarely, but it does have its purposes, particularly when subclassing an immutable type like a tuple or a string. I don’t want to go in to too much detail on __new__ because it’s not too useful, but it is covered in great detail in the Python docs.
__init__(self, [...)
The initializer for the class. It gets passed whatever the primary constructor was called with (so, for example, if we called x = SomeClass(10, 'foo'), __init__ would get passed 10 and 'foo' as arguments. __init__ is almost universally used in Python class definitions.
__del__(self)
If __new__ and __init__ formed the constructor of the object, __del__ is the destructor. It doesn’t implement behavior for the statement del x (so that code would not translate to x.__del__()). Rather, it defines behavior for when an object is garbage collected. It can be quite useful for objects that might require extra cleanup upon deletion, like sockets or file objects. Be careful, however, as there is no guarantee that __del__ will be executed if the object is still alive when the interpreter exits, so __del__ can’t serve as a replacement for good coding practices (like always closing a connection when you’re done with it. In fact, __del__ should almost never be used because of the precarious circumstances under which it is called; use it with caution!

Putting it all together, here’s an example of __init__ and __del__ in action:

from os.path import join

class FileObject:
    '''Wrapper for file objects to make sure the file gets closed on deletion.'''

    def __init__(self, filepath='~', filename='sample.txt'):
        # open a file filename in filepath in read and write mode
        self.file = open(join(filepath, filename), 'r+')

    def __del__(self):
        self.file.close()
        del self.file

Making Operators Work on Custom Classes

One of the biggest advantages of using Python’s magic methods is that they provide a simple way to make objects behave like built-in types. That means you can avoid ugly, counter-intuitive, and nonstandard ways of performing basic operators. In some languages, it’s common to do something like this:

if instance.equals(other_instance):
    # do something

You could certainly do this in Python, too, but this adds confusion and is unnecessarily verbose. Different libraries might use different names for the same operations, making the client do way more work than necessary. With the power of magic methods, however, we can define one method (__eq__, in this case), and say what we mean instead:

if instance == other_instance:
    #do something

That’s part of the power of magic methods. The vast majority of them allow us to define meaning for operators so that we can use them on our own classes just like they were built in types.

Comparison magic methods

Python has a whole slew of magic methods designed to implement intuitive comparisons between objects using operators, not awkward method calls. They also provide a way to override the default Python behavior for comparisons of objects (by reference). Here’s the list of those methods and what they do:

__cmp__(self, other)
__cmp__ is the most basic of the comparison magic methods. It actually implements behavior for all of the comparison operators (<, ==, !=, etc.), but it might not do it the way you want (for example, if whether one instance was equal to another were determined by one criterion and and whether an instance is greater than another were determined by something else). __cmp__ should return a negative integer if self < other, zero if self == other, and positive if self > other. It’s usually best to define each comparison you need rather than define them all at once, but __cmp__ can be a good way to save repetition and improve clarity when you need all comparisons implemented with similar criteria.
__eq__(self, other)
Defines behavior for the equality operator, ==.
__ne__(self, other)
Defines behavior for the inequality operator, !=.
__lt__(self, other)
Defines behavior for the less-than operator, <.
__gt__(self, other)
Defines behavior for the greater-than operator, >.
__le__(self, other)
Defines behavior for the less-than-or-equal-to operator, <=.
__ge__(self, other)
Defines behavior for the greater-than-or-equal-to operator, >=.

For an example, consider a class to model a word. We might want to compare words lexicographically (by the alphabet), which is the default comparison behavior for strings, but we also might want to do it based on some other criterion, like length or number of syllables. In this example, we’ll compare by length. Here’s an implementation:

class Word(str):
    '''Class for words, defining comparison based on word length.'''

    def __new__(cls, word):
        # Note that we have to use __new__. This is because str is an immutable
        # type, so we have to initialize it early (at creation)
        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')] # Word is now all chars before first space
        return str.__new__(cls, word)

    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)

Now, we can create two Words (by using Word('foo') and Word('bar')) and compare them based on length. Note, however, that we didn’t define __eq__ and __ne__. This is because this would lead to some weird behavior (notably that Word('foo') == Word('bar') would evaluate to true). It wouldn’t make sense to test for equality based on length, so we fall back on str‘s implementation of equality.

Now would be a good time to note that you don’t have to define every comparison magic method to get rich comparisons. The standard library has kindly provided us with a class decorator in the module functools that will define all rich comparison methods if you only define __eq__ and one other (e.g. __gt__, __lt__, etc.) This feature is only available in Python 2.7, but when you get a chance it saves a great deal of time and effort. You can use it by placing @total_ordering above your class definition.

Numeric magic methods

Just like you can create ways for instances of your class to be compared with comparison operators, you can define behavior for numeric operators. Buckle your seat belts, folks…there’s a lot of these. For organization’s sake, I’ve split the numeric magic methods into 5 categories: unary operators, normal arithmetic operators, reflected arithmetic operators (more on this later), augmented assignment, and type conversions.

Unary operators and functions

Unary operators and functions only have one operand, e.g. negation, absolute value, etc.

__pos__(self)
Implements behavior for unary positive (e.g. +some_object)
__neg__(self)
Implements behavior for negation (e.g. -some_object)
__abs__(self)
Implements behavior for the built in abs() function.
__invert__(self)
Implements behavior for inversion using the ~ operator. For an explanation on what this does, see the Wikipedia article on bitwise operations.
__round__(self, n)
Implements behavior for the built in round() function. n is the number of decimal places to round to.
__floor__(self)
Implements behavior for math.floor(), i.e., rounding down to the nearest integer.
__ceil__(self)
Implements behavior for math.ceil(), i.e., rounding up to the nearest integer.
__trunc__(self)
Implements behavior for math.trunc(), i.e., truncating to an integral.

Normal arithmetic operators

Now, we cover the typical binary operators (and a function or two): +, -, * and the like. These are, for the most part, pretty self-explanatory.

__add__(self, other)
Implements addition.
__sub__(self, other)
Implements subtraction.
__mul__(self, other)
Implements multiplication.
__floordiv__(self, other)
Implements integer division using the // operator.
__div__(self, other)
Implements division using the / operator.
__truediv__(self, other)
Implements true division. Note that this only works when from __future__ import division is in effect.
__mod__(self, other)
Implements modulo using the % operator.
__divmod__(self, other)
Implements behavior for long division using the divmod() built in function.
__pow__
Implements behavior for exponents using the ** operator.
__lshift__(self, other)
Implements left bitwise shift using the << operator.
__rshift__(self, other)
Implements right bitwise shift using the >> operator.
__and__(self, other)
Implements bitwise and using the & operator.
__or__(self, other)
Implements bitwise or using the | operator.
__xor__(self, other)
Implements bitwise xor using the ^ operator.

Reflected arithmetic operators

You know how I said I would get to reflected arithmetic in a bit? Some of you might think it’s some big, scary, foreign concept. It’s actually quite simple. Here’s an example:

some_object + other

That was «normal» addition. The reflected equivalent is the same thing, except with the operands switched around:

other + some_object

So, all of these magic methods do the same thing as their normal equivalents, except the perform the operation with other as the first operand and self as the second, rather than the other way around. In most cases, the result of a reflected operation is the same as its normal equivalent, so you may just end up defining __radd__ as calling __add__ and so on. Note that the object on the left hand side of the operator (other in the example) must not define (or return NotImplemented) for its definition of the non-reflected version of an operation. For instance, in the example, some_object.__radd__ will only be called if other does not define __add__.

__radd__(self, other)
Implements reflected addition.
__rsub__(self, other)
Implements reflected subtraction.
__rmul__(self, other)
Implements reflected multiplication.
__rfloordiv__(self, other)
Implements reflected integer division using the // operator.
__rdiv__(self, other)
Implements reflected division using the / operator.
__rtruediv__(self, other)
Implements reflected true division. Note that this only works when from __future__ import division is in effect.
__rmod__(self, other)
Implements reflected modulo using the % operator.
__rdivmod__(self, other)
Implements behavior for long division using the divmod() built in function, when divmod(other, self) is called.
__rpow__
Implements behavior for reflected exponents using the ** operator.
__rlshift__(self, other)
Implements reflected left bitwise shift using the << operator.
__rrshift__(self, other)
Implements reflected right bitwise shift using the >> operator.
__rand__(self, other)
Implements reflected bitwise and using the & operator.
__ror__(self, other)
Implements reflected bitwise or using the | operator.
__rxor__(self, other)
Implements reflected bitwise xor using the ^ operator.

Augmented assignment

Python also has a wide variety of magic methods to allow custom behavior to be defined for augmented assignment. You’re probably already familiar with augmented assignment, it combines «normal» operators with assignment. If you still don’t know what I’m talking about, here’s an example:

x = 5
x += 1 # in other words x = x + 1

Each of these methods should return the value that the variable on the left hand side should be assigned to (for instance, for a += b, __iadd__ might return a + b, which would be assigned to a). Here’s the list:

__iadd__(self, other)
Implements addition with assignment.
__isub__(self, other)
Implements subtraction with assignment.
__imul__(self, other)
Implements multiplication with assignment.
__ifloordiv__(self, other)
Implements integer division with assignment using the //= operator.
__idiv__(self, other)
Implements division with assignment using the /= operator.
__itruediv__(self, other)
Implements true division with assignment. Note that this only works when from __future__ import division is in effect.
__imod__(self, other)
Implements modulo with assignment using the %= operator.
__ipow__
Implements behavior for exponents with assignment using the **= operator.
__ilshift__(self, other)
Implements left bitwise shift with assignment using the <<= operator.
__irshift__(self, other)
Implements right bitwise shift with assignment using the >>= operator.
__iand__(self, other)
Implements bitwise and with assignment using the &= operator.
__ior__(self, other)
Implements bitwise or with assignment using the |= operator.
__ixor__(self, other)
Implements bitwise xor with assignment using the ^= operator.

Type conversion magic methods

Python also has an array of magic methods designed to implement behavior for built in type conversion functions like float(). Here they are:

__int__(self)
Implements type conversion to int.
__long__(self)
Implements type conversion to long.
__float__(self)
Implements type conversion to float.
__complex__(self)
Implements type conversion to complex.
__oct__(self)
Implements type conversion to octal.
__hex__(self)
Implements type conversion to hexadecimal.
__index__(self)
Implements type conversion to an int when the object is used in a slice expression. If you define a custom numeric type that might be used in slicing, you should define __index__.
__trunc__(self)
Called when math.trunc(self) is called. __trunc__ should return the value of `self truncated to an integral type (usually a long).
__coerce__(self, other)
Method to implement mixed mode arithmetic. __coerce__ should return None if type conversion is impossible. Otherwise, it should return a pair (2-tuple) of self and other, manipulated to have the same type.

Representing your Classes

It’s often useful to have a string representation of a class. In Python, there are a few methods that you can implement in your class definition to customize how built in functions that return representations of your class behave.

__str__(self)
Defines behavior for when str() is called on an instance of your class.
__repr__(self)
Defines behavior for when repr() is called on an instance of your class. The major difference between str() and repr() is intended audience. repr() is intended to produce output that is mostly machine-readable (in many cases, it could be valid Python code even), whereas str() is intended to be human-readable.
__unicode__(self)
Defines behavior for when unicode() is called on an instance of your class. unicode() is like str(), but it returns a unicode string. Be wary: if a client calls str() on an instance of your class and you’ve only defined __unicode__(), it won’t work. You should always try to define __str__() as well in case someone doesn’t have the luxury of using unicode.
__format__(self, formatstr)
Defines behavior for when an instance of your class is used in new-style string formatting. For instance, "Hello, {0:abc}!".format(a) would lead to the call a.__format__("abc"). This can be useful for defining your own numerical or string types that you might like to give special formatting options.
__hash__(self)
Defines behavior for when hash() is called on an instance of your class. It has to return an integer, and its result is used for quick key comparison in dictionaries. Note that this usually entails implementing __eq__ as well. Live by the following rule: a == b implies hash(a) == hash(b).
__nonzero__(self)
Defines behavior for when bool() is called on an instance of your class. Should return True or False, depending on whether you would want to consider the instance to be True or False.
__dir__(self)
Defines behavior for when dir() is called on an instance of your class. This method should return a list of attributes for the user. Typically, implementing __dir__ is unnecessary, but it can be vitally important for interactive use of your classes if you redefine __getattr__ or __getattribute__ (which you will see in the next section) or are otherwise dynamically generating attributes.
__sizeof__(self)
Defines behavior for when sys.getsizeof() is called on an instance of your class. This should return the size of your object, in bytes. This is generally more useful for Python classes implemented in C extensions, but it helps to be aware of it.

We’re pretty much done with the boring (and example-free) part of the magic methods guide. Now that we’ve covered some of the more basic magic methods, it’s time to move to more advanced material.

Controlling Attribute Access

Many people coming to Python from other languages complain that it lacks true encapsulation for classes; that is, there’s no way to define private attributes with public getter and setters. This couldn’t be farther than the truth: it just happens that Python accomplishes a great deal of encapsulation through «magic», instead of explicit modifiers for methods or fields. Take a look:

__getattr__(self, name)
You can define behavior for when a user attempts to access an attribute that doesn’t exist (either at all or yet). This can be useful for catching and redirecting common misspellings, giving warnings about using deprecated attributes (you can still choose to compute and return that attribute, if you wish), or deftly handing an AttributeError. It only gets called when a nonexistent attribute is accessed, however, so it isn’t a true encapsulation solution.
__setattr__(self, name, value)
Unlike __getattr__, __setattr__ is an encapsulation solution. It allows you to define behavior for assignment to an attribute regardless of whether or not that attribute exists, meaning you can define custom rules for any changes in the values of attributes. However, you have to be careful with how you use __setattr__, as the example at the end of the list will show.
__delattr__(self, name)
This is the exact same as __setattr__, but for deleting attributes instead of setting them. The same precautions need to be taken as with __setattr__ as well in order to prevent infinite recursion (calling del self.name in the implementation of __delattr__ would cause infinite recursion).
__getattribute__(self, name)
After all this, __getattribute__ fits in pretty well with its companions __setattr__ and __delattr__. However, I don’t recommend you use it. __getattribute__ can only be used with new-style classes (all classes are new-style in the newest versions of Python, and in older versions you can make a class new-style by subclassing object. It allows you to define rules for whenever an attribute’s value is accessed. It suffers from some similar infinite recursion problems as its partners-in-crime (this time you call the base class’s __getattribute__ method to prevent this). It also mainly obviates the need for __getattr__, which, when __getattribute__ is implemented, only gets called if it is called explicitly or an AttributeError is raised. This method can be used (after all, it’s your choice), but I don’t recommend it because it has a small use case (it’s far more rare that we need special behavior to retrieve a value than to assign to it) and because it can be really difficult to implement bug-free.

You can easily cause a problem in your definitions of any of the methods controlling attribute access. Consider this example:

def __setattr__(self, name, value):
    self.name = value
    # since every time an attribute is assigned, __setattr__() is called, this
    # is recursion.
    # so this really means self.__setattr__('name', value). Since the method
    # keeps calling itself, the recursion goes on forever causing a crash

def __setattr__(self, name, value):
    self.__dict__[name] = value # assigning to the dict of names in the class
    # define custom behavior here

Again, Python’s magic methods are incredibly powerful, and with great power comes great responsibility. It’s important to know the proper way to use magic methods so you don’t break any code.

So, what have we learned about custom attribute access in Python? It’s not to be used lightly. In fact, it tends to be excessively powerful and counter-intuitive. But the reason why it exists is to scratch a certain itch: Python doesn’t seek to make bad things impossible, but just to make them difficult. Freedom is paramount, so you can really do whatever you want. Here’s an example of some of the special attribute access methods in action (note that we use super because not all classes have an attribute __dict__):

class AccessCounter(object):
    '''A class that contains a value and implements an access counter.
    The counter increments each time the value is changed.'''

    def __init__(self, val):
        super(AccessCounter, self).__setattr__('counter', 0)
        super(AccessCounter, self).__setattr__('value', val)

    def __setattr__(self, name, value):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
        # Make this unconditional.
        # If you want to prevent other attributes to be set, raise AttributeError(name)
        super(AccessCounter, self).__setattr__(name, value)

    def __delattr__(self, name):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
        super(AccessCounter, self).__delattr__(name)

Making Custom Sequences

There’s a number of ways to get your Python classes to act like built in sequences (dict, tuple, list, str, etc.). These are by far my favorite magic methods in Python because of the absurd degree of control they give you and the way that they magically make a whole array of global functions work beautifully on instances of your class. But before we get down to the good stuff, a quick word on requirements.

Requirements

Now that we’re talking about creating your own sequences in Python, it’s time to talk about protocols. Protocols are somewhat similar to interfaces in other languages in that they give you a set of methods you must define. However, in Python protocols are totally informal and require no explicit declarations to implement. Rather, they’re more like guidelines.

Why are we talking about protocols now? Because implementing custom container types in Python involves using some of these protocols. First, there’s the protocol for defining immutable containers: to make an immutable container, you need only define __len__ and __getitem__ (more on these later). The mutable container protocol requires everything that immutable containers require plus __setitem__ and __delitem__. Lastly, if you want your object to be iterable, you’ll have to define __iter__, which returns an iterator. That iterator must conform to an iterator protocol, which requires iterators to have methods called __iter__(returning itself) and next.

The magic behind containers

Without any more wait, here are the magic methods that containers use:

__len__(self)
Returns the length of the container. Part of the protocol for both immutable and mutable containers.
__getitem__(self, key)
Defines behavior for when an item is accessed, using the notation self[key]. This is also part of both the mutable and immutable container protocols. It should also raise appropriate exceptions: TypeError if the type of the key is wrong and KeyError if there is no corresponding value for the key.
__setitem__(self, key, value)
Defines behavior for when an item is assigned to, using the notation self[nkey] = value. This is part of the mutable container protocol. Again, you should raise KeyError and TypeError where appropriate.
__delitem__(self, key)
Defines behavior for when an item is deleted (e.g. del self[key]). This is only part of the mutable container protocol. You must raise the appropriate exceptions when an invalid key is used.
__iter__(self)
Should return an iterator for the container. Iterators are returned in a number of contexts, most notably by the iter() built in function and when a container is looped over using the form for x in container:. Iterators are their own objects, and they also must define an __iter__ method that returns self.
__reversed__(self)
Called to implement behavior for the reversed() built in function. Should return a reversed version of the sequence. Implement this only if the sequence class is ordered, like list or tuple.
__contains__(self, item)
__contains__ defines behavior for membership tests using in and not in. Why isn’t this part of a sequence protocol, you ask? Because when __contains__ isn’t defined, Python just iterates over the sequence and returns True if it comes across the item it’s looking for.
__missing__(self, key)
__missing__ is used in subclasses of dict. It defines behavior for whenever a key is accessed that does not exist in a dictionary (so, for instance, if I had a dictionary d and said d["george"] when "george" is not a key in the dict, d.__missing__("george") would be called).

An example

For our example, let’s look at a list that implements some functional constructs that you might be used to from other languages (Haskell, for example).

class FunctionalList:
    '''A class wrapping a list with some extra functional magic, like head,
    tail, init, last, drop, and take.'''

    def __init__(self, values=None):
        if values is None:
            self.values = []
        else:
            self.values = values

    def __len__(self):
        return len(self.values)

    def __getitem__(self, key):
        # if key is of invalid type or value, the list values will raise the error
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]

    def __iter__(self):
        return iter(self.values)

    def __reversed__(self):
        return reversed(self.values)

    def append(self, value):
        self.values.append(value)
    def head(self):
        # get the first element
        return self.values[0]
    def tail(self):
        # get all elements after the first
        return self.values[1:]
    def init(self):
        # get elements up to the last
        return self.values[:-1]
    def last(self):
        # get last element
        return self.values[-1]
    def drop(self, n):
        # get all elements except first n
        return self.values[n:]
    def take(self, n):
        # get first n elements
        return self.values[:n]

There you have it, a (marginally) useful example of how to implement your own sequence. Of course, there are more useful applications of custom sequences, but quite a few of them are already implemented in the standard library (batteries included, right?), like Counter, OrderedDict, and NamedTuple.

Reflection

You can also control how reflection using the built in functions isinstance() and issubclass()behaves by defining magic methods. The magic methods are:

__instancecheck__(self, instance)
Checks if an instance is an instance of the class you defined (e.g. isinstance(instance, class).
__subclasscheck__(self, subclass)
Checks if a class subclasses the class you defined (e.g. issubclass(subclass, class)).

The use case for these magic methods might seem small, and that may very well be true. I won’t spend too much more time on reflection magic methods because they aren’t very important, but they reflect something important about object-oriented programming in Python and Python in general: there is almost always an easy way to do something, even if it’s rarely necessary. These magic methods might not seem useful, but if you ever need them you’ll be glad that they’re there (and that you read this guide!).

Callable Objects

As you may already know, in Python, functions are first-class objects. This means that they can be passed to functions and methods just as if they were objects of any other kind. This is an incredibly powerful feature.

A special magic method in Python allows instances of your classes to behave as if they were functions, so that you can «call» them, pass them to functions that take functions as arguments, and so on. This is another powerful convenience feature that makes programming in Python that much sweeter.

__call__(self, [args...])
Allows an instance of a class to be called as a function. Essentially, this means that x() is the same as x.__call__(). Note that __call__ takes a variable number of arguments; this means that you define __call__ as you would any other function, taking however many arguments you’d like it to.

__call__ can be particularly useful in classes with instances that need to often change state. «Calling» the instance can be an intuitive and elegant way to change the object’s state. An example might be a class representing an entity’s position on a plane:

class Entity:
    '''Class to represent an entity. Callable to update the entity's position.'''

    def __init__(self, size, x, y):
        self.x, self.y = x, y
        self.size = size

    def __call__(self, x, y):
        '''Change the position of the entity.'''
        self.x, self.y = x, y

    # snip...

Context Managers

In Python 2.5, a new keyword was introduced in Python along with a new method for code reuse: the with statement. The concept of context managers was hardly new in Python (it was implemented before as a part of the library), but not until PEP 343 was accepted did it achieve status as a first-class language construct. You may have seen with statements before:

with open('foo.txt') as bar:
    # perform some action with bar

Context managers allow setup and cleanup actions to be taken for objects when their creation is wrapped with a with statement. The behavior of the context manager is determined by two magic methods:

__enter__(self)
Defines what the context manager should do at the beginning of the block created by the with statement. Note that the return value of __enter__ is bound to the target of the with statement, or the name after the as.
__exit__(self, exception_type, exception_value, traceback)
Defines what the context manager should do after its block has been executed (or terminates). It can be used to handle exceptions, perform cleanup, or do something always done immediately after the action in the block. If the block executes successfully, exception_type, exception_value, and traceback will be None. Otherwise, you can choose to handle the exception or let the user handle it; if you want to handle it, make sure __exit__ returns True after all is said and done. If you don’t want the exception to be handled by the context manager, just let it happen.

__enter__ and __exit__ can be useful for specific classes that have well-defined and common behavior for setup and cleanup. You can also use these methods to create generic context managers that wrap other objects. Here’s an example:

class Closer:
    '''A context manager to automatically close an object with a close method
    in a with statement.'''

    def __init__(self, obj):
        self.obj = obj

    def __enter__(self):
        return self.obj # bound to target

    def __exit__(self, exception_type, exception_val, trace):
        try:
           self.obj.close()
        except AttributeError: # obj isn't closable
           print 'Not closable.'
           return True # exception handled successfully

Here’s an example of Closer in action, using an FTP connection to demonstrate it (a closable socket):

>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...     conn.dir()
...
# output omitted for brevity
>>> conn.dir()
# long AttributeError message, can't use a connection that's closed
>>> with Closer(int(5)) as i:
...     i += 1
...
Not closable.
>>> i
6

See how our wrapper gracefully handled both proper and improper uses? That’s the power of context managers and magic methods. Note that the Python standard library includes a module contextlib that contains a context manager, contextlib.closing(), that does approximately the same thing (without any handling of the case where an object does not have a close() method).

Abstract Base Classes

See http://docs.python.org/2/library/abc.html.

Building Descriptor Objects

Descriptors are classes which, when accessed through either getting, setting, or deleting, can also alter other objects. Descriptors aren’t meant to stand alone; rather, they’re meant to be held by an owner class. Descriptors can be useful when building object-oriented databases or classes that have attributes whose values are dependent on each other. Descriptors are particularly useful when representing attributes in several different units of measurement or representing computed attributes (like distance from the origin in a class to represent a point on a grid).

To be a descriptor, a class must have at least one of __get__, __set__, and __delete__ implemented. Let’s take a look at those magic methods:

__get__(self, instance, owner)
Define behavior for when the descriptor’s value is retrieved. instance is the instance of the owner object. owner is the owner class itself.
__set__(self, instance, value)
Define behavior for when the descriptor’s value is changed. instance is the instance of the owner class and value is the value to set the descriptor to.
__delete__(self, instance)
Define behavior for when the descriptor’s value is deleted. instance is the instance of the owner object.

Now, an example of a useful application of descriptors: unit conversions.

class Meter(object):
    '''Descriptor for a meter.'''

    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)

class Foot(object):
    '''Descriptor for a foot.'''

    def __get__(self, instance, owner):
        return instance.meter * 3.2808
    def __set__(self, instance, value):
        instance.meter = float(value) / 3.2808

class Distance(object):
    '''Class to represent distance holding two descriptors for feet and
    meters.'''
    meter = Meter()
    foot = Foot()

Copying

Sometimes, particularly when dealing with mutable objects, you want to be able to copy an object and make changes without affecting what you copied from. This is where Python’s copy comes into play. However (fortunately), Python modules are not sentient, so we don’t have to worry about a Linux-based robot uprising, but we do have to tell Python how to efficiently copy things.

__copy__(self)
Defines behavior for copy.copy() for instances of your class. copy.copy() returns a shallow copy of your object — this means that, while the instance itself is a new instance, all of its data is referenced — i.e., the object itself is copied, but its data is still referenced (and hence changes to data in a shallow copy may cause changes in the original).
__deepcopy__(self, memodict={})
Defines behavior for copy.deepcopy() for instances of your class. copy.deepcopy() returns a deep copy of your object — the object and its data are both copied. memodict is a cache of previously copied objects — this optimizes copying and prevents infinite recursion when copying recursive data structures. When you want to deep copy an individual attribute, call copy.deepcopy() on that attribute with memodict as the first argument.

What are some use cases for these magic methods? As always, in any case where you need more fine-grained control than what the default behavior gives you. For instance, if you are attempting to copy an object that stores a cache as a dictionary (which might be large), it might not make sense to copy the cache as well — if the cache can be shared in memory between instances, then it should be.

Pickling Your Objects

If you spend time with other Pythonistas, chances are you’ve at least heard of pickling. Pickling is a serialization process for Python data structures, and can be incredibly useful when you need to store an object and retrieve it later (usually for caching). It’s also a major source of worries and confusion.

Pickling is so important that it doesn’t just have its own module (pickle), but its own protocol and the magic methods to go with it. But first, a brief word on how to pickle existing types(feel free to skip it if you already know).

Pickling: A Quick Soak in the Brine

Let’s dive into pickling. Say you have a dictionary that you want to store and retrieve later. You couldwrite it’s contents to a file, carefully making sure that you write correct syntax, then retrieve it using either exec() or processing the file input. But this is precarious at best: if you store important data in plain text, it could be corrupted or changed in any number of ways to make your program crash or worse run malicious code on your computer. Instead, we’re going to pickle it:

import pickle

data = {'foo': [1, 2, 3],
        'bar': ('Hello', 'world!'),
        'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # write the pickled data to the file jar
jar.close()

Now, a few hours later, we want it back. All we have to do is unpickle it:

import pickle

pkl_file = open('data.pkl', 'rb') # connect to the pickled data
data = pickle.load(pkl_file) # load it into a variable
print data
pkl_file.close()

What happens? Exactly what you expect. It’s just like we had data all along.

Now, for a word of caution: pickling is not perfect. Pickle files are easily corrupted on accident and on purpose. Pickling may be more secure than using flat text files, but it still can be used to run malicious code. It’s also incompatible across different versions of Python, so don’t expect to distribute pickled objects and expect people to be able to open them. However, it can also be a powerful tool for caching and other common serialization tasks.

Pickling your own Objects

Pickling isn’t just for built-in types. It’s for any class that follows the pickle protocol. The pickle protocol has four optional methods for Python objects to customize how they act (it’s a bit different for C extensions, but that’s not in our scope):

__getinitargs__(self)
If you’d like for __init__ to be called when your class is unpickled, you can define __getinitargs__, which should return a tuple of the arguments that you’d like to be passed to __init__. Note that this method will only work for old-style classes.
__getnewargs__(self)
For new-style classes, you can influence what arguments get passed to __new__ upon unpickling. This method should also return a tuple of arguments that will then be passed to __new__.
__getstate__(self)
Instead of the object’s __dict__ attribute being stored, you can return a custom state to be stored when the object is pickled. That state will be used by __setstate__ when the object is unpickled.
__setstate__(self, state)
When the object is unpickled, if __setstate__ is defined the object’s state will be passed to it instead of directly applied to the object’s __dict__. This goes hand in hand with __getstate__: when both are defined, you can represent the object’s pickled state however you want with whatever you want.
__reduce__(self)
When defining extension types (i.e., types implemented using Python’s C API), you have to tell Python how to pickle them if you want them to pickle them. __reduce__() is called when an object defining it is pickled. It can either return a string representing a global name that Python will look up and pickle, or a tuple. The tuple contains between 2 and 5 elements: a callable object that is called to recreate the object, a tuple of arguments for that callable object, state to be passed to __setstate__ (optional), an iterator yielding list items to be pickled (optional), and an iterator yielding dictionary items to be pickled (optional).
__reduce_ex__(self)
__reduce_ex__ exists for compatibility. If it is defined, __reduce_ex__ will be called over __reduce__ on pickling. __reduce__ can be defined as well for older versions of the pickling API that did not support __reduce_ex__.

An Example

Our example is a Slate, which remembers what its values have been and when those values were written to it. However, this particular slate goes blank each time it is pickled: the current value will not be saved.

import time

class Slate:
    '''Class to store a string and a changelog, and forget its value when
    pickled.'''

    def __init__(self, value):
        self.value = value
        self.last_change = time.asctime()
        self.history = {}

    def change(self, new_value):
        # Change the value. Commit last value to history
        self.history[self.last_change] = self.value
        self.value = new_value
        self.last_change = time.asctime()

    def print_changes(self):
        print 'Changelog for Slate object:'
        for k, v in self.history.items():
            print '%st %s' % (k, v)

    def __getstate__(self):
        # Deliberately do not return self.value or self.last_change.
        # We want to have a "blank slate" when we unpickle.
        return self.history

    def __setstate__(self, state):
        # Make self.history = state and last_change and value undefined
        self.history = state
        self.value, self.last_change = None, None

Conclusion

The goal of this guide is to bring something to anyone that reads it, regardless of their experience with Python or object-oriented programming. If you’re just getting started with Python, you’ve gained valuable knowledge of the basics of writing feature-rich, elegant, and easy-to-use classes. If you’re an intermediate Python programmer, you’ve probably picked up some slick new concepts and strategies and some good ways to reduce the amount of code written by you and clients. If you’re an expert Pythonista, you’ve been refreshed on some of the stuff you might have forgotten about and maybe picked up a few new tricks along the way. Whatever your experience level, I hope that this trip through Python’s special methods has been truly magical. (I couldn’t resist the final pun!)

Appendix 1: How to Call Magic Methods

Some of the magic methods in Python directly map to built-in functions; in this case, how to invoke them is fairly obvious. However, in other
cases, the invocation is far less obvious. This appendix is devoted to exposing non-obvious syntax that leads to magic methods getting called.

Magic Method When it gets invoked (example) Explanation
__new__(cls [,...]) instance = MyClass(arg1, arg2) __new__ is called on instance creation
__init__(self [,...]) instance = MyClass(arg1, arg2) __init__ is called on instance creation
__cmp__(self, other) self == other, self > other, etc. Called for any comparison
__pos__(self) +self Unary plus sign
__neg__(self) -self Unary minus sign
__invert__(self) ~self Bitwise inversion
__index__(self) x[self] Conversion when object is used as index
__nonzero__(self) bool(self) Boolean value of the object
__getattr__(self, name) self.name # name doesn't exist Accessing nonexistent attribute
__setattr__(self, name, val) self.name = val Assigning to an attribute
__delattr__(self, name) del self.name Deleting an attribute
__getattribute__(self, name) self.name Accessing any attribute
__getitem__(self, key) self[key] Accessing an item using an index
__setitem__(self, key, val) self[key] = val Assigning to an item using an index
__delitem__(self, key) del self[key] Deleting an item using an index
__iter__(self) for x in self Iteration
__contains__(self, value) value in self, value not in self Membership tests using in
__call__(self [,...]) self(args) «Calling» an instance
__enter__(self) with self as x: with statement context managers
__exit__(self, exc, val, trace) with self as x: with statement context managers
__getstate__(self) pickle.dump(pkl_file, self) Pickling
__setstate__(self) data = pickle.load(pkl_file) Pickling

Hopefully, this table should have cleared up any questions you might have had about what syntax invokes which magic method.

Appendix 2: Changes in Python 3

Here, we document a few major places where Python 3 differs from 2.x in terms of its object model:

  • Since the distinction between string and unicode has been done away with in Python 3, __unicode__ is gone and __bytes__ (which behaves similarly to __str__ and __unicode__ in 2.7) exists for a new built-in for constructing byte arrays.
  • Since division defaults to true division in Python 3, __div__ is gone in Python 3
  • __coerce__ is gone due to redundancy with other magic methods and confusing behavior
  • __cmp__ is gone due to redundancy with other magic methods
  • __nonzero__ has been renamed to __bool__

Python has a secret superpower with a similarly stupendous name: Magic Methods. These methods can fundamentally change the way you code with Python classes and introduce code that seems ✨ magical ✨ to handle complex logic. They’re more powerful than list comprehensions and more exciting than any new PEP8 linter.

Today, we’ll be talking about a few things:

  • What magic methods are
  • Some simple introductory magic method usage
  • How to programmatically manage class properties
  • How to overwrite operator symbol functionality
  • How to make your classes iterable

We also have a cheat sheet for utilizing these magic methods quicker within your projects:

Without further ado, let’s dive in!

What are magic methods?

Magic methods are methods that Python calls on your behalf in specific circumstances. These methods are named in a particular way to quickly distinguish them from other Python methods: they’re preceded and followed by two underscores.

class Speaker:
    # This is a magic method
    def __init__(self):
        print("Hello, world!")
# This will call __init__ and print "Hello, world!"
instance = Speaker()Code language: Python (python)

This is why magic methods also called “dunder methods,” which is a shorthand for “Double underscore methods.”

In the above code you can see what I’m talking about: Python calls the __init__ dunder method on your behalf when a new class instance is created.

This barely scratches the surface when it comes to the power that magic methods provide. Let’s dive into their usage.

Simple magic method usage

If you’ve ever created a class, you’re likely familiar with the following method:

__init__(self, …args)ClassName()

It’s probably the best-known magic method, Python’s init acts as a class constructor. You can use this to pass initial arguments to a Python class.

For example, take the following:

class Speaker:
    message = ""
    def __init__(self, val):
        self.message = val
       
    def sayIt(self):
        print(self.message)

instance = Speaker("Hello, world!")
instance.sayIt()Code language: Python (python)

Here, whenever the Speaker class is initialized, it will assign self.message to the passed value. We’re then able to use a custom “sayIt” method that utilizes self.message.

Clean up class instantiation with del

In addition to a class initializer, there’s also a class deletion handler:

__del__(self)del instance

This method will run any time you call del on a class instance. This is particularly useful whenever you have an I/O operation in the constructor in order to cleanup said I/O operations.

import os

class Test:
    def __init__(self):
        f = open("temp.csv", "w")
        f.write("data,more data,testing")
        f.close()
    def __del__(self):
        os.remove('temp.csv')
        print("Cleanup done!")

firstItem = Test()

del firstItemCode language: Python (python)

This type is cleanup is integral to ensure your applications are deterministic on each run, which in turn increases general application stability. After all, if you leave remnants of your cache, they’re likely to be picked up by subsequent runs and cause havoc with your application logic.

How to programmatically manage class properties

Stuff like class constructors and cleanup are par for the course when it comes to class management. Ready for the weird stuff?

What about declaring attributes that don’t exist? __getattr__ has you covered.

__getattr__(self, key)instance.property (when property doesn’t exist)

Simply check what the lookup key is (in this case with the __name property) and return a value if you want to create a new property programmatically:

class Test:
    number = 1

    def __getattr__(self, __name: str):
        if __name == "string":
            return "Test"
        pass


test = Test()
print(test.number) # Will print `1`
print(test.string) # Will print `"Test"`Code language: Python (python)

There also exists a slightly different getattribute built-in:

__getattribute__(self, key)instance.property (regardless of if property exists)

class Test:
    number = 1

    def __getattribute__(self, __name: str):
        if __name == "string":
            return "Test"
        pass


test = Test()
print(test.number) # `None`
print(test.string) # `"Test"`Code language: Python (python)

Notice how instead of test.number returning the expected 1 value, it returns a None.

This is because while __getattr__ will resolve the existing variables and fallback to the special method when nothing is found, __getattribute__ runs first and doesn’t fall back to existing values in the class instance.

In order to have __getattribute__ to have the same behavior as __getattr__, we need to explicitly tell Python not to get stuck in the __getattribute__ trap we’ve set up.

To do this, we can call super().__getattribute__:

class Test:
    number = 1

    def __getattribute__(self, __name: str):
        """
        We need a "try/except" here, otherwise it will fail during
        lookup of an invalid key
        """
        try:
            existingVal = super().__getattribute__(__name)
            if existingVal:
                return existingVal
        except:
            if __name == "string":
                return "Test"
        pass


test = Test()
print(test.number) # Will print `1`
print(test.string) # Will print `"Test"`Code language: Python (python)

Customize class property dictionary lookup

While __getattr__ and __getattribute__ both work wonders for adding in keys programmatically, there’s a problem with that method. When usingthe dir built-in method, it won’t show the new keys.

Let’s show you what I’m talking about with a code sample. Take the following:

class Test:
    number = 1

    def __getattr__(self, __name: str):
        if __name == "string":
            return "Test"
        pass


test = Test()
print(dir(test))Code language: Python (python)

This print statement will output all of these keys:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'number']Code language: Python (python)

This list of keys includes other magic methods, which muddies the output a bit for our needs. Let’s filter those out with the following logic:

def simpledir(obj):
    return [x for x in dir(obj) if not x.startswith('__')]Code language: Python (python)

Now, when we run simpledir(test), we only see:

['number']Code language: Python (python)

But where is our ’string’ field? It doesn’t show up.

This is because while we’ve told Python how to look up the overwritten values, we’ve not told Python which keys we’ve added.

To do this, we can use the __dir__ magic method.

__dir__(self)dir(instance)

class Test:
    number = 1

    def __dir__(self):
        originalList = super().__dir__()
        originalList.append("string")
        return originalList
   
    def __getattr__(self, __name: str):
        if __name == "string":
            return "Test"
        passCode language: Python (python)

Customizing dir behavior like this will now enable us to treat our dynamic properties as if they existed normally. Now all we’re missing is a way to set values to those properties…

Set programmatically created keys

While we’re now telling Python which keys we’re programmatically creating and how to lookup the value of those keys, we’re not telling Python how to store those values.

Take the following code:

class Test:
    number = 1
 
    def __getattr__(self, __name: str):
        print("Test");
        if __name == "string":
            return "Test"
        pass
 
test = Test()
 
test.string = "Value"
print(test.string)Code language: Python (python)

Here, we might expect the print(test.string) to output «Test» as well as «Value», since getattr should be called. But, if we look at the log, we only see the following:

"Value"Code language: Python (python)

This is because, once we assign test.string, it no longer calls getattr the way we expect it to.

To solve this problem, we need to use the __setattr__ magic method to “listen” for property assignment.

__setattr__(self, key, val)instance.property = newVal

class Test:
    updateCount = 0
    valid = 1

    def __setattr__(self, key, val):
        super().__setattr__("updateCount", self.updateCount + 1)
        pass


test = Test()
test.valid = 12
print(test.updateCount)Code language: Python (python)

> Notice our usage of super().__setattr__. We need to do this similarly to how we utilized the super() method in __getattribute__, otherwise self.updateCount += 1 would trigger an infinite loop of calls to __setattr__.

Clean up programmatic property instanciation

Just as we can hook into the setting and getting behavior of an attribute, we can also hook into the del behavior of an attribute using __delattr__.

For example, what if we wanted to create a class that acted like a dictionary. For each key created in this dictionary we’d want to automatically create a temporary file. Then, on cleanup (using del), let’s remove that file with os.remove:

__delattr__(self, key)del instance.property

import os

class FileDictionary:
    def __setattr__(self, filename, val):
        f = open(filename, "w")
        f.write(val)
        f.close()
   
    def __delattr__(self, filename):
        os.remove(filename)

fileDictionary = FileDictionary()

fileDictionary.README = "Hello"
del fileDictionary.READMECode language: Python (python)

Remember, if you’re not cleaning up your side effects, it may cause havoc with future usage of your app. This is why it’s so important to add in __delattr__ when relevant.

Convert programatic lookups to index properties

In our most recent FileDictionary example, we created a class called “FileDictionary”, but then accessed the child values with the dot accessor:

fileDictionary.README = "Hello"Code language: Python (python)

However, this dot syntax causes some minor headache: it’s not consistent with how you access properties from a dictionary. The reason we’re not using the standard dictionary syntax is because if you do the following:

fileDictionary['README'] = "Hello"Code language: Python (python)

We would quickly get an error from Python:

> TypeError: ‘FileDictionary’ object is not subscriptable

To solve this problem, we need to migrate away from __setattr__, which only supports dot notation, to __setitem__, which only supports the dictionary-style notation.

__getitem__(self, key)instance[property]

__setitem__(self, key, val)instance[property] = newVal

__delitem__(self, key)  – del instance[property]

import os

class FileDictionary:
    def __setitem__(self, filename, val):
        f = open(filename, "w")
        f.write(val)
        f.close()

    def __delitem__(self, filename):
        os.remove(filename)

fileDictionary = FileDictionary()

fileDictionary['README'] = "Hello"
del fileDictionary['README']Code language: Python (python)

As a wonderful side effect, you’re now able to add in a file extension to the fileDictionry. This is because bracket notation supports non-ASCII symbols while the dot notation does not.

fileDictionary['README.md'] = "Hello"
del fileDictionary['README.md']Code language: Python (python)

How to replace operator symbol functionality with custom logic

There’s nothing more Pythonic than the simplicity of using simple mathematical symbols to represent mathematic actions.

After all, what could more clearly represent the sum of two numbers than:

sum = 2 + 2Code language: Python (python)

Meanwhile, if we have a wrapper around a number:

sum = numInstance.getNumber() + numInstance.getNumber()Code language: Python (python)

It gets a bit harder to read through.

What if we could utilize those symbols to handle this custom class logic for us?

sum = numInstance + numInstance;Code language: Python (python)

Luckily we can!

For example, here’s how we can make the + symbol run custom logic:

__add__(self, other)instance + other

class Test:
    __internal = 0
    def __init__(self, val):
        self.__internal = val
    def __add__(self, other):
        return self.__internal + other.__internal
 

firstItem = Test(12)
secondItem = Test(31)

# This will call "__add__" instead of the traditional arithmetic operation
print(firstItem + secondItem)Code language: Python (python)

There’s also other math symbols you can overwrite:

__sub__(self, other)instance - other

__mul__(self, other)instance * other

Manage comparison symbol behavior

Addition, subtraction, and multiplication aren’t the only usages for operator overloading, however. We can also modify the comparison operators in Python to run custom logic.

Let’s say we want to check if two strings match, regardless of casing:

__eq__(self, other)instance == other

class Test():
    str = ""

    def __init__(self, val):
        self.str = val

    def __eq__(self, other):
        return self.str.lower() == other.str.lower()

firstItem = Test("AB")
secondItem = Test("ab")

print(firstItem == secondItem)Code language: Python (python)

You can also have different logic for == and != using __ne__.

__ne__(self, other)instance != other

However, if you don’t provide a __ne__, butdoprovide a __eq__, Python will simply negate the __eq__ logic on your behalf when instance != other is called.

There’s also a slew of magic methods for customizing other comparison operators:

__lt__(self, other)instance &lt; other

__gt__(self, other)instance &gt; other

__le__(self, other)instance &lt;= other

__ge__(self, other)instance &gt;= other

Overwrite a class’s type casting logic

Python, like any other programming language, has the concept of data types. Similarly, you’re able to convert easily from any of those types to another type using built-in methods of type-casting data.

For example, if you call bool() on a string, it will cast the truthy value to a Boolean.

What if you could customize the behavior of the bool() method? You see where we’re going with this…

__bool__(self)bool(instance)

from os.path import exists

class File:
    file_path = ""
   
    def __init__(self, file_path):
        self.file_path = file_path
          # This method should return `True` or `False`
    def __bool__(self):
        return exists(self.file_path)

file = File("temp.txt")

# Will return True or False depending on if file exists
print(bool(file))Code language: Python (python)

There’s also other type casts logic you can customize:

__int__(self)int(instance)

__str__(self)str(instance)

How to make your classes iterable

Let’s say that we’ve used a custom class to build a replacement for a List:

class ListLike:
    length = 0

    def __getitem__(self, key):
        return self.__getattribute__(str(key))

    def __setitem__(self, key, val):
        self.__setattr__(str(key), val)

    def __delitem__(self, key):
        self.__delattr__(key)

    def append(self, val):
        self[str(self.length)] = val
        self.length += 1

listLike = ListLike()
print(listLike.length) # 0
listLike.append("Hello")
listLike.append("World")
print(listLike.length) # 2
print(listLike[0]) # "Hello"Code language: Python (python)

This appears to work amazingly at first glance, until you try to do the following:

[x for x in listLike]Code language: Python (python)

Or any other kind of iteration on the ListLike. You’ll get the following confusingly named error:

'ListLike' object has no attribute '2'Code language: plaintext (plaintext)

This is because Python doesn’t know howto iterate through your class, and therefore attempts to access a property in the class. This is where __iter__ comes into play: It allows you to return an iterable to utilize anytime Python might request iterating through the class, like ina list comprehension.

__iter__(self)[x for x in instance]

class ListLike:
    length = 0
 
    def __getitem__(self, key):
        return self.__getattribute__(str(key))
 
    def __setitem__(self, key, val):
        self.__setattr__(str(key), val)
 
    def __delitem__(self, key):
        self.__delattr__(key)
 
    def __iter__(self):
        isMethod = lambda x: type(x).__name__ == 'method'
        # Only return non-method keys that are not "length"
        return iter([x for x in dir(self) if not x.startswith('__') and x != 'length' and not isMethod(self[x])])
 
    def append(self, val):
        self[str(self.length)] = val
        self.length += 1
 
listLike = ListLike()
 
listLike.append("Hello")
listLike.append("World")
 
[print(x) for x in listLike]Code language: Python (python)

Notice that we’re having to return a real list wrapped in the iter method for the __iter__ return value: This is required by Python. 

If you don’t do this, you’ll get the error:

iter() returned non-iterator of type 'list'

Check if an item exists using the “in” keyword

The __iter__ magic method isn’t the only way to customize traditionally list-like behavior for a class. You can also use the __contains__ method to add support for simple “is this in the class” checks.

__contains__(self, item)key in instance

Something to keep in mind is that if __contains__ isn’t defined, Python will use the information provided by __iter__ to check if the key is present. However, __contains__ is a more optimized method, since the default __iter__ checking behavior will iterate through every key until it finds a match.

Python magic method cheat sheet

Python magic methods can level up your application logic by reducing the amount of boilerplate required to do specific actions, but that’s not its only usecase. Othertimes, you might want to use magic methods to provide an API with a nicer development experience for consuming developers.

That said, we know that with so many magic methods it can be difficult to remember them all. This is why we made a cheat sheet that you can download or print out to reference when writing code. 

Magic methods

In the class tutorial we learned to define a
class like this:

class Website:

    def __init__(self, url, founding_year, free_to_use):
        self.url = url
        self.founding_year = founding_year
        self.free_to_use = free_to_use

    def info(self):
        print("URL:", self.url)
        print("Founding year:", self.founding_year)
        print("Free to use:", self.free_to_use)

After doing that we can create a new Website object like
Website('https://github.com/', 2008, True). Python first creates the
Website object, and then calls __init__ with the arguments we passed
to Website to set it up. Methods that have a name __like_this__ and a
special meaning are called magic methods or special methods.

Most magic methods define what the object has or what it can do, like
«does it have a length» or «can we for loop over it». There are other
magic methods that do other things also, like __init__.

Some magic methods have a default implementation that is used if the
class doesn’t define anything else. For example, if we don’t define an
__init__ then our class will take no arguments and it won’t have any
attributes by default. We’ll learn more about this when we’ll talk about
inheritance.

TODO: write a classes2.md.

Custom length

Let’s get started by defining an object that has a length:

>>> class Thing:
...     def __len__(self):
...         return 5
...
>>> t = Thing()
>>> t
<__main__.Thing object at 0x7f05e4597198>
>>> t.__len__()
5
>>> len(t)
5
>>>

This is what most magic methods are like. So far we have learned to use
len() with lists, strings and other built-in types, but now we can
call len() on our own Thing object. Many things can be fully
customized with magic methods.

Note that magic methods like __len__ need to be defined in the class,
just attaching an attribute called __len__ doesn’t work:

>>> class EmptyThing:
...     pass
...
>>> def length():
...     return 5
...
>>> e = EmptyThing()
>>> e.__len__ = length
>>> e.__len__()
5
>>> len(e)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'EmptyThing' has no len()
>>>

You don’t really need to worry about why Python works like this, but
it’s explained
here
if you want to know more about it.

String representations

You have probably noticed that typing something to the interactive >>>
prompt is not quite the same thing as printing it. For example,
strings behave like this:

>>> 'hello'
'hello'
>>> print('hello')
hello
>>>

If you want to print something the way it’s displayed on the >>>
prompt you can use the repr() function. Here «repr» is short for
«representation».

>>> message = 'hello'
>>> print("the message is", repr(message))
the message is 'hello'
>>>

Combining repr() with string
formatting is also
easy.

>>> print(f"the message is {repr(message)}")
the message is 'hello'
>>>

The __repr__ magic method can be used to customize this. For example,
we can do this:

>>> class Website:
...     def __repr__(self):
...         return '<a Website object>'
...
>>> w = Website()
>>> w.__repr__()
'<a Website object>'
>>> str(w)
'<a Website object>'
>>> print(w)
<a Website object>
>>> w
<a Website object>
>>>

The __repr__ method can return any string, but usually you should
follow one of these styles:

  1. A piece of code that describes how another, similar object can be
    created.

    >>> class Website:
    ...     def __init__(self, name, founding_year):
    ...         self.name = name
    ...         self.founding_year = founding_year
    ...     def __repr__(self):
    ...         return f'Website(name={repr(self.name)}, founding_year={repr(self.founding_year)})'
    ...
    >>> github = Website('GitHub', 2008)
    >>> github
    Website(name='GitHub', founding_year=2008)
    >>>

    This is useful for simple data containers like this Website class.

  2. A description of the object wrapped between < and >.

    >>> class Website:
    ...     def __init__(self, name, founding_year):
    ...         self.name = name
    ...         self.founding_year = founding_year
    ...     def __repr__(self):
    ...         return f'<Website {repr(self.name)}, founded in {repr(self.founding_year)}>'
    ...
    >>> github = Website('GitHub', 2008)
    >>> github
    <Website 'GitHub', founded in 2008>
    >>>

    This style is good when you want to tell more about the object than
    you can by showing the __init__ arguments. Python’s built-in
    things also use this style more:

    >>> import random
    >>> random
    <module 'random' from '/some/path/random.py'>
    >>>

Other magic methods

There are many more magic methods, and I don’t see any reason to list
them all here. The official
documentation has
more information about magic methods if you need it. We’ll go through
using the most important magic methods in the rest of this tutorial, so
if you just keep reading you’ll learn more about them.

When should we use magic methods?

There’s nothing wrong with using __init__ everywhere, but other than
that, magic methods are usually not needed. website.has_user(user) and
user in website.userlist are way better than something weird that we
could do with magic methods like user @ website. People expect
website.has_user(user) check if a user has registered on the website,
but nobody can guess what user @ website does. Explicit is better than
implicit, and simple is better than complex.

On the other hand, using magic methods when needed can turn something
good into something great. Especially the __repr__ method is useful
because people can get a good idea of what an object is by just looking
at it on the >>> prompt or printing it. I recommend using __repr__
methods in things that other people will import and use in their
projects, but __repr__ methods aren’t worth it for simple scripts that
are not meant to be imported.

Summary

  • Magic methods define what instances of a class can do and how, like
    «does it have a length» or «what does it look like when I print it».
  • Python uses magic methods to implement many things internally, and we
    can customize everything by implementing the magic methods
    ourselves.
  • Defining custom __repr__ methods is often a good idea when making
    things that other people will import and use in their own projects,
    and the __init__ method is very useful for many things.
    Other than that, magic methods are usually not worth it.

If you have trouble with this tutorial, please
tell me about it and I’ll make this tutorial better,
or ask for help online.
If you like this tutorial, please give it a
star.

You may use this tutorial freely at your own risk. See
LICENSE.

Previous | Next |
List of contents

Magic methods in Python are the special methods that start and end with the double underscores.
They are also called dunder methods. Magic methods are not meant to be invoked directly by you, but the invocation happens internally from the class on a certain action.
For example, when you add two numbers using the + operator, internally, the __add__() method will be called.

Built-in classes in Python define many magic methods.
Use the dir() function to see the number of magic methods inherited by a class.
For example, the following lists all the attributes and methods defined in the int class.

print(dir(int))

#output:
#['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', 
#'__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', 
#'__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', 
#'__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', 
#'__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__',
#'__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', 
#'__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', 
#'__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__',
#'__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 
#'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

As you can see above, the int class includes various magic methods surrounded by double underscores.
For example, the __add__ method is a magic method which gets called when we add two numbers using the + operator.
Consider the following example.

num=10
res = num.__add__(5) 
print(res)

As you can see, when you do num+10, the + operator calls the __add__(10) method.
You can also call num.__add__(5) directly which will give the same result.
However, as mentioned before, magic methods are not meant to be called directly, but internally, through some other methods or actions.

Magic methods are most frequently used to define overloaded behaviours of predefined operators in Python. For instance, arithmetic operators by default operate upon numeric operands.
This means that numeric objects must be used along with operators like +, -, *, /, etc. The + operator is also defined as a concatenation operator in string, list and tuple classes. We can say that the + operator is overloaded.

In order to make the overloaded behaviour available in your own custom class, the corresponding magic method should be overridden. For example, in order to use the + operator with objects of a user-defined class, it should include the __add__() method.

Let’s see how to implement and use some of the important magic methods.

__new__() method

Languages such as Java and C# use the new operator to create a new instance of a class. In Python the __new__() magic method is implicitly called before the __init__() method. The __new__() method returns a new object, which is then initialized by __init__().

class Employee:
    def __new__(cls):
        print ("__new__ magic method is called")
        inst = object.__new__(cls)
                return inst
    def __init__(self):
        print ("__init__ magic method is called")
        self.name='Satya'

The above example will produce the following output when you create an instance of the Employee class.
The __new__() method is called before the __init__() method.

__str__() method

Another useful magic method is __str__(). It is overridden to return a printable string representation of any user defined class.
We have seen str() built-in function which returns a string from the object parameter. For example, str(12) returns ’12’. When invoked, it calls the __str__() method in the int class.

num=12
val = int.__str__(num)
print(type(val))

Let us now override the __str__() method in the employee class to return a string representation of its object.

class employee:
  def __init__(self):
    self.name='Swati'
    self.salary=10000
  def __str__(self):
    return 'name='+self.name+', salary=$'+str(self.salary)
    
    
e1 = employee()
print(e1)

The str() function internally calls the __str__() method defined in the above employee class. This is why it is called a magic method!

__add__() method

In following example, a class named distance is defined with two instance attributes — ft and inch. The addition of these two distance objects is desired to be performed using the overloading + operator.

To achieve this, the magic method __add__() is overridden, which performs the addition of the ft and inch attributes of the two objects.
The __str__() method returns the object’s string representation.

class distance:
  def __init__(self, x=None,y=None):
    self.ft=x
    self.inch=y
  def __add__(self,x):
    temp=distance()
    temp.ft=self.ft+x.ft
    temp.inch=self.inch+x.inch
    if temp.inch>=12:
        temp.ft+=1
        temp.inch-=12
        return temp
  def __str__(self):
    return 'ft:'+str(self.ft)+' in: '+str(self.inch)
    
    
d1=distance(3,10)
d2=distance(4,4)
print("d1= {} d2={}".format(d1, d2))

d3=d1+d2
print(d3)

The example overloades the __add__() method which will be called when adding two objects using the + operator.

__ge__() method

The following method is added in the distance class to overload the >= operator.

class distance:
  def __init__(self, x=None,y=None):
      self.ft=x
      self.inch=y
  def __ge__(self, x):
      val1=self.ft*12+self.inch
      val2=x.ft*12+x.inch
      if val1>=val2:
          return True
      else:
          return False
          
          
d1=distance(2,1)
d2=distance(4,10)
print(d1>=d2)

The __ge__() method gets invoked when the >= operator is used and returns True or False. Accordingly, the appropriate message can be displayed.

Important Magic Methods

The following tables list important magic methods in Python 3.

Initialization and Construction Description
__new__(cls, other) To get called in an object’s instantiation.
__init__(self, other) To get called by the __new__ method.
__del__(self) Destructor method.
Unary operators and functions Description
__pos__(self) To get called for unary positive e.g. +someobject.
__neg__(self) To get called for unary negative e.g. -someobject.
__abs__(self) To get called by built-in abs() function.
__invert__(self) To get called for inversion using the ~ operator.
__round__(self,n) To get called by built-in round() function.
__floor__(self) To get called by built-in math.floor() function.
__ceil__(self) To get called by built-in math.ceil() function.
__trunc__(self) To get called by built-in math.trunc() function.
Augmented Assignment Description

__iadd__(self, other) To get called on addition with assignment e.g. a +=b.
__isub__(self, other) To get called on subtraction with assignment e.g. a -=b.
__imul__(self, other) To get called on multiplication with assignment e.g. a *=b.
__ifloordiv__(self, other) To get called on integer division with assignment e.g. a //=b.
__idiv__(self, other) To get called on division with assignment e.g. a /=b.
__itruediv__(self, other) To get called on true division with assignment
__imod__(self, other) To get called on modulo with assignment e.g. a%=b.
__ipow__(self, other) To get called on exponentswith assignment e.g. a **=b.
__ilshift__(self, other) To get called on left bitwise shift with assignment e.g. a<<=b.
__irshift__(self, other) To get called on right bitwise shift with assignment e.g. a >>=b.
__iand__(self, other) To get called on bitwise AND with assignment e.g. a&=b.
__ior__(self, other) To get called on bitwise OR with assignment e.g. a|=b.
__ixor__(self, other) To get called on bitwise XOR with assignment e.g. a ^=b.
Type Conversion Magic Methods Description
__int__(self) To get called by built-int int() method to convert a type to an int.
__float__(self) To get called by built-int float() method to convert a type to float.
__complex__(self) To get called by built-int complex() method to convert a type to complex.
__oct__(self) To get called by built-int oct() method to convert a type to octal.
__hex__(self) To get called by built-int hex() method to convert a type to hexadecimal.
__index__(self) To get called on type conversion to an int when the object is used in a slice expression.
__trunc__(self) To get called from math.trunc() method.
String Magic Methods Description
__str__(self) To get called by built-int str() method to return a string representation of a type.
__repr__(self) To get called by built-int repr() method to return a machine readable representation of a type.
__unicode__(self) To get called by built-int unicode() method to return an unicode string of a type.
__format__(self, formatstr) To get called by built-int string.format() method to return a new style of string.
__hash__(self) To get called by built-int hash() method to return an integer.
__nonzero__(self) To get called by built-int bool() method to return True or False.
__dir__(self) To get called by built-int dir() method to return a list of attributes of a class.
__sizeof__(self) To get called by built-int sys.getsizeof() method to return the size of an object.
Attribute Magic Methods Description
__getattr__(self, name) Is called when the accessing attribute of a class that does not exist.
__setattr__(self, name, value) Is called when assigning a value to the attribute of a class.
__delattr__(self, name) Is called when deleting an attribute of a class.
Operator Magic Methods Description
__add__(self, other) To get called on add operation using + operator
__sub__(self, other) To get called on subtraction operation using — operator.
__mul__(self, other) To get called on multiplication operation using * operator.
__floordiv__(self, other) To get called on floor division operation using // operator.
__truediv__(self, other) To get called on division operation using / operator.
__mod__(self, other) To get called on modulo operation using % operator.
__pow__(self, other[, modulo]) To get called on calculating the power using ** operator.
__lt__(self, other) To get called on comparison using < operator.
__le__(self, other) To get called on comparison using <= operator.
__eq__(self, other) To get called on comparison using == operator.
__ne__(self, other) To get called on comparison using != operator.
__ge__(self, other) To get called on comparison using >= operator.

Thus, you can use the appropriate magic methods to add various functionalities in your custom class.

The magic methods in Python programming language are specifically for Object Oriented Design. Every class that we create has its own magic methods. Python’s standard interpreter assigns these to every class we create inside it. So, in this article, we shall see in detail how to call and use magic methods for a better programming approach. Let the coding fun begin!

Brushing up OOP knowledge

Before we get to the main topic, let us understand and polish the knowledge of OOP concepts. We shall see only the basics. So, Object Oriented Programming is a way of enclosing the data members and member functions into a user-defined entity called a Class.

Class is something that holds particular data items that relate to each other and communicate in a specific way. We access the properties and the member functions using Object. The object is an instance of a class. In any programming language, memory is never allocated when we create a class, but it is actually created when we create its instance I.e object.

Example:

The animal is a type of class. In that, we include all the living beings that reside on Earth. So, everyone has their own way of living, food, and shelter. The animal just defines the blueprint of all these. For example, the cat is the object of the Animal class. It has four legs, eats mice, and lives in houses or bushes. In the same way, the tiger has four legs but it kills and eats many animals so we say that the tiger eats meat, it lives in the forest.

Code Example with Python:

class Animal:
    def __init__(self, legs, food, shelter):
        self.legs = legs
        self.food = food
        self.shelter = shelter
        
    def showAnimal(self):
        print("The animal has {} legs: ".format(self.legs))
        print("The animal eats: {}".format(self.food))
        print("The animal lives in: {}".format(self.shelter))
        
cat = Animal(4, "Mouse", "House")
tiger = Animal(4, "Meat", "Forest")
cat.showAnimal()
tiger.showAnimal()

Output:

The animal has 4 legs: 
The animal eats: Mouse
The animal lives in: House
The animal has 4 legs: 
The animal eats: Meat
The animal lives in: Forest

Explanation:

  1. The animal class contains the number of legs, food, and shelter as properties.
  2. When we create an instance and insert the values inside the constructor, the difference in their behaviour is clear.
  3. So, objects of the same class can be different according to the behaviour of values.

So, in the above example, we have a class as Animal. Python has a set of methods namely Dunder methods that are responsible for holding the properties, data members, and member functions of a class.

Definition: When we create an object, the Python interpreter calls special functions in the backend of code execution. They are known as Magic Methods or Dunder Methods.

Why do we say Dunder? Because their names lie between double underscores. They perform some set of calculations that are just like magic while we create an object of a class. So, how do we check what are they and how many of them are there in the standard class? Use the steps below to find them:

  1. Create a sample class.
  2. Create its object.
  3. Use the dir() function and insert the object inside it.
  4. This function prints a list of all the Magic Methods along with data members and member functions that are assigned to the class.

Code:

Output:

__class__
__delattr__
__dict__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__gt__
__hash__
__init__
__init_subclass__
__le__
__lt__
__module__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
__weakref__
food
legs
shelter
showAnimal

The names that you see in double underscores are all magic methods. The rest of the properties are defined by the user. As we can see __init__() which is the constructor of any class in Python, is also a magic method. Let us see their use one by one. To understand their functionality always try to override the functions.

One thing to note is that, for any class the user defines, there is some set of default magic methods with respect to each of them.

Use and implementation of some magic methods

In this section, we shall see the use and implementation and use of some magic methods for writing a better OOP design.

1. __new__():

This method helps the constructor __init__() method to create objects for a class. So, when we create an instance of a class, the Python interpreter first calls the __new__() method and after that__init__() method. They work hand in hand with each other.

  1. When a programmer opts to create an object, __new__() gets invoked that accepts the name of the object.
  2. Then __init__() is invoked where the parameters including self are inserted into the object which in turn helps us to modify the class properties.

Code:

class Sample:
    def __new__(self, parameter):
        print("new invoked", parameter)
        return super().__new__(self)
        
    def __init__(self, parameter):
        print("init invoked", parameter)
        
obj = Sample("a")

Output:

new invoked a
init invoked a

Explanation:

  1. First, we create a class as Sample.
  2. Then override the __new__() method by creating it. Then, as usual, the self parameter comes, and after that gives a simple parameter.
  3. Return a super() function with the __new__() function with the self parameter to get access to the customizations we make to the method.
  4. Then, with the same practice call the __init__() function with some parameter.
  5. After that create an object of a sample class.
  6. Now, when we run the code, the interpreter first calls the __new__(), and then it calls __init__() method.

2. __init__():

Python is an Object Oriented Programming language. So, the class must have a constructor. This requirement is fulfilled using the __init__() method. When we create a class and want to give some initial parameters to it. The initializer method performs this task for us.

Code:

class Sample:        
    def __init__(self, parameter):
        print("init invoked", parameter)

obj = Sample("a")

Output:

Explanation:

  1. Create/override the __init__() method. Insert self parameter to signal the interpreter that this is a class method.
  2. Insert required parameter.
  3. Then print that parameter using the print() function.
  4. After that create an object.
  5. When we run the code we get the output as “init invoked a”, this states that the interpreter calls init() and prints that parameter.

3. __str__():

This method helps us to display the object according to our requirements. So, let us say that when we create an object and try to print it. The print() function displays the memory location of the object. If we want to modify we can do this. The __str__() function gives a nice representation of the object.

Code (before using __str__()):

class Student:
    def __init__(self, name, roll_no):
        self.name = name
        self.roll_no = roll_no
        
stud_1 = Student("Suresh", 1)
print(stud_1) 

Output:

<__main__.Student object at 0x0000023E2CF37CA0>

Code (after using __str__()):

class Student:
    def __init__(self, name, roll_no):
        self.name = name
        self.roll_no = roll_no
        
    def __str__(self):
        return ("{} {}".format(self.name, self.roll_no))
        
stud_1 = Student("Suresh", 1)
print(stud_1) 

Output:

Cool right! now we can also use similar methods. We can format the object as according to our needs.

4. __repr__():

Similar to the __str__(), we can use __repr__ function for the decoration of objects. The code is similar to __str__() implementation.

class Student:
    def __init__(self, name, roll_no):
        self.name = name
        self.roll_no = roll_no
        
    def __repr__(self):
        print("repr invoked")
        return ("{} {}".format(self.name, self.roll_no))
        
stud_1 = Student("Suresh", 1)
print(stud_1) 

Output:

5. __sizeof__():

When we create a class the interpreter never assigns memory to it. It assigns memory to the object. If we want to know the memory allocated to that object, then we can call or override the __sizeof__() function and pass our object. This also returns size of a list =, tuple, dictionary object.

Code:

class Student:
    def __init__(self, name, roll_no):
        self.name = name
        self.roll_no = roll_no
        
stud_1 = Student("Suresh", 1)
print("Size of student class object: ", stud_1.__sizeof__()) 

list_1 = [1, 2, 3, 4]
tup_1 = (1, 2, 3, 4, 5)
dict_1 = {"a":1, "b":2, "c":3, "d":4}
print("Size of list: ", list_1.__sizeof__())
print("Size of tuple: ", tup_1.__sizeof__())
print("Size of dictionary: ", dict_1.__sizeof__())

Output:

Size of student class object:  32
Size of list object:  104
Size of tuple object:  64
Size of dictionary object:  216

6. __add__():

This magic method is specifically similar to its name. It adds two variables. For an integer it returns the sum, for a string, it returns their concatenation result.

Code:

class Numbers:
    def __init__(self, a, b):
        self.a = a
        self.b = b
        
    def __add__(self):
        print("__add__ invoked")
        return self.a + self.b

num = Numbers(3, 4)
num_2 = Numbers("a", "b")
print(num.__add__())
print(num_2.__add__())

Output:

__add__ invoked
7
__add__ invoked
ab

7. __reduce__():

This magic method returns a set or a dictionary of all the parameters of a class and their values in key: value format. This can be directly called using the object name with the dot operator. So, when we create a class and instantiate it with some values. The function shall return it with the name of the parameters which were given during the declaration of a class.

Code:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
        
emp = Employee("Shrinivas", 150000)
print(emp.__reduce__())

Output:

(<function _reconstructor at 0x0000023E22892EE0>, (<class '__main__.Employee'>, <class 'object'>, None), {'name': 'Shrinivas', 'sal': 150000})

Code (after overriding __reduce__()):

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
        
    def __reduce__(self):
        return self.name, self.sal
        
emp = Employee("Shrinivas", 150000)
print(emp.__reduce__())

Output:

Explanation:

When we override and try to return the parameters, we only get their values in a set.

8. __hash__():

The __hash__() function returns a specific hash value of the object stored in the heap memory. We can either override it or call it using the object name. Hashing is very useful to fetch the memory address of any random element in a computer. All programming languages use hash for the sake of simplicity and for memory allocation.

Code:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
    
    def __hash__(self):
        return super().__hash__()
        
emp = Employee("Shrinivas", 150000)
print(emp.__hash__())

Output:

Code:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
    
emp = Employee("Shrinivas", 150000)
print(emp.__hash__())

Output:

9. __getattribute__(name):

This function returns the value of the attribute of a class if it exists. We need to call the function and pass the attribute which we assigned to the class parameter using the self keyword. Like if we assign the value of salary to self.sal we need to call sal inside the __getattribute__() function.

Code:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary
        
    def __getattribute__(self, name):
        return super().__getattribute__(name)
        
emp = Employee("Ravi", 500000)
print(emp.__getattribute__("sal"))

Output:

Explanation:

In this function, the “self.sal” is assigned to the salary parameter of the Employee class. The function returns its value as the attribute that exists inside the class. If it does not exist the function returns an error message.

10. __setattr__(name, value):

As the name suggests, this magic method helps us change the value of an attribute when we define the object. No need to override the __getattribute__() and __setattr__() functions. Just call them using the objects created.

Code:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.sal = salary

        
emp = Employee("Ravi", 500000)
emp.__setattr__("name", "Suresh")
emp.__setattr__("sal":600000)
print("The changed name of the employee is: ", emp.__getattribute__("name"))
print("The changed salary of the employee is: ", emp.__getattribute__("sal"))

        

Output:

The changed name of the employee is: Suresh
The changed salary of the employee is: 600000

Explanation:

  1. the __setattr__() take two parameters.
    1. name of attribute
    2. its new value
  2. Then it assigns that particular value to that attribute.
  3. After that, to check the value assigned to it call the __getattrbute__() function using the employee object and dot operator. emp.__getattribute(“name”).

Point to note: These two functions replace getter and setter methods for a class in Python.

Conclusion

So, we saw the in-depth implementation of some of the magic methods in Python. I hope this helps and will make programming easier. They prove to be helpful pieces of code for quick implementation and use. Happy python programming 🐍🐍😎.

Понравилась статья? Поделить с друзьями:
  • Анаферон капли инструкция по применению для детей с 1 месяца
  • Лазерный уровень zitrek ll1v1h инструкция по применению
  • Краска молотковая хаммерайт инструкция по применению
  • Должностные инструкции для школы 2022 год
  • Рексетин инструкция по применению цена отзывы аналоги таблетки цена