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