Python 3 Vigenere Cipher

Nov 02 2020

ฉันยังค่อนข้างใหม่สำหรับ Python และฉันกำลังพยายามดูว่าฉันใช้โมดูลฟังก์ชัน ฯลฯ อย่างมีประสิทธิภาพหรือไม่หรือมีวิธีอื่นที่ง่ายกว่าในการทำอะไรบางอย่าง

Python 3 Vigenere Cipher นี้สร้างขึ้นใหม่ของการเข้ารหัสที่ใช้ JavaScript และใช้ใน Windows ยอมรับอักขระตัวอักษรใด ๆ และมีตัวเลือกการสาธิตในตัวการสาธิตการเข้ารหัสใช้ขั้นตอนที่ 1 ของการเข้ารหัส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()

คำตอบ

7 hjpotter92 Nov 02 2020 at 17:35

Shebang

Shebang ควรเป็นแบบทั่วไป คุณกำลังโทรอยู่pythonซึ่งอาจชี้ไปที่ python 2 ในบางระบบ

Python Shebang ทั่วไปที่เป็นมิตรกับสิ่งแวดล้อมเสมือนคือ:

#!/usr/bin/env python3

PEP-8

ไม่กี่คะแนนจากคู่มือ PEP-8 :

  • ฟังก์ชันระดับบนสุดรอบทิศทางและคำจำกัดความของคลาสด้วยบรรทัดว่างสองบรรทัด
  • ใช้บรรทัดว่างในฟังก์ชันเท่าที่จำเป็นเพื่อระบุส่วนตรรกะ
  • โดยปกติค่าคงที่จะถูกกำหนดในระดับโมดูลและเขียนด้วยตัวพิมพ์ใหญ่ทั้งหมดโดยมีเครื่องหมายขีดล่างคั่นคำ

PEP-484

ฟังก์ชันคำใบ้ประเภทช่วยให้ติดตามฟังก์ชั่นของคุณได้ง่ายขึ้น ตรวจสอบห้าวหาญ-484

if __name__ บล็อก

ใส่ตรรกะการดำเนินการของสคริปต์ของคุณภายในif __name__ == "__main__"บล็อก คำอธิบายอธิบายเพิ่มเติมสามารถตรวจสอบได้ที่กองมากเกิน

ตรรกะซ้ำซ้อน

ในโค้ดของคุณคุณมีฟังก์ชันที่แตกต่างกัน 5 ฟังก์ชันเพื่ออ่านข้อมูลของผู้ใช้เท่านั้น พวกเขาทั้งหมดมีงานเดียวกันคือ:

  1. วนไม่มีสิ้นสุด
  2. ขอข้อมูลผู้ใช้
  3. แปลงเป็นตัวพิมพ์ใหญ่
  4. ตรวจสอบว่าอินพุตว่างหรือไม่ (หรือในชุดของค่าที่ถูกต้อง)
  5. ในกรณีของค่าว่างให้ส่งคืนค่าเริ่มต้น
  6. ส่งคืนด้วยค่า

ทั้งหมดนี้สามารถจัดการได้โดยฟังก์ชั่นเดียว:

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 / การตรวจสอบความถูกต้อง

regex สำหรับการตรวจสอบความถูกต้องอนุญาตให้ใช้ได้_ในขณะที่ข้อความแสดงข้อผิดพลาดระบุว่าไม่มีอักขระพิเศษ การตรวจสอบในการเขียนซ้ำด้านบนทำได้โดยใช้ (อัปเดตตามความคิดเห็นด้านล่าง):

value.isalpha() and value.isascii()

ซึ่งจะดำเนินการได้เร็วกว่า regex (ยกเว้นกรณีที่ผู้ใช้จะช่วยให้การป้อนค่าที่ไม่ถูกต้อง\$ 10^ n \$บางครั้งซึ่งรูปแบบที่คอมไพล์ไว้แล้วอาจทำงานได้ดีกว่าเล็กน้อย)

ประสิทธิภาพ

บางสิ่งที่สามารถเปลี่ยนแปลงได้เพื่อให้โค้ดมีประสิทธิภาพมากขึ้น:

  1. แทนการเชื่อมโยง (ท้าย) เพื่อสตริงผลักดันไปยังรายชื่อและที่ปลายใช้mode_string "".join()รายละเอียดเพิ่มเติมได้ที่กองมากเกิน

  2. คุณสามารถทำให้โปรแกรมของคุณรองรับระบบ linux (* nix) ได้เช่นกัน clsพึ่งพาเฉพาะในหน้าต่างเป็นสายระบบของคุณ บางที ( นำมาจาก Stack Overflow ):

    def clear():
        os.system("cls" if os.name == "nt" else "clear")
    
  3. มี 2 ​​ฟังก์ชันที่มีชื่อคล้ายกันมาก: start_cipher(mode...)และstart_cipher_mode(mode). ซึ่งทำให้มันเป็นจริงยากที่จะรู้ว่าที่หนึ่งอย่างแท้จริงเริ่มต้นเลขศูนย์ บางทีอาจมี 2 ฟังก์ชั่นที่แยกจากกันencryptและdecrypt?

  4. เมื่อใช้การทำงานแบบโมดูโลคุณสามารถลบเงื่อนไขต่อไปนี้:

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

    และจะมีลักษณะดังนี้:

    result.append(alphabet[cipher_alphabets[cipher_alphabet_index % alphabets_length].find(char)]
    
  5. เนื่องจากคุณใช้เฉพาะalphabetสตริงเพื่อทำงานกับค่าดัชนีของอักขระในนั้นให้สร้างพจนานุกรม การค้นหาในพจนานุกรมคือ\$ O(1) \$เทียบกับ\$ O(n) \$สำหรับ.find(). นี่จะเป็น:

    from itertools import count
    alphabet_map = dict(zip(alphabet, count()))
    
  6. จาก 2 จุดข้างต้นเป็นที่ชัดเจนว่าคุณไม่จำเป็นต้องใช้อักขระ / ตัวอักษรหลังจากป้อนข้อมูลโดยผู้ใช้ เฉพาะค่าดัชนีโมดูโลเท่านั้นที่มีความสำคัญ สิ่งนี้อาจเป็นเรื่องยากที่จะเข้าใจ / นำไปใช้โดยไม่ต้องมีความเข้าใจทางคณิตศาสตร์มากนักดังนั้นคุณสามารถข้ามสิ่งนี้ไปได้ในตอนนี้

1 Oddthinking Nov 03 2020 at 00:52

@hjpotter ครอบคลุมความคิดเห็นส่วนใหญ่ของฉัน

บูลีน

Python มีแนวคิดเกี่ยวกับค่าที่แท้จริงและเป็นเท็จดังนั้นจึงเป็นที่นิยมในการปฏิบัติต่อค่าเป็นบูลีนโดยตรงแทนที่จะเปรียบเทียบกับค่าจริงหรือเท็จ


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

สามารถทำให้ง่ายขึ้นเพื่อ:

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

    elif string_is_alpha(alphabet) is False:

สิ่งนี้สามารถทำให้ง่ายขึ้นเพื่อ:

    elif not string_is_alpha(alphabet):

ถ้าทั่วไปคุณแทบไม่ต้องการใช้ "is" เพื่อเปรียบเทียบ (ข้อยกเว้นหลักกำลังเปรียบเทียบกับNone)

การรวบรวมนิพจน์ทั่วไป

นี่เป็นการปรับปรุงประสิทธิภาพที่ไม่จำเป็น แต่อาจมีประโยชน์ที่จะทราบในภายหลัง:

การเรียกไปที่re.matchจำเป็นต้องรวบรวม regexp ทุกครั้งที่เรียก คุณสามารถmatchคอมไพล์regexp ล่วงหน้าได้หนึ่งครั้งจากนั้นเรียกใช้วัตถุที่คอมไพล์แล้วเพื่อเร่งความเร็ว

ลูกโลก

เกือบทุกครั้งที่ฉันไปถึงglobalคีย์เวิร์ดนั้นมันกลายเป็นข้อผิดพลาด

ฉันไม่คิดว่าคุณจำเป็นต้องประกาศตัวระบุการสาธิตเป็นส่วนกลาง ควรจะพร้อมใช้งานอยู่แล้ว (เพื่ออ่านจากเท่านั้น - หากคุณพยายามเขียนถึงพวกเขาคุณจะกำหนดตัวแปรใหม่ในขอบเขตใหม่โดยซ่อนต้นฉบับ)