>>> +--+_+-+_++_+--_+_-_+-+-+-___++++_+-_-+++_+-+_--++--_
'ПРИВЕТ, ХАБР!'
Что это было? Да, вы не ошиблись — это азбука Морзе с плюсиками вместо точек прямо в синтаксисе Питона!
Если вы не понимаете, как это работает, или просто не прочь освежить свои знания в День Советской армии (и Военно-морского флота!), добро пожаловать под кат.
Унарные операторы в 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.
Заключение
Переопределение операторов может завести вас далеко и надолго.
Не злоупотребляйте им!