
В Python очень классной идеей является возможность переопределять взаимодействие объекта с любыми операторами, в том числе и унарные операторы.И это можно очень интересно применить!
Долгое время меня смущало отсутствие возможности инкрементировать число на 1 с помощью синтаксиса С++. То есть вот так:
val++
Эта команда просто прибавляет единицу к значению val. В Python аналогичная операция делается так:
val += 1
Ну такой cебе ZEN😭.
Давайте напишем класс, который сможет провернуть что-то подобное. А именно, сделаем чтобы величина значения
увеличивалась на 1 с помощью такой записи:
>>> c = Counter()
>>> print(c)
0
>>> +c
1
Да, вместо x++ мы сделаем +x, чтобы не конфликтовать со стандартным синтаксисом. Но и это уже не плохо!
Для взаимодействия с оператором "+" есть магический метод __add__, но для бинарного оператора.
То есть когда операнда два. Для унарной версии оператора "+" есть метод __pos__, что означает positive. То есть как себя ведёт объект когда его пытаются сделать положительным. Вот его и используем:
class Counter(object):
def __init__(self, init=0):
self.val = init
def __pos__(self, *args):
self.val += 1
def __repr__(self):
return 'Count: {}'.format(self.val)
Чтобы удобно было смотреть на результат, добавил метод __repr__. Проверяем что получится.
>>> c = Counter()
>>> print(c)
Count: 0
>>> +c
Count: 1
>>> +c
Count: 2
Отлично, сделали счётчик с необычным синтаксисом! Добавим аналогичный метод для оператора "-" __neg__, что означает negate и описывает реакцию на попытку сделать число отрицательным. Теперь можем двигать значение в обе стороны:
>>> class Counter(object):
...
>>> def __neg__(self):
>>> self.val -= 1
>>> c = Counter(5)
>>> print(c)
Count: 5
>>> +c
Count: 6
>>> -c
Count: 5
Стоит учесть, что данная реализация методов изменяет сам объект а не возвращает новый изменённый, как это принято в Python. Так что весь пример не очень Pythonic-way ). Но такое решение реализует необходимый нам минималистичный синтаксис.
Не буду утверждать, что пример получился полезным с точки зрения использования в реальной работе. Но для практики изучения вышло вполне интересно.
Давайте не останавливаться на унарных и добавим обработку бинарных, то есть обычные "+" и "-".
Сделаем возможность прибавлять к канутеру любое число обычным синтаксисом
def __add__(self, other):
if not isinstance(other, (int, float)):
raise TypeError
return Counter(int(self.val+other))
def __sub__(self, other):
if not isinstance(other, (int, float)):
raise TypeError
return Counter(int(self.val-other))
Теперь можно делать так:
>>> c = Counter(3)
>>> c + 2
Count: 5
В этом случае возвращается новый каунтер, старый останется независимым. Добавим обработку для случая когда наш каунтер справа
def __radd__(self, other):
return self.__add__(other)
def __rsub__(self, other):
return self.__sub__(other)
>>> c = Counter(3)
>>> 2 + с
Count: 5
Добавим в __pos__ и __neg__ возвращаемое значение:
def __pos__(self, *args):
self.val += 1
return self
def __neg__(self):
self.val -= 1
return self
Теперь такой синтаксис тоже допустим (и тоже антипаттерн):
>>> c = Counter()
>>> print(c)
Count: 0
>>> ++c
Count: 2
>>> ++++c
Count: 6
>>> ---c
Count: 3
Следующие две записи идентичный по результату
с += 2
# or
++с
В обоих случаях каунтер с увеличится на 2.
Если инкремент изменить на 0.5, то у нас получится синтаксис весьма похожий на C. То есть, чтобы прибавить 1 надо будет написать ++x 😎