Python 3 Vigenere Cipher

Nov 02 2020

Saya masih cukup baru di Python dan saya mencoba untuk melihat apakah saya telah menggunakan modul, fungsi, dll secara efisien atau apakah ada cara lain / lebih mudah untuk melakukan sesuatu.

Python 3 Vigenere Cipher ini adalah pembuatan ulang cipher berbasis JavaScript dan berbasis di Windows. Ia menerima karakter alfabet apa pun, dan memiliki opsi demo built in. Demo sandi menggunakan tahap 1 dari sandi 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()

Jawaban

7 hjpotter92 Nov 02 2020 at 17:35

peristiwa

Shebang harus generik. Anda saat ini menelepon python, yang mungkin mengarah ke python 2 pada sistem tertentu.

Python shebang yang ramah lingkungan dan generik adalah:

#!/usr/bin/env python3

PEP-8

Beberapa poin dari panduan PEP-8 :

  • Kelilingi fungsi tingkat atas dan definisi kelas dengan dua baris kosong.
  • Gunakan baris kosong dalam fungsi, sesekali, untuk menunjukkan bagian logis.
  • Konstanta biasanya ditentukan pada tingkat modul dan ditulis dengan huruf kapital semua dengan garis bawah yang memisahkan kata.

PEP-484

Fungsi petunjuk jenis memudahkan untuk mengikuti fungsi Anda. Lihat PEP-484 .

if __name__ blok

Letakkan logika eksekusi skrip Anda di dalam if __name__ == "__main__"blok. Penjelasan yang lebih deskriptif dapat diperiksa di Stack Overflow .

Logika yang berlebihan

Dalam kode Anda, Anda memiliki 5 fungsi berbeda, hanya untuk membaca input pengguna. Semuanya memiliki tugas yang sama:

  1. loop tak terbatas
  2. meminta masukan pengguna
  3. ubah menjadi huruf besar
  4. memvalidasi jika input kosong (atau dalam satu set nilai yang valid)
  5. dalam kasus nilai kosong, kembalikan default
  6. kembali dengan nilainya

Semua ini dapat ditangani dengan satu fungsi:

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 / validasi

Regex untuk memvalidasi input memungkinkan _, sedangkan pesan kesalahan secara eksplisit mengatakan tidak ada karakter khusus. Pemeriksaan dalam penulisan ulang di atas dilakukan menggunakan (diperbarui berdasarkan komentar di bawah):

value.isalpha() and value.isascii()

yang akan bekerja lebih cepat daripada regex (kecuali pengguna terus memasukkan nilai yang salah \$ 10^ n \$kali, di mana pola yang telah dikompilasi mungkin berkinerja sedikit lebih baik).

Performa

Beberapa hal yang dapat diubah untuk membuat kode lebih berkinerja:

  1. Alih-alih menggabungkan (menambahkan) ke string mode_string, dorong ke daftar dan pada akhirnya, gunakan "".join(). Detail selengkapnya tentang Stack Overflow .

  2. Anda juga dapat membuat program Anda mendukung sistem linux (* nix). Satu-satunya ketergantungan pada windows adalah panggilan sistem Anda ke cls. Mungkin ( diambil dari Stack Overflow ):

    def clear():
        os.system("cls" if os.name == "nt" else "clear")
    
  3. Ada 2 fungsi dengan nama yang sangat mirip: start_cipher(mode...)dan start_cipher_mode(mode). Ini membuatnya sangat sulit untuk mengetahui mana yang benar-benar memulai sandi . Mungkin, memiliki 2 fungsi terpisah encryptdan decrypt?

  4. Menggunakan operasi modulo, Anda dapat menghapus kondisional berikut:

    if cipher_alphabet_index == len(cipher_alphabets):
        cipher_alphabet_index = 0
    

    dan akan terlihat seperti:

    result.append(alphabet[cipher_alphabets[cipher_alphabet_index % alphabets_length].find(char)]
    
  5. Karena Anda hanya menggunakan alphabetstring untuk benar-benar bekerja dengan nilai indeks karakter di dalamnya, buatlah kamus. Pencarian dalam kamus adalah \$ O(1) \$dibandingkan dengan \$ O(n) \$untuk .find(). Ini akan menjadi:

    from itertools import count
    alphabet_map = dict(zip(alphabet, count()))
    
  6. Dari 2 poin di atas, jelas bahwa Anda tidak terlalu membutuhkan karakter / huruf setelah pengguna memasukkan. Hanya modulo nilai indeks yang penting. Ini mungkin sulit untuk dipahami / diterapkan tanpa pemahaman matematis yang memadai, jadi Anda dapat melewati ini untuk saat ini.

1 Oddthinking Nov 03 2020 at 00:52

@hjpotter menutupi sebagian besar komentar saya.

Boolean

Python memiliki konsep truthy dan falsy nilai-nilai, jadi sebaiknya hanya untuk nilai-nilai memperlakukan sebagai boolean langsung, daripada membandingkannya dengan Benar atau Salah.


return True if re.match("^[a-zA-Z_]*$", input_string) else False

dapat disederhanakan menjadi:

return re.match("^[a-zA-Z_]*$", input_string)

    elif string_is_alpha(alphabet) is False:

Ini dapat disederhanakan menjadi:

    elif not string_is_alpha(alphabet):

Jika umum, Anda jarang ingin menggunakan "adalah" untuk perbandingan. (Pengecualian utama adalah membandingkan None.)

Mengompilasi ekspresi reguler

Ini hampir pasti merupakan peningkatan kinerja yang tidak perlu, tetapi mungkin berguna untuk diketahui nanti:

Panggilan ke re.matchperlu mengkompilasi regexp setiap kali dipanggil. Anda dapat matchmengkompilasi regexp sekali, lalu memanggil objek yang dikompilasi untuk mempercepatnya.

Globals

Hampir setiap kali saya meraih globalkata kunci tersebut, ternyata itu adalah kesalahan.

Saya tidak berpikir Anda perlu menyatakan pengenal demo sebagai global; mereka seharusnya sudah tersedia untuk digunakan (hanya untuk membaca - jika Anda mencoba untuk menulis kepada mereka, Anda akan mendefinisikan variabel baru dalam lingkup baru, menyembunyikan aslinya).