Python geçmişi bilgi yarışması programı

Oct 17 2020

Bu program, kullanıcıların soruları yönetmelerine ve kullanıcıları yıllarca süren tarihi olaylardan test etmelerine olanak tanır. Kodumu nasıl daha iyi düzenleyebilirim, belki işlevler için OOP uygulayabilir ve global değişken kullanmaktan nasıl kaçınabilirim konusunda öneriler arıyorum 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()

Yanıtlar

3 Reinderien Oct 17 2020 at 20:21

Sabitler

data_filename küresel bir sabit olduğu için büyük harfle yazılmalıdır.

Erken dönüş

numberKoşul olarak kullanmaya gerek yok while; yerine:

number = None
while number is None:
    try:
        number = int(input(prompt))
    except ValueError:
        print(errmsg)
return number

olabilir

while True:
    try:
        return int(input(prompt))
    except ValueError:
        print(errmsg)

Menü yönetimi

display_menubir demetler dizisi, daha iyisi @dataclass, her biri bir başlık dizesi özniteliğine ve çağrılabilir bir özniteliğe sahip olan bir dizi adlandırılmış tuple veya es kullanabilir. O zaman display_menuolabilir

print('What do you want to do?')
print('\n'.join(f'[{i}] {item.title}' for i, item in enumerate(menu, 1)))

Görüyorum ki bir de sıralamanız var Choices. Bu fena değil ve bir enum-options-to-function-referansı sözlüğünüz olduğu sürece, hem bir enum hem de yukarıdaki öneriyi kullanabilirsiniz.

Üyelik testleri

answer != 'y' and answer != 'n'

olabilir

answer not in {'y', 'n'}

Döngü yapısı

inputİki kez aramak zorunda kalmayın; bu:

answer = input(prompt).strip().lower()
while answer != 'y' and answer != 'n':
    print(errmsg)
    answer = input(prompt).strip().lower()
return answer

olabilir

while True:
    answer = input(prompt).strip().lower()
    if answer in {'y', 'n'}:
        return answer
    print(errmsg)

Dosya işlemleri

file = open(data_filename, 'a+b')
file.seek(0)

birkaç sorunu var:

  • Yeni açılan dosya tanıtıcıları başlangıçta bir başlangıç ​​aramasına ihtiyaç duymaz; ikili okuma modunda açarsanız bu gereksizdir
  • withAçıkça kullanmalı ve bunlardan kaçınmalısınızclose()
  • Yalnızca varsayılan başlatmak için rahatsız etmelidir datagerektiğinde
  • Dosyanın boyutunu değil, var olup olmadığını kontrol etmek istiyorsunuz

Yani:

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}}

Gereksiz yüklemler

if 0 < num <= len(events):
    ...
elif num < 0:
    print('Number of questions can\'t be negative!')
else:
    print('Too much questions!')

yeniden düşünülmesi gerekiyor. İlk too much questionsönce olmalı too many questions. Ayrıca, kullanıcı 0 girerse ne olur? Elbette bu "çok fazla soru" değil, ama basılacak olan bu. Önerildi:

if num < 1:
    print('Not enough questions.')
elif num > len(events):
    print('Too many questions.')
else:
    ...

Daha güçlü türde, daha güçlü yapılandırılmış veriler

stats['total_successes'] + stats['total_failures']

"veri makarna" olarak adlandırdığım şeye bir örnek. Burada, @dataclassyazı tipi ipuçlarını içeren bir şey daha uygun olduğunda sözlükler kötüye kullanılmaktadır .

Dize enterpolasyonu

'Total: {0:10.2f}% successes, {1:10.2f}% failures'.format(total_successes, total_failures)

olarak daha kolay ifade edilir

(
    f'Total: {total_successes:10.2f}% successes, '
    f'{total_failures:10.2f}% failures'
)

Satır ayırma isteğe bağlıdır ancak okunaklılık açısından daha iyidir.

Dize kaçışları

'Number of questions can\'t be negative!'

olarak daha kolay yazılır

"Number of questions can't be negative!"