โปรแกรมตอบคำถามประวัติ Python
โปรแกรมนี้ช่วยให้ผู้ใช้จัดการคำถามและตอบคำถามผู้ใช้จากเหตุการณ์ในประวัติศาสตร์หลายปี ฉันกำลังมองหาคำแนะนำเกี่ยวกับวิธีการที่ฉันสามารถที่ดีกว่าการจัดระเบียบรหัสของฉันอาจจะใช้ OOP data_filenameสำหรับฟังก์ชั่นและหลีกเลี่ยงการใช้ตัวแปรทั่วโลก
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from enum import Enum
import os
import pickle
import random
data_filename = 'program.obj'
def read_int(prompt='> ', errmsg='Invalid number!'):
number = None
while number is None:
try:
number = int(input(prompt))
except ValueError:
print(errmsg)
return number
def display_menu():
print('What do you want to do?')
print('[1] List all historical events')
print('[2] Add event')
print('[3] Remove event')
print('[4] Quiz')
print('[5] Statistics')
print('[6] Clear statistics') # TODO: move to options submenu
print('[7] Exit')
def clear_screen():
print(chr(27) + "[2J")
def pause():
input('Press any key to continue...')
def yes_or_no(prompt='Proceed? [y|n]\n> ', errmsg='Valid answers are y and n.'):
answer = input(prompt).strip().lower()
while answer != 'y' and answer != 'n':
print(errmsg)
answer = input(prompt).strip().lower()
return answer
def read_data_file():
file = open(data_filename, 'a+b')
file.seek(0)
data = {'events': [], 'statistics': {'total_successes': 0, 'total_failures': 0}}
if os.path.getsize(data_filename) > 0:
data = pickle.load(file)
file.close()
return data
def list_events(data):
events = data['events']
if len(events) == 0:
print('Event list is empty!')
else:
print('Historical events:')
for event in events:
print(event)
def add_event(data):
events = data['events']
year = read_int(prompt='Enter year: ')
description = input('Enter description: ')
events.append({'year': year, 'description': description})
print('Successfully added a new historical event!')
def remove_event(data):
events = data['events']
if len(events) == 0:
print('Event list is empty!')
else:
for event in events:
print(events.index(event), event)
index = read_int('Which event do you want to delete? ')
try:
events.pop(index)
print('Successfully deleted event!')
except IndexError:
print('Number out of range!')
def quiz(data):
events = data['events']
stats = data['statistics']
if len(events) == 0:
print('Event list is empty!')
else:
num = read_int(prompt='How many questions should I ask? ')
if 0 < num <= len(events):
for event in random.sample(events, num):
print(event['description'])
year = read_int('In which year was following event occurred? ')
if year == event['year']:
stats['total_successes'] += 1
print('Good answer!')
else:
stats['total_failures'] += 1
print('Bad answer!')
elif num < 0:
print('Number of questions can\'t be negative!')
else:
print('Too much questions!')
def display_stats(data):
stats = data['statistics']
tries = stats['total_successes'] + stats['total_failures']
if tries == 0:
total_successes = 0
total_failures = 0
else:
total_successes = stats['total_successes']/tries * 100
total_failures = stats['total_failures']/tries * 100
print('Statistics')
print('Total: {0:10.2f}% successes, {1:10.2f}% failures'.format(total_successes, total_failures))
def clear_stats(data):
answer = yes_or_no('Are you sure you want to clear statistics? [y|n]\n> ')
if answer == 'y':
data['statistics'] = {'total_successes': 0, 'total_failures': 0}
print('Successfully cleared statistics!')
else:
print('Statistics left unchanged.')
# TODO: settings submenu
# def settings(data):
# print('-' * 10)
# print('Program settings')
# print('-' * 10)
# print('[1] Clear statistics')
# print('[2] Back')
# user_choice = read_int()
# while user_choice != 2:
# if user_choice == 1:
# clear_stats(data)
def update_data_file(data):
file = open(data_filename, 'wb')
pickle.dump(data, file)
file.close()
class Choices(Enum):
list_events = 1
add_event = 2
remove_event = 3
quiz = 4
statistics = 5
clear_stats = 6
exit = 7
program_data = read_data_file()
choice = None
while choice != Choices.exit.value:
clear_screen()
display_menu()
choice = read_int()
if choice == Choices.list_events.value:
list_events(program_data)
elif choice == Choices.add_event.value:
add_event(program_data)
elif choice == Choices.remove_event.value:
remove_event(program_data)
elif choice == Choices.quiz.value:
quiz(program_data)
elif choice == Choices.statistics.value:
display_stats(program_data)
elif choice == Choices.clear_stats.value:
clear_stats(program_data)
elif choice == Choices.exit.value:
print('Good bye!')
else:
print('Invalid choice!')
update_data_file(program_data)
pause()
คำตอบ
ค่าคงที่
data_filename ควรเป็นตัวพิมพ์ใหญ่เนื่องจากเป็นค่าคงที่ทั่วโลก
ผลตอบแทนก่อนกำหนด
ไม่มีความจำเป็นที่จะใช้เป็นnumberเป็นเงื่อนไขในของคุณwhile; แทน:
number = None
while number is None:
try:
number = int(input(prompt))
except ValueError:
print(errmsg)
return number
เป็นไปได้
while True:
try:
return int(input(prompt))
except ValueError:
print(errmsg)
การจัดการเมนู
display_menuสามารถใช้ลำดับของทูเปิลได้ดีกว่า แต่ลำดับของทูเปิลที่มีชื่อหรือ@dataclasses แต่ละตัวมีแอตทริบิวต์สตริงหัวเรื่องและแอตทริบิวต์ที่เรียกได้ แล้วคุณdisplay_menuอาจจะ
print('What do you want to do?')
print('\n'.join(f'[{i}] {item.title}' for i, item in enumerate(menu, 1)))
ฉันเห็นว่าคุณมีChoicesenum ด้วย นั่นไม่ใช่เรื่องแย่และคุณสามารถใช้ทั้ง enum และคำแนะนำข้างต้นได้ตราบใดที่คุณมีพจนานุกรมการอ้างอิง enum-choice-to-function-references
การทดสอบการเป็นสมาชิก
answer != 'y' and answer != 'n'
เป็นไปได้
answer not in {'y', 'n'}
โครงสร้างห่วง
หลีกเลี่ยงการโทรinputซ้ำสองครั้ง นี้:
answer = input(prompt).strip().lower()
while answer != 'y' and answer != 'n':
print(errmsg)
answer = input(prompt).strip().lower()
return answer
เป็นไปได้
while True:
answer = input(prompt).strip().lower()
if answer in {'y', 'n'}:
return answer
print(errmsg)
การทำงานของไฟล์
file = open(data_filename, 'a+b')
file.seek(0)
มีปัญหาเล็กน้อย:
- การจัดการไฟล์ที่เพิ่งเปิดใหม่ไม่จำเป็นต้องมีการค้นหาเพื่อเริ่มต้น ที่ซ้ำซ้อนหากคุณเปิดในโหมดอ่านไบนารี
- คุณควรใช้
withและหลีกเลี่ยงการโจ่งแจ้งclose() - คุณควรเริ่มต้นเฉพาะ
dataเมื่อจำเป็นเท่านั้น - คุณต้องการตรวจสอบว่ามีไฟล์อยู่หรือไม่ไม่ใช่ขนาดของไฟล์
ดังนั้น:
if os.path.exists(data_filename):
with open(data_filename, 'rb') as file:
return pickle.load(file)
return {'events': [], 'statistics': {'total_successes': 0, 'total_failures': 0}}
เพรดิเคตซ้ำซ้อน
if 0 < num <= len(events):
...
elif num < 0:
print('Number of questions can\'t be negative!')
else:
print('Too much questions!')
ต้องคิดใหม่ ครั้งแรกที่ควรจะเป็นtoo much questions too many questionsนอกจากนี้จะเกิดอะไรขึ้นถ้าผู้ใช้ป้อน 0? นั่นไม่ใช่ "คำถามมากเกินไป" แต่นั่นคือสิ่งที่จะพิมพ์ออกมา แนะนำ:
if num < 1:
print('Not enough questions.')
elif num > len(events):
print('Too many questions.')
else:
...
ข้อมูลที่มีโครงสร้างแข็งแรงขึ้น
stats['total_successes'] + stats['total_failures']
เป็นตัวอย่างของสิ่งที่ฉันเคยเห็นเรียกว่า "data pasta" พจนานุกรมกำลังถูกใช้ในทางที่ผิดที่นี่เมื่อ@dataclassคำแนะนำประเภทที่มีเนื้อหาเหมาะสมกว่า
การแก้ไขสตริง
'Total: {0:10.2f}% successes, {1:10.2f}% failures'.format(total_successes, total_failures)
แสดงได้ง่ายกว่าเป็น
(
f'Total: {total_successes:10.2f}% successes, '
f'{total_failures:10.2f}% failures'
)
การแบ่งบรรทัดเป็นทางเลือก แต่ดีกว่าเพื่อความชัดเจน
สตริงหนี
'Number of questions can\'t be negative!'
เขียนได้ง่ายกว่าด้วย
"Number of questions can't be negative!"