Python 3 Vigenere Cipher
Wciąż jestem całkiem nowy w Pythonie i próbuję sprawdzić, czy efektywnie korzystałem z modułów, funkcji itp. Lub czy istnieje inny / łatwiejszy sposób na zrobienie czegoś.
Ten szyfr Python 3 Vigenere Cipher to przebudowa szyfru opartego na JavaScript i oparty na systemie Windows. Akceptuje dowolny znak alfabetu i ma wbudowane opcje demo. Demo szyfru wykorzystuje etap 1 szyfru CIA Kryptos .
#! python
import os
import re
## Initialize global variables
continue_cipher = ""
demo_alphabet = "KRYPTOSABCDEFGHIJLMNQUVWXZ"
demo_key = "PALIMPSEST"
demo_cipher_string = "EMUFPHZLRFAXYUSDJKZLDKRNSHGNFIVJYQTQUXQBQVYUVLLTREVJYQTMKYRDMFD"
demo_cipher_decoded = "BETWEENSUBTLESHADINGANDTHEABSENCEOFLIGHTLIESTHENUANCEOFIQLUSION"
## Visuals
def display_header():
print("################################################")
print("# #")
print("# --- VIGENERE CIPHER --- #")
print("# #")
print("# A simple Vigenere cipher decoder/encoder #")
print("# #")
print("################################################", end="\n\n")
return
def display_results(mode, cipher_vars):
# Clear screen for final results
os.system('cls')
# Display header
display_header()
# Decompose cipher_vars
(alphabet, key, cipher_string, results) = cipher_vars
print("Mode:", "Decrypt" if mode == "D" else "Encrypt", end="\n\n")
print("Alphabet:", alphabet)
print("Key:", key)
print("Cipher String:", cipher_string, end="\n\n")
print("Decoded string:" if mode == "D" else "Encoded string:", results, end="\n\n")
return
## Validations
def string_is_alpha(input_string):
return True if re.match("^[a-zA-Z_]*$", input_string) else False
## Cipher variables
def get_alphabet():
global demo_alphabet
while True:
alphabet = input("Enter cipher alphabet: ").upper()
if alphabet == "":
alphabet = demo_alphabet
break
elif string_is_alpha(alphabet) is False:
print("The alphabet is not valid. Alphabet should not contain spaces, digits or special characters.")
else:
break
return alphabet
def get_key():
global demo_key
while True:
key = input("Enter cipher key: ").upper()
if key == "":
key = demo_key
break
elif string_is_alpha(key) is False:
print("The key is not valid. Key should not contain spaces, digits or special characters.")
else:
break
return key
def get_cipher_string(mode):
global demo_cipher_string
global demo_cipher_decoded
while True:
cipher_string = input("Enter cipher string: ").upper()
if cipher_string == "":
cipher_string = demo_cipher_string if mode == "D" else demo_cipher_decoded
break
elif string_is_alpha(cipher_string) is False:
print("The cipher string is not valid. Cipher strings should not contain spaces, digits or special characters.")
else:
break
return cipher_string
## Cipher actions
def get_cipher_alphabets(alphabet, key):
cipher_alphabets = []
for char in key:
char_index = alphabet.find(char)
cipher_alphabet = alphabet[char_index:] + alphabet[:char_index]
cipher_alphabets.append(cipher_alphabet)
return cipher_alphabets
def start_cipher(mode, alphabet, key, cipher_string):
mode_string = ""
cipher_alphabets = get_cipher_alphabets(alphabet, key)
cipher_alphabet_index = 0
for char in cipher_string:
# Reset cipher_alphabet_index to 0 when at end of cipher alphabets
if cipher_alphabet_index == len(cipher_alphabets):
cipher_alphabet_index = 0
# Use appropriate alphabet based on mode
# Syntax: base_alphabet[mode_alphabet.find(char)]
if mode == "D":
mode_string += alphabet[cipher_alphabets[cipher_alphabet_index].find(char)]
else:
mode_string += cipher_alphabets[cipher_alphabet_index][alphabet.find(char)]
cipher_alphabet_index += 1
return mode_string
## Cipher Mode
def get_cipher_mode():
while True:
cipher_mode = input("Choose cipher mode - [D]ecrypt or [E]ncrypt: ").upper()
if cipher_mode != "D" and cipher_mode != "E":
print("That is not a valid option. Please enter 'D' for decrypt and 'E' for encrypt.")
else:
break
print("")
return cipher_mode
def start_cipher_mode(mode):
print("Press 'enter' to use demo options")
alphabet = get_alphabet()
key = get_key()
cipher_string = get_cipher_string(mode)
mode_string = start_cipher(mode, alphabet, key, cipher_string)
return alphabet, key, cipher_string, mode_string
## Loop cipher
def get_continue_cipher():
while True:
continue_cipher = input("Do you want to decode/encode more? [Y/N]: ").upper()
if continue_cipher != "Y" and continue_cipher != "N":
print("That is not a valid option. Please enter 'Y' to continue and 'N' to quit.")
else:
break
return continue_cipher
## Start vigenere cipher program
while continue_cipher != "N":
# Clear the screen after each operation
os.system('cls')
# Display header
display_header()
# Determine cipher mode
cipher_mode = get_cipher_mode()
cipher_vars = start_cipher_mode(cipher_mode)
# Display results
display_results(cipher_mode, cipher_vars)
continue_cipher = get_continue_cipher()
Odpowiedzi
szulernia
Shebang powinien być ogólny. Obecnie dzwonisz python, co może wskazywać na język Python 2 w niektórych systemach.
Ogólny, przyjazny dla środowiska wirtualny shebang w języku Python to:
#!/usr/bin/env python3
PEP-8
Kilka punktów z poradnika PEP-8 :
- Otocz definicje funkcji najwyższego poziomu i klas dwoma pustymi wierszami.
- Używaj oszczędnie pustych linii w funkcjach, aby wskazać sekcje logiczne.
- Stałe są zwykle definiowane na poziomie modułu i zapisywane dużymi literami z podkreśleniami oddzielającymi słowa.
PEP-484
Funkcje podpowiedzi do typów ułatwiają wykonywanie funkcji. Sprawdź PEP-484 .
if __name__ blok
Umieść logikę wykonywania skryptu wewnątrz if __name__ == "__main__"bloku. Bardziej opisowe wyjaśnienie można sprawdzić na stronie Stack Overflow .
Logika nadmiarowa
W swoim kodzie masz 5 różnych funkcji, tylko do odczytu danych wejściowych użytkownika. Wszyscy mają tę samą pracę:
- nieskończona pętla
- poproś o wprowadzenie danych przez użytkownika
- przekonwertować na wielkie litery
- Sprawdź, czy dane wejściowe są puste (lub w zestawie prawidłowych wartości)
- w przypadku pustej wartości, zwraca wartość domyślną
- powrót z wartością
Wszystko to mogłaby obsłużyć jedna funkcja:
def ask_user_input(message: str, options: List[str] = None, default: str = None, check_alpha: bool = False) -> str:
if not any([options, default]):
raise ValueError("Either a set of `options` for validation or a fallback `default` needed.")
while True:
value = input(message).upper()
if options:
if value in options:
break
else:
print(f"Invalid value. Select one of {', '.join(options)}")
continue
if default is not None:
if not value:
value = default
break
elif not check_alpha:
break
elif not (value.isalpha() and value.isascii()):
print("The input text should only consist of ascii alphabets.")
continue
else:
break
return value
Regex / validation
Wyrażenie regularne do walidacji danych wejściowych pozwala na _, podczas gdy komunikat o błędzie wyraźnie nie mówi o żadnych znakach specjalnych. Sprawdzanie powyższego przepisania odbywa się za pomocą (zaktualizowane na podstawie poniższego komentarza):
value.isalpha() and value.isascii()
który będzie działał szybciej niż regex (chyba że użytkownik będzie ciągle wprowadzał błędne wartości \$ 10^ n \$razy, w których prekompilowany wzorzec może działać nieco lepiej).
Występ
Kilka rzeczy, które można zmienić, aby kod był bardziej wydajny:
Zamiast łączyć (dołączać) do łańcucha
mode_string, wypchnij do listy i na końcu użyj"".join(). Więcej informacji na temat przepełnienia stosu .Możesz sprawić, by Twój program obsługiwał również systemy linux (* nix). Jedyną zależnością od systemu Windows jest wywołanie systemowe
cls. Być może ( wzięte z przepełnienia stosu ):def clear(): os.system("cls" if os.name == "nt" else "clear")Istnieją 2 funkcje o bardzo podobnych nazwach:
start_cipher(mode...)istart_cipher_mode(mode). To sprawia, że naprawdę trudno jest wiedzieć, który z nich naprawdę rozpoczyna szyfr . Być może mają 2 oddzielne funkcjeencryptidecrypt?Używając operacji modulo, możesz usunąć następujący warunek:
if cipher_alphabet_index == len(cipher_alphabets): cipher_alphabet_index = 0i wyglądałoby następująco:
result.append(alphabet[cipher_alphabets[cipher_alphabet_index % alphabets_length].find(char)]Ponieważ używasz ciągu tylko
alphabetdo faktycznej pracy z wartościami indeksu znaków w nim, utwórz słownik. Wyszukiwania w słowniku to \$ O(1) \$w porównaniu do \$ O(n) \$dla.find(). To byłoby:from itertools import count alphabet_map = dict(zip(alphabet, count()))Z powyższych 2 punktów jasno wynika, że tak naprawdę nie potrzebujesz znaków / alfabetów po wprowadzeniu danych przez użytkownika. Liczy się tylko wartość indeksu modulo. Może to być trudne do zrozumienia / wdrożenia bez znacznego zrozumienia matematycznego, więc na razie możesz to pominąć.
@hjpotter opisał większość moich komentarzy.
Booleans
Python ma koncepcję prawdziwych i fałszywych wartości, dlatego lepiej jest traktować wartości bezpośrednio jako wartości logiczne, zamiast porównywać je z wartością True lub False.
return True if re.match("^[a-zA-Z_]*$", input_string) else False
można uprościć do:
return re.match("^[a-zA-Z_]*$", input_string)
elif string_is_alpha(alphabet) is False:
Można to uprościć, aby:
elif not string_is_alpha(alphabet):
Jeśli chodzi o ogólne, rzadko chcesz używać „jest” dla porównania. (Głównym wyjątkiem jest porównanie None.)
Kompilowanie wyrażeń regularnych
Jest to prawie na pewno niepotrzebna poprawa wydajności, ale warto wiedzieć na później:
Wywołanie to re.matchmusi kompilować wyrażenie regularne za każdym razem, gdy jest wywoływane. Możesz wstępnie skompilować wyrażenie regularne raz, a następnie wywołać matchskompilowany obiekt, aby go przyspieszyć.
Globals
Niemal za każdym razem, gdy sięgam po globalsłowo kluczowe, okazuje się, że jest to błąd.
Myślę, że nie musisz deklarować identyfikatorów demo jako globalnych; powinny już być dostępne do użycia (tylko do odczytu - jeśli spróbujesz do nich napisać, zdefiniujesz nową zmienną w nowym zakresie, ukrywając oryginały).