https://habrahabr.ru/post/349776/- Программирование
- Ненормальное программирование
- Python
>>> +--+_+-+_++_+--_+_-_+-+-+-___++++_+-_-+++_+-+_--++--_
'ПРИВЕТ, ХАБР!'
Что это было? Да, вы не ошиблись — это азбука Морзе с плюсиками вместо точек прямо в синтаксисе Питона!
Если вы не понимаете, как это работает, или просто не прочь освежить свои знания в День Советской армии (и Военно-морского флота!), добро пожаловать под кат.
Унарные операторы в Python
В Питоне есть три унарных оператора:
+,
- и
~ (побитовое отрицание). (Есть ещё
not, но это отдельная история.) Интересно то, что их можно комбинировать в неограниченных количествах:
>>> ++-++-+---+--++-++-+1
-1
>>> -~-~-~-~-~-~-~-~-~-~1
11
И все три из них можно переопределить для своих объектов.
Но только у двух из них — плюса и минуса — есть омонимические бинарные варианты. Именно это позволит нам скомбинировать несколько последовательностей плюсов и минусов, каждая из которых будет одной буквой в азбуке Морзе, в единое валидное выражение: приведённая в начале строка
+--+_+-+_++_+--_+_-_+-+-+-___++++_+-_-+++_+-+_--++--_ распарсится как
(+--+_) + (-+_) + (+_) + (--_) + _ - _ + (-+-+-___) + (+++_) + (-_) - (+++_) + (-+_) - (-++--_)
Осталось определить объекты
_ (конец последовательности) и
___ (конец последовательности и пробел).
Переопределение операторов в Python
Для переопределения операторов в Python нужно объявлять в классе методы со специальными названиями. Так, для унарных плюса и минуса это
__pos__ и
__neg__, а для бинарных — это сразу четыре метода:
__add__,
__radd__,
__sub__ и
__rsub__.
Давайте заведём простенький класс, инстансом которого будет наш
_. В первую очередь ему нужно поддерживать унарные операторы и накапливать факты их применения:
class Morse(object):
def __init__(self, buffer=""):
self.buffer = buffer
def __neg__(self):
return Morse("-" + self.buffer)
def __pos__(self):
return Morse("." + self.buffer)
Также наш объект должен уметь конвертироваться в строчку. Давайте заведём словарь с расшифровкой азбуки Морзе и добавим метод
__str__.
Азбука Морзеmorse_alphabet = {
"А" : ".-",
"Б" : "-...",
"В" : ".--",
"Г" : "--.",
"Д" : "-..",
"Е" : ".",
"Ж" : "...-",
"З" : "--..",
"И" : "..",
"Й" : ".---",
"К" : "-.-",
"Л" : ".-..",
"М" : "--",
"Н" : "-.",
"О" : "---",
"П" : ".--.",
"Р" : ".-.",
"С" : "...",
"Т" : "-",
"У" : "..-",
"Ф" : "..-.",
"Х" : "....",
"Ц" : "-.-.",
"Ч" : "---.",
"Ш" : "----",
"Щ" : "--.-",
"Ъ" : "--.--",
"Ы" : "-.--",
"Ь" : "-..-",
"Э" : "..-..",
"Ю" : "..--",
"Я" : ".-.-",
"1" : ".----",
"2" : "..---",
"3" : "...--",
"4" : "....-",
"5" : ".....",
"6" : "-....",
"7" : "--...",
"8" : "---..",
"9" : "----.",
"0" : "-----",
"." : "......",
"," : ".-.-.-",
":" : "---...",
";" : "-.-.-.",
"(" : "-.--.-",
")" : "-.--.-",
"'" : ".----.",
"\"": ".-..-.",
"-" : "-....-",
"/" : "-..-.",
"?" : "..--..",
"!" : "--..--",
"@" : ".--.-.",
"=" : "-...-",
}
inverse_morse_alphabet = {v: k for k, v in morse_alphabet.items()}
Метод:
def __str__(self):
return inverse_morse_alphabet[self.buffer]
# Если в словаре нет текущей последовательности,
# то это KeyError. Ну и отлично.
Далее, бинарное сложение и вычитание. Они в Питоне левоассоциативны, то бишь будут выполняться слева направо. Начнём с простого:
def __add__(self, other):
return str(self) + str(+other)
# Обратите внимание на унарный + перед other.
Итак, после сложения первых двух последовательностей у нас получится строка. Сможет ли она сложиться со следующим за ней объектом типа
Morse? Нет, сложение с этим типом в
str.__add__ не предусмотрено. Поэтому Питон попытается вызвать у правого объекта метод
__radd__. Реализуем его:
def __radd__(self, s):
return s + str(+self)
Осталось сделать аналогично для вычитания:
def __sub__(self, other):
return str(self) + str(-other)
def __rsub__(self, s):
return s + str(-self)
Весь класс вместеclass Morse(object):
def __init__(self, buffer=""):
self.buffer = buffer
def __neg__(self):
return Morse("-" + self.buffer)
def __pos__(self):
return Morse("." + self.buffer)
def __str__(self):
return inverse_morse_alphabet[self.buffer]
def __add__(self, other):
return str(self) + str(+other)
def __radd__(self, s):
return s + str(+self)
def __sub__(self, other):
return str(self) + str(-other)
def __rsub__(self, s):
return s + str(-self)
Давайте напишем простенькую функцию, которая будет конвертировать нам строки в код на Питоне:
def morsify(s):
s = "_".join(map(morse_alphabet.get, s.upper()))
s = s.replace(".", "+") + ("_" if s else "")
return s
Теперь мы можем забить всю эту красоту в консоль и увидеть, что код работает:
>>> morsify("ПРИВЕТ,ХАБР!")
'+--+_+-+_++_+--_+_-_+-+-+-_++++_+-_-+++_+-+_--++--_'
>>> _ = Morse()
>>> +--+_+-+_++_+--_+_-_+-+-+-_++++_+-_-+++_+-+_--++--_
'ПРИВЕТ,ХАБР!'
Добавляем поддержку пробелов
Давайте сделаем объект, который будет вести себя как
Morse, только ещё добавлять пробел в конце.
class MorseWithSpace(Morse):
def __str__(self):
return super().__str__() + " "
___ = MorseWithSpace()
Просто? Да! Работает? Нет :-(
Чтобы в процессе работы объекты типа
MorseWithSpace не подменялись объектами типа
Morse, надо ещё поменять
__pos__ и
__neg__:
def __neg__(self):
return MorseWithSpace(super().__neg__().buffer)
def __pos__(self):
return MorseWithSpace(super().__pos__().buffer)
Также стоит добавить запись
" " : " " в словарь азбуки Морзе и поменять чуть-чуть функцию morsify:
def morsify(s):
s = "_".join(map(morse_alphabet.get, s.upper()))
s = s.replace(".", "+") + ("_" if s else "")
s = s.replace("_ ", "__").replace(" _", "__")
return s
Работает!
>>> morsify("ПРИВЕТ, ХАБР!")
'+--+_+-+_++_+--_+_-_+-+-+-___++++_+-_-+++_+-+_--++--_'
>>> ___ = MorseWithSpace()
>>> +--+_+-+_++_+--_+_-_+-+-+-___++++_+-_-+++_+-+_--++--_
'ПРИВЕТ, ХАБР!'
Весь код в Gist.
Заключение
Переопределение операторов может завести вас далеко и надолго.
Не злоупотребляйте им!