Унарный оператор в Python

Унарный оператор в Python

  • 2020-06-10 / 18:50

В 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  😎