В этом сценарии мы реализуем класс, который создает объекты-словари, принимающие только целые и плавающие значения.
При добавлении других типов данных, таких как строки, списки и кортежи, будет появляться исключение, указывающее пользователю, что пользовательский объект dict может принимать только целые и плавающие числа в качестве значений.
Для реализации этого процесса используются следующие методы:
Для начала создаем пользовательский класс CustomIntFloat и передаем dict в список наследования аргументов. Это означает, что созданный объект будет вести себя как словарь, за исключением тех мест, в которых это поведение будет выборочно изменено.
Затем создаем метод __init__, чтобы сконструировать объект dict CustomIntFloat, который принимает ключ и значение в список аргументов, установленный в тип None по умолчанию. Таким образом, если пользователь создает объект класса CustomIntFloat без передачи ключа или значения, будет сгенерирован пустой dict. Данное условие гласит: если ключ не передан, то параметру ключа по умолчанию присваивается аргумент None, а пустой dict создается путем ссылки на объект CustomIntFloat с атрибутом класса empty_dict.
Если пользователь указывает ключ length и соответствующее значение, которое является экземпляром класса int или float, то ключ и значение будут установлены в объекте.
Наконец, если пользователь указывает несколько ключей и значений в качестве итерируемых в операторе else, то они будут заархивированы функцией zip и им будет присвоено имя переменной zipped. Выполняем цикл на zipped, чтобы проверить, имеет ли значение тип int или float. Если нет, то будет сгенерировано пользовательское исключение CustomIntFloatError.
При генерации исключения CustomIntFloatError создается экземпляр класса CustomIntFloatError.
Таким образом, этот пользовательский класс исключений нуждается в помощи magic-методов __init__ и __str__. Созданный экземпляр принимает переданное значение и устанавливает его в качестве значения атрибута в классе CustomIntFloatError.
Это означает, что при появлении сообщения об ошибке значение, переданное в __init__ объекта CustomIntFloat, может быть установлено как атрибут (self.value) в классе CustomIntFloatError и с легкостью проверено.
Если ввод неверный, то появляется исключение CustomIntFloatError, а объект не создается. Сообщение об ошибке информирует пользователя о том, что допустимыми являются только целые и плавающие значения.
Аналогичным образом при попытке создать экземпляр объекта z (который был закомментирован) с несколькими ключами и значениями, возникает то же исключение, информирующее пользователя о том, что ‘three’ не является допустимым вводом.
# z = CustomIntFloat(key=['a', 'b', 'c'], value=[1, 2, 'three'])
z = CustomIntFloat(key=['a', 'b', 'c'], value=[1, 2, 3])
__setitem__ — это magic-метод, который вызывается при установке ключа и значения в словаре. Если после создания объекта CustomIntFloat пользователь попытается добавить значение, которое не относится к типу int или float, появится то же исключение CustomIntFloatError. Ниже показано, как установить ключ и значение:
x = CustomIntFloat('a', 1)
print(type(x))
x['b'] = 2.1
print(x)
# x['c'] = 'Three'
В результате недопустимого ввода возникает исключение CustomIntFloatError:
Исходный код:
class CustomIntFloatError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return self.value + ' is not valid\nOnly Integers and floats are valid values \nin CustomIntFloat(dict) '
class CustomIntFloat(dict):
empty_dict = {}
def __init__(self, key=None, value=None):
if key is None:
CustomIntFloat.empty_dict = {}
elif len(key) == 1 and isinstance(value, (int, float)):
dict.__setitem__(self, key, value)
else:
zipped = zip(key, value)
for tup in zipped:
if isinstance(tup[1], (int, float)):
dict.__setitem__(self, tup[0], tup[1])
else:
raise CustomIntFloatError(tup[1])
def __setitem__(self, key, value):
if not isinstance(value, (int, float)):
raise CustomIntFloatError(value)
return dict.__setitem__(self, key, value)
С помощью наследования через такие встроенные классы, как dict, можно настраивать поведение через повторную реализацию magic-методов. У этого подхода есть множество преимуществ.
Стоит отметить, что пользователю не нужно изучать новый синтаксис. Он может добавить ключ и значение к объекту dict CustomIntFloat привычным образом. Единственным отличием является выбор допустимых значений типа int и float. Если пользователь указывает любой другой тип, то сообщение об ошибке информирует его об этом и указывает допустимые типы значений.
С помощью magic-методов можно также воспользоваться математическими операторами в Python. Рассмотрим на примере таких методов, как __add__, __sub__ и __mul__ в созданном нами пользовательском объекте.
Такие операторы, как +, -, / и *, являются полиморфными методами. Как показано ниже, знак плюса (+) является полиморфным и может использоваться для объединения строк, суммирования целых чисел и комбинирования списков. Это возможно благодаря тому, что такие типы, как str, list и int, обладают методом add в соответствующих классах. Python просто преобразует знак + в вызов метода __add__ для объекта, который его вызвал (см. примеры ниже).
Это означает, что при включении метода __add__ в класс можно воспользоваться знаком + в объектах.
Создаем класс NumOperations, который генерирует объекты NumOperations. Когда пользователь этого класса передает список в список аргументов __init__, он устанавливается в качестве атрибута в объекте NumOperations и получает название .math_list.
После создания объекта(ов) NumOperations можно с легкостью использовать magic-методы для работы с ними и передачи математической операции.
Например, magic-метод __sub__ принимает 2 объекта NumOperations, объединяет их списки и просматривает другие соответствующие им списки кортежей. Второй элемент в кортеже вычитается из первого, и это значение добавляется в новый список minuslst и передается в качестве аргумента в конструктор NumOperations.
Теперь он возвращает новый объект NumOperations.
Эта передача выполняется по методу __sub__. Это означает, что можно воспользоваться оператором минус (-).
Magic-метод __repr__ реализуется повторно, чтобы возвращать представление строки списка, установленное в новом объекте. Он был изменен, поэтому когда пользователь печатает выходные данные двух объектов NumOperations, результат будет соответствовать ожиданиям.
Ниже представлен список, где элементы были вычтены друг из друга:
[90, 81, 72, 63, 54].
Методы __add__ и __mul__ реализуются аналогично __sub__, однако используют списковое включение для сокращения количества строк кода.
Magic-методы вычитания, сложения и умножения были определены для работы с пользовательскими объектами NumOperation.Такое поведение при передаче аналогично таким пакетам анализа данных, как Pandas и Numpy.
Методы __add__ и __mul__ также предназначены для работы с двумя объектами NumOperations. Это означает, что пользователь может воспользоваться оператором плюс + и умножением *. Как видно из приведенного ниже примера, q является результатом x * y, который возвращает новый объект NumOperations. При вводе q мы получаем представление строки операции передачи в виде списка.
Исходный код доступен по ссылке на GitHub gist:
class NumOperations(object):
def __init__(self, math_list):
self.math_list = math_list
def __sub__(self, other):
minuslst = []
zipped = zip(self.math_list, other.math_list)
for tup in zipped:
minuslst.append(tup[0] - tup[1])
return NumOperations(minuslst)
def __add__(self, other):
addlst = [x + y for x, y in zip(self.math_list, other.math_list)]
return NumOperations(addlst)
def __mul__(self, other):
mullst = [x * y for x, y in zip(self.math_list, other.math_list)]
return NumOperations(mullst)
def __repr__(self):
return str(self.math_list)
x = NumOperations([100, 90, 80, 70, 60])
y = NumOperations([10, 9, 8, 7, 6])
p = x - y
z = x + y
q = x * y
print('Subtraction: ' + str(p))
print('Addition: ' + str(z))
print('Multiplication: ' + str(q))
Перевод статьи Stephen Fordham: Using Magic Methods in Python
Комментарии