Руководство по магическим методам в Питоне
Время на прочтение
28 мин
Количество просмотров 482K
Это перевод 1.17 версии руководства от Rafe Kettler.
Содержание
- Вступление
- Конструирование и инициализация
- Переопределение операторов на произвольных классах
- Магические методы сравнения
- Числовые магический методы
- Представление своих классов
- Контроль доступа к атрибутам
- Создание произвольных последовательностей
- Отражение
- Вызываемые объекты
- Менеджеры контекста
- Абстрактные базовые классы
- Построение дескрипторов
- Копирование
- Использование модуля pickle на своих объектах
- Заключение
- Приложение 1: Как вызывать магические методы
- Приложение 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
- Introduction
- Construction and Initialization
- Making Operators Work on Custom Classes
- Comparison magic methods
- Numeric magic methods
- Representing your Classes
- Controlling Attribute Access
- Making Custom Sequences
- Reflection
- Abstract Base Classes
- Callable Objects
- Context Managers
- Building Descriptor Objects
- Copying
- Pickling your Objects
- Conclusion
- Appendix 1: How to Call Magic Methods
- 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 passed10
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 statementdel x
(so that code would not translate tox.__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 ifself < other
, zero ifself == other
, and positive ifself > 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 Word
s (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, whendivmod(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 returnNone
if type conversion is impossible. Otherwise, it should return a pair (2-tuple) ofself
andother
, 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 betweenstr()
andrepr()
is intended audience.repr()
is intended to produce output that is mostly machine-readable (in many cases, it could be valid Python code even), whereasstr()
is intended to be human-readable. __unicode__(self)
- Defines behavior for when
unicode()
is called on an instance of your class.unicode()
is likestr()
, but it returns a unicode string. Be wary: if a client callsstr()
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 calla.__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
implieshash(a) == hash(b)
. __nonzero__(self)
- Defines behavior for when
bool()
is called on an instance of your class. Should returnTrue
orFalse
, depending on whether you would want to consider the instance to beTrue
orFalse
. __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 (callingdel 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 subclassingobject
. 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 anAttributeError
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 andKeyError
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 raiseKeyError
andTypeError
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 formfor x in container:
. Iterators are their own objects, and they also must define an__iter__
method that returnsself
. __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 usingin
andnot 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 returnsTrue
if it comes across the item it’s looking for.__missing__(self, key)
__missing__
is used in subclasses ofdict
. It defines behavior for whenever a key is accessed that does not exist in a dictionary (so, for instance, if I had a dictionaryd
and saidd["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 asx.__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 thewith
statement, or the name after theas
. __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
, andtraceback
will beNone
. Otherwise, you can choose to handle the exception or let the user handle it; if you want to handle it, make sure__exit__
returnsTrue
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 andvalue
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, callcopy.deepcopy()
on that attribute withmemodict
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 firstItem
Code 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"
pass
Code 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.README
Code 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 + 2
Code 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 < other
__gt__(self, other)
– instance > other
__le__(self, other)
– instance <= other
__ge__(self, other)
– instance >= 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:
-
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.
-
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:
- The animal class contains the number of legs, food, and shelter as properties.
- When we create an instance and insert the values inside the constructor, the difference in their behaviour is clear.
- 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:
- Create a sample class.
- Create its object.
- Use the dir() function and insert the object inside it.
- 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.
- When a programmer opts to create an object, __new__() gets invoked that accepts the name of the object.
- 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:
- First, we create a class as Sample.
- Then override the __new__() method by creating it. Then, as usual, the self parameter comes, and after that gives a simple parameter.
- Return a super() function with the
__new__()
function with the self parameter to get access to the customizations we make to the method. - Then, with the same practice call the
__init__()
function with some parameter. - After that create an object of a sample class.
- 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:
- Create/override the __init__() method. Insert self parameter to signal the interpreter that this is a class method.
- Insert required parameter.
- Then print that parameter using the print() function.
- After that create an object.
- 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:
- the __setattr__() take two parameters.
- name of attribute
- its new value
- Then it assigns that particular value to that attribute.
- 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 🐍🐍😎.