Indulging in unary operators in python
>>> +--+_+-+_++_+--_+_-_+-+-+-___++++_+-_-+++_+-+_--++--_
'ПРИВЕТ, ХАБР!'
What was it? Yes, you were not mistaken - this is Morse code with pluses instead of dots directly in Python syntax!
If you do not understand how this works, or simply do not mind refreshing your knowledge on Soviet Army Day (and the Navy!), Welcome to cat.
Unary operators in Python
In Python, there are three unary operator:
+
, -
and ~
(bitwise negation). (There is still not
, but this is a different story.) The interesting thing is that they can be combined in unlimited quantities:>>> ++-++-+---+--++-++-+1-1>>> -~-~-~-~-~-~-~-~-~-~111
And all three of them can be redefined for their objects.
But only two of them - plus and minus - have homonymous binary options. This will allow us to combine several sequences of pluses and minuses, each of which will be a single letter in Morse code, into a single valid expression: the line at the beginning is
+--+_+-+_++_+--_+_-_+-+-+-___++++_+-_-+++_+-+_--++--_
parsed as
(+--+_) + (-+_) + (+_) + (--_) + _ - _ + (-+-+-___) + (+++_) + (-_) - (+++_) + (-+_) - (-++--_)
It remains to determine the objects
_
(end of sequence) and ___
(end of sequence and space).Overriding statements in Python
To override operators in Python, you need to declare methods with special names in the class. So, for the unary plus and minus it
__pos__
, and __neg__
, as for binary - it's just the four methods: __add__
, __radd__
, __sub__
and __rsub__
. Let's get a simple class whose instance will be ours
_
. First of all, he needs to support unary operators and accumulate facts of their application:classMorse(object):def__init__(self, buffer=""):
self.buffer = buffer
def__neg__(self):return Morse("-" + self.buffer)
def__pos__(self):return Morse("." + self.buffer)
Also, our object should be able to convert to a line. Let's start a dictionary with Morse code decoding and add a method
__str__
.Morse code
morse_alphabet = {
"А" : ".-",
"Б" : "-...",
"В" : ".--",
"Г" : "--.",
"Д" : "-..",
"Е" : ".",
"Ж" : "...-",
"З" : "--..",
"И" : "..",
"Й" : ".---",
"К" : "-.-",
"Л" : ".-..",
"М" : "--",
"Н" : "-.",
"О" : "---",
"П" : ".--.",
"Р" : ".-.",
"С" : "...",
"Т" : "-",
"У" : "..-",
"Ф" : "..-.",
"Х" : "....",
"Ц" : "-.-.",
"Ч" : "---.",
"Ш" : "----",
"Щ" : "--.-",
"Ъ" : "--.--",
"Ы" : "-.--",
"Ь" : "-..-",
"Э" : "..-..",
"Ю" : "..--",
"Я" : ".-.-",
"1" : ".----",
"2" : "..---",
"3" : "...--",
"4" : "....-",
"5" : ".....",
"6" : "-....",
"7" : "--...",
"8" : "---..",
"9" : "----.",
"0" : "-----",
"." : "......",
"," : ".-.-.-",
":" : "---...",
";" : "-.-.-.",
"(" : "-.--.-",
")" : "-.--.-",
"'" : ".----.",
"\"": ".-..-.",
"-" : "-....-",
"/" : "-..-.",
"?" : "..--..",
"!" : "--..--",
"@" : ".--.-.",
"=" : "-...-",
}
inverse_morse_alphabet = {v: k for k, v in morse_alphabet.items()}
Method:
def__str__(self):return inverse_morse_alphabet[self.buffer]
# Если в словаре нет текущей последовательности,# то это KeyError. Ну и отлично.
Next, binary addition and subtraction. They are left-associative in Python, that is, they will be executed from left to right. Let's start with a simple one:
def__add__(self, other):return str(self) + str(+other)
# Обратите внимание на унарный + перед other.
So, after adding the first two sequences, we get a line. Will it be able to stack up with an object of the type following it
Morse
? No, addition with this type is str.__add__
not provided. Therefore, Python will try to call a method on the right object __radd__
. We realize it:def__radd__(self, s):return s + str(+self)
It remains to do the same for subtraction:
def__sub__(self, other):return str(self) + str(-other)
def__rsub__(self, s):return s + str(-self)
Whole class together
classMorse(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)
Let's write a simple function that will convert us strings into Python code:
defmorsify(s):
s = "_".join(map(morse_alphabet.get, s.upper()))
s = s.replace(".", "+") + ("_"if s else"")
return s
Now we can hammer all this beauty into the console and see that the code works:
>>> morsify("ПРИВЕТ,ХАБР!")
'+--+_+-+_++_+--_+_-_+-+-+-_++++_+-_-+++_+-+_--++--_'>>> _ = Morse()
>>> +--+_+-+_++_+--_+_-_+-+-+-_++++_+-_-+++_+-+_--++--_
'ПРИВЕТ,ХАБР!'
Add space support
Let's make an object that behaves like
Morse
just add a space at the end.classMorseWithSpace(Morse):def__str__(self):return super().__str__() + " "
___ = MorseWithSpace()
Simply? Yes! Works? No :-(
So that in the process, objects of type are
MorseWithSpace
not replaced by objects of type Morse
, you must also change __pos__
and __neg__
:def__neg__(self):return MorseWithSpace(super().__neg__().buffer)
def__pos__(self):return MorseWithSpace(super().__pos__().buffer)
It is also worth adding an entry
" " : " "
to the Morse code dictionary and changing the morsify function a little bit:defmorsify(s):
s = "_".join(map(morse_alphabet.get, s.upper()))
s = s.replace(".", "+") + ("_"if s else"")
s = s.replace("_ ", "__").replace(" _", "__")
return s
Works!
>>> morsify("ПРИВЕТ, ХАБР!")
'+--+_+-+_++_+--_+_-_+-+-+-___++++_+-_-+++_+-+_--++--_'>>> ___ = MorseWithSpace()
>>> +--+_+-+_++_+--_+_-_+-+-+-___++++_+-_-+++_+-+_--++--_
'ПРИВЕТ, ХАБР!'
All code in Gist .
Conclusion
Overriding operators can take you far and long.
Do not abuse it!