I am creating a simple program with a GUI to encrypt/decrypt text using different ciphers. I tried to make it so that it would be easy to add new ciphers.
This is my code:
import tkinter as tk
ALPHABET = "abcdefghijklmnopqrstuvwxyz"
class Cipher:
# encode_fun and decode_fun should take in args (key, message).
def __init__(self, encode_fun, decode_fun):
self.encode_fun = encode_fun
self.decode_fun = decode_fun
self.output = None
def encode(self, key, message):
try:
self.output.delete(0, "end")
self.output.insert(0, self.encode_fun(key, message))
except:
return
def decode(self, key, message):
try:
self.output.delete(0, "end")
self.output.insert(0, self.decode_fun(key, message))
except:
return
def display(self, master):
frame = tk.Frame(master)
frame.pack()
encode_button = tk.Button(frame, text="Encode")
encode_button.pack(side="left")
decode_button = tk.Button(frame, text="Decode")
decode_button.pack(side="right")
tk.Label(frame, text="Key:").pack()
key_text_box = tk.Entry(frame)
key_text_box.pack()
tk.Label(frame, text="Text:").pack()
message_text_box = tk.Entry(frame)
message_text_box.pack()
self.output = tk.Entry(frame, background="gray", foreground="white")
self.output.pack()
encode_button.config(command=
lambda: self.encode(key_text_box.get(), message_text_box.get()))
decode_button.config(command=
lambda: self.decode(key_text_box.get(), message_text_box.get()))
def caesar_shift_encode(key, message):
encoded_message = ""
for char in message.lower():
try:
encoded_message += ALPHABET[(ALPHABET.index(char) + int(key)) % len(ALPHABET)]
except:
encoded_message += char
return encoded_message
def caesar_shift_decode(key, message):
return caesar_shift_encode(str(-int(key)), message)
def xor_cipher_encode(key, message):
return "".join(
[ALPHABET[ALPHABET.index(key_char) ^ ALPHABET.index(message_char)]
for key_char, message_char in zip(key, message)]
)
root = tk.Tk()
caesar_shift = Cipher(caesar_shift_encode, caesar_shift_decode)
caesar_shift.display(root)
xor_cipher = Cipher(xor_cipher_encode, xor_cipher_encode)
xor_cipher.display(root)
tk.mainloop()
I would be grateful to hear about any improvements that could be made to this code.
1 Answer 1
ALPHABET = "abcdefghijklmnopqrstuvwxyz"
We don't like from string import ascii_lowercase ? Ok, whatever.
self.output = None
def encode(self, key, message):
try:
self.output.delete(0, "end")
I don't understand what's going on here.
Did we want to initialize output to the [] empty list, instead?
self.output.insert(0, self.encode_fun(key, message))
Note that inserting at front is O(N), while appending at end is O(1). Consider using a deque.
[EDIT: Later we see self.output = tk.Entry(frame, ....
Maybe the identifier output is on the vague side and
could be renamed to suggest that it's a display window?
Or maybe just a # commment here would help to
better communicate the intent.]
except:
return
No.
This should apparently mention pass instead of return,
though there's no difference in behavior ATM.
A pass emphasizes to the Gentle Reader that
we're evaluating this method for side effects.
If we did want return for some reason,
consider making it an explicit return None,
since that is what happens. Prefer pass,
consistent with the signature's (lack of) type hints.
Avoid "bare except".
Prefer except Exception:,
so as not to interfere with KeyboardException.
Better yet, use except AttributeError: if
that's what you're expecting,
and fix the code defect which triggers "'NoneType' object has no attribute 'delete'"
if that's what motivated the try.
Kudos on keeping the Tk graphic calls confined to .display().
For one thing, this makes it easy to add
unit tests
later on for encode / decode.
I also appreciate how everything is a local variable
rather than defining self.this, self.that, and self.the_other.
It reduces coupling, and means the Reader has fewer
things to worry about when reading other bits of code.
Please add these type hints:
def caesar_shift_encode(key: str, message: str) -> str:
I initially expected key: int, based on the
usual definition of a Caesar cipher.
except:
encoded_message += char
No.
Here, at least, it is apparent that Author's Intent was
to cope with "ValueError: substring not found".
So say that explicitly.
In any event, don't use a bare except.
def xor_cipher_encode(key, message):
This is an odd signature.
It suggests parallel structure with the Caesar signature
that accepted a single integer f"{key}".
But it's not that.
In a crypto context we might expect 128 bits or 512 bits of high-entropy keying material. But it's not that.
It turns out the "key" is a "one-time pad". Ok. I'm just surprised is all.
Validate that len(otp) >= len(message), so caller won't be surprised by silent truncation.
root = tk.Tk() ...
Please protect this with a guard:
if __name__ == "__main__":
root = tk.Tk() ...
Why?
At some point you or a collaborator will want to
write a unit test,
which should be able to import this
without side effects.
This program accomplishes its design goals, with fairly clear and mostly bug free code.
I would be happy to delegate or accept maintenance tasks on this codebase.
-
1\$\begingroup\$
self.outputis an instance oftk.Entry. (I need to access it to display the encrypted/decrypted message. Andself.output.delete(0, "end")clears the entry so thatself.output.insert(0, ...)can insert the encrypted/decrypted text in (to replace the previous text). \$\endgroup\$sbottingota– sbottingota2023年04月11日 05:34:43 +00:00Commented Apr 11, 2023 at 5:34
You must log in to answer this question.
Explore related questions
See similar questions with these tags.