Найти - Пользователи
Полная версия: Оценить, понять, простить.
Начало » Python для новичков » Оценить, понять, простить.
1 2 3 4
Babay82
Дожив до четвертого десятка, решил заняться изучением программирования.
Цели устроится на работу в какой-нибудь IT гигант не стоит, т.к. реально понимаю , что в моем возрасте это вряд ли осуществимо в данной области, так скорее хобби(но очень интересное), но и говнокод писать не хочется. Если что-то делаешь, то делай правильно. В реальной жизни мне обратиться за советом не к кому. обращаюсь к вам.
В общем осваиваю Майкла Доусона “Программируем на питон”, дойдя и изучив главы Функции и Файлы ,для закрепления материала, собрал несколько ранее созданных учебных заданий и объединил их в одну программку, в которой пользователь с компьютером играет в игру “Угадай число”, а программа собирает результаты и выводит на экран.
В общем хотел попросить вас оценить получившийся продукт, указать на недостатки, недочеты. Вообще , правильно ли я усвоил понятия функций, все ли в порядке с логикой и т.д.
И ещё, это мой первый опыт в данной области вообще, так что, то что для ВАС тривиально, для меня тёмный лес)
Вот собственно, сам код.
 import random, pickle, shelve, sys
def acquaintance(question):
    """Знакомство с пользователем"""
    response = None
    while not response:
        response = input(question). title()
    return response
def hallo_unit(name):
    print("""\t\t\tДобро пожаловать в игру \"УГАДАЙ ЧИСЛО\",""", name,"""
                        Я буду загадывать число, а ты угадывать!
                Ты можешь задать диапазон чисел в которых будешь угадывать
                или оставить как есть, тогда диапазон будет от 1 до 100.
                            у тебя всего 10 попыток!
                            """)
def ask_yes_no(question):
    """Задает вопрос с ответом "ДА" или "НЕТ"."""
    response = None
    while response not in ("ДА", "НЕТ"):
       response  = input(question).upper()
    return response
def game_interval():
    """При желании пользователя меняет величину диапазона"""
    answer = ask_yes_no("\n\n\tВы желаете изменить диапазон чисел?")
    if answer == "НЕТ":        
        interval = 100        
    else:
        try:
            interval = int(input("\n\n\tВведите величину диапазона, в котором будете отгадывать."))
        except:
            print("\n\tВы ввели недопустимое значение, попробуем ещё разок!")
            interval = game_interval()
    return interval
def ask_number(question, interval):
    """Просит ввести число из диапазона"""
    response = None
    while response not in range(1,interval):
        try:
            response = int(input(question))
            if response not in range(1,interval):
                print("\n\tЭто число вне диапазона!")
        except:
            print("\n\tВы ввели недопустимое значение, попробуем ещё разок!")
    return response
def user_game(interval, name):
    """Пользователь отгадывает число"""
    computer_choise = random.randrange(interval) + 1
    score = 0
    response = None
    while response != computer_choise :
        response = ask_number("\n\n\tНазывай число в заданном диапазоне.", interval)
        if response > computer_choise :
            score += 1
            print("\n\tМеньше")
        elif response < computer_choise :
            score += 1
            print("\n\tБольше")
        elif response == computer_choise :
            score += 1
            print("\tМолодец, это действительно" , computer_choise)
            print("\tТы отгадал число всего за", score ,"шагов!")
        elif response <= 1 or response >= interval:
            print("Это число вне диапазона, пожалуйста введите другое!")
        if score == 10:
            print("\n\tНу ты , вообще, неудачник!")
            break
    records_list_4(score, interval, name)
     
def records_list_4(score, interval, name):
    """Добавляет рекорды в виде консервированных списков на полке"""
    records = shelve.open("records.dat")
    interval = str(interval)
    if interval in records:
        name_records = records[interval]
        name_record = (score, name)
        name_records.append(name_record)
        records[interval] = name_records        
    else:
        name_records = []
        name_record = (score, name)
        name_records.append(name_record)
        records[interval] = name_records        
    records.close()
    
def print_record_2():
    """Выводит на экран рекорды из файла"""
    print("\t\t\tСписок рекордов\n")
    print("""\t\tВ графе  СЛОЖНОСТЬ указан интервал в котором велся поиск,
            чем больше тем сложнее, в графе РЕКОРД указано количество
            шагов за которое игрок угадал число, чем меньше тем лучше результат
            \n\n""")
    print("\t\tСЛОЖНОСТЬ\t\t\tИМЯ\t\tРЕКОРД\n\n")
    records = shelve.open("records.dat")
    for i in records:
        lvl_records = records[i]
        name_records = []
        for j in lvl_records:
            score, name = j
            name_record = (score, name, i)
            name_records.append(name_record)
        name_records.sort()
        name_records = name_records[:3]
        for k in name_records:
            score, name, lvl, = k
            print("\t\t", lvl, "\t\t\t", name, "\t\t", score)
        print("\n\n")
def computer_game(interval, ):
    """Компьютер отгадывает число"""
    print("""\n\n\t\t А теперь моя очередь, я буду отгадывать,
            Пиши больше или меньше, когда угадаю пиши - правильно.
                  посмотрим кто быстрее?""")
    item = int(input("\n\tЗагадай число в заданном тобой диапазоне:"))
    name = "Компьютер"
    low = 1
    hight = interval
    score = 0
    user_response = ""
    response_copy = None
    while user_response != "Правильно":
        mid = (low + hight) // 2        
        response = range(interval + 1)[mid]        
        print("\n\t", response)
        user_response = input("").title()
        if user_response == "Правильно":
            score += 1
            print("\n\tЯ отгадал число всего за", score,"шагов!")
        elif user_response == "Больше":
            low = mid + 1
            score += 1
        elif user_response == "Меньше":
            hight = mid - 1
            score += 1
        else:
            print("\n\tПовторите пожалуйста - 'больше' или 'меньше'")
        if score == 10:
            print("\n\tВот я лошара!!!")
            break
        if response == response_copy:
            print("Вы либо забывчивый либо мошенник, я с такими не дружу! Прощайте!")
            sys.exit()
        response_copy = response
    records_list_4(score, interval, name)
                            
def main():
    end_game = None
    while end_game != "НЕТ":
        name = acquaintance("\tДавай знакомиться,меня зовут Компьютер, а тебя?:")
        hallo_unit(name)
        interval = game_interval()
        user_game(interval, name)        
        computer_game(interval)        
        print_record_2()
        end_game = ask_yes_no("""\n\n\t\t\tЖелаете продолжить или и отдать ход другому,
                                    если ни то ни другое введите \"нет\".""")
    input("Нажмите Enter чтобы выйти")
        
main()
        
        


py.user.next
Так, в целом алгоритм хороший, нет запутанностей, всё ясно происходит.

Тут плохая привычка экономии имён
Babay82
 interval = str(interval)
Babay82
 name_records = name_records[:3]
Не надо так делать, лучше придумывай новое имя каждый раз. Чем это закончится - однажды ты будешь смотреть на середину функции и видеть имя переменной, но что в ней находится в данный момент, ты не сможешь точно сказать, потому что будешь знать, что у тебя есть привычка переиспользования имён. Тебе будет казаться, что там одно, а там окажется другое, потому что ты забудешь, что что-то подменял там опять. А чтобы не забыть, что подменял, тебе всякий раз для понимания имён надо будет перечитывать весь код функции, чтобы точно знать, что в переменных. То есть одна привычка якобы экономии имён приводит к вырабатыванию другой привычки, которая затратна уже по времени. Ну и баги из-за таких подмен бывают так же часто, как и баги из-за копипаста.

Тут у тебя предположение, что ты знаешь про все исключения
Babay82
 except:
Если выполнить
 import this
там есть фраза “In the face of ambiguity, refuse the temptation to guess.”

Не надо предполагать, пиши точно все исключения, которые ожидаешь. Чем это закончится - однажды ты будешь получать странное поведение, при котором программа будет делать всё правильно, но результат будет неожиданный. И причину этого надо будет искать очень долго, а всё из-за того, что ты будешь уверен, что происходящее исключение является именно тем, которое ты и ожидаешь. А в это время там вылезет какое-то вообще неожиданное исключение, связанное с каким-нибудь отсутствующим ключом в словаре или невыполненной операцией внутри объекта. Но так как ты про него не думал при создании конструкции перехвата, то ты его и не будешь ждать. При этом оно будет происходит, а ты будешь думать, что это твоё ожидаемое исключение происходит и что там всё в порядке по алгоритму.


Ещё подумай на тему того, что будет, если тебе срочно потребуется добавить в программу французский язык, потому что какой-нибудь француз вдруг платит тебе €1000 за программу. Для этого фразы нужно держать отдельно от кода и все в одном месте. Это касается не только фраз, но и других данных, которые гипотетически могут измениться. Даже если ты уверен, что оно не изменится никогда, уверенность эту убирай и делай так, будто это ружьё, висящее на стене, которое когда-нибудь выстрелит. Например, имя файла для базы данных у тебя зафиксировано, потому что ты не думаешь, что оно изменится. Но на эти мысли расчитывать нельзя, так как очень часто бывает так, что программа дописывается неожиданным образом и в ней появляются новые имена файлов с настройками, которые к старым именам уже не подходят (создают двусмысленности с ними). И чем проще поменять имя файла с данными в программе на другое, тем лучше. Завтра таких имён станет сто в программе и скорость их исправления будет очень сильно влиять на разработку. Переделывать полпроекта три дня и проверять каждое изменение при этом на безошибочность гораздо сложнее, чем переделать один файл во всём проекте за пять минут и без всяких проверок просто знать, что там всё в порядке.
Babay82
py.user.next
Спасибо, принял к сведению.
py.user.next
Например, имя файла для базы данных у тебя зафиксировано, потому что ты не думаешь, что оно изменится. Но на эти мысли расчитывать нельзя, очень часто бывает так, что программа дописывается неожиданным образом и в ней появляются новые имена файлов с настройками, которые к старым именам уже не подходят (создают двусмысленности с ними). И чем проще поменять имя файла с данными в программе на другое, тем лучше. Завтра таких имён станет сто в программе и скорость их исправления будет очень сильно влиять на разработку. Переделывать полпроекта три дня и проверять каждое изменение при этом на безошибочность гораздо сложнее, чем переделать один файл во всём проекте за пять минут и без всяких проверок просто знать, что там всё в порядке.
Например, каким образом это можно реализовать?В общих чертах, чтоб не совершать в дальнейшем таких ошибок
py.user.next
Babay82
Например, каким образом это можно реализовать?
Надо параметризировать функцию. То есть имя файла должно в неё подаваться, а не быть зашито внутри функции. Потом ты все такие имена собираешь в одном месте. И когда они в одном месте, ты можешь их “настраивать” (динамически присваивать им разные значения, в зависимости от условий запуска программы).

В частности, как это выглядит на практике. Очень частое явление, когда у тебя есть файл с конфигурацией программы. Файл конфигурации нужен для того, чтобы разные пользователи программы могли её под себя настраивать по-разному. Это касается и таких пользователей, которые в разное время входят в одну и ту же операционную систему. Каждый из них может захотеть такие настройки, которые противоречат настройкам другого пользователя. Поэтому чаще всего в программах есть глобальная конфигурация для всех пользователей и возможность её перекрыть своей, пользовательской конфигурацией. И вот программа должна уметь брать глобальную конфигурацию, но при желании пользователя она должна уметь брать его конфигурацию. Допустим, пользователь просто указывает в командной строке при запуске программы
program -c myconfig.xml
и программа вместо конфигурации из глобального конфигурационного файла по умолчанию config.xml использует вот этот файл пользователя myconfig.xml из текущей директории. А как она это сделает? Она просто в одном месте заменит значение строки с именем файла - и все функции, которые с этим файлом работают, его путь получат из этой строки, потому что этот путь в каждую из функций передаётся через аргумент.

Бывают там ещё более сложные моменты - когда надо не весь файл заменить, а подменить только некоторые поля. И поэтому имя твоей базы данных может хранится в конфигурационном файле, а может подаваться с командной строки. Это всё не одно вместо другого, а одно идёт вдобавок к другому. Ты просто меняешь одну строчку в конфигурационном файле и твоя программа начинает сохранять данные вообще в другую базу. То есть программу ты не меняешь, но при этом можешь её очень гибко настраивать на произвольное поведение.

А когда у тебя всё зашито, оно так удобно в целом смотрится, но ты не можешь менять ничего. Ты даже не сможешь это разным пользователям дать, потому что для них для всех что-нибудь да будет неудобным и поменять они это не смогут, потому что оно зашито в программе.
Babay82
py.user.next
Спасибо, учту. Я просто до таких нюансов не дошел ещё, наверное. Пока осваиваюсь в новой, для себя области знаний.
Rodegast
 def acquaintance(question):
    response = None
    while not response:
        response = input(question). title()
    return response

Стремись к тому что бы функции возвращали только данные определённого типа. В данном случае лучше заменить response = None на response = “” Модуль pickle у тебя не используется. Полки лучше заменить на что нибудь другое.
Babay82
Rodegast
Ст
Спасибо, проект делал , после прохождения главы о файлах, и сразу импортировал модули т.к. изначально планировал записывать рекорды в виде списков со словарями, но не знаю как при создании нового рекорда сделать так что-бы автоматически создавалась бы новая переменная которая бы ссылалась на этот список, что-бы потом можно было к нему обратиться(чтоб не переписывать постоянно файл с рекордами), а с полками как то нашел выход .
Да и проект был для закрепления материала по работе с текстовыми и бинарными файлами, pickle использовал в учебном задании , это код автора в который нужно было добавить очки за ответы и список результатов. В одном случае с помощью pickle .Вот здесь:
 import sys, pickle, shelve
def instruction():
    """Загружает инструкцию с игрой"""
    print("""\t\t\tДобро пожаловать в сказку!
        \tВам будут заданы вопросы и к ним предложены варианты ответов
        отвечаете правильно, получаете указанное количество баллов, если нет
        \t\tне получаете ничего. Желаю вам сказочной удачи!\n\n""")
def ask_yes_no(question):
    """Задает вопрос с ответом "ДА" или "НЕТ"."""
    response = None
    while response not in ("ДА", "НЕТ"):
       response  = input(question).upper()
    return response
def acquaintance(question):
    """Знакомство с пользователем"""
    response = None
    while not response:
        response = input(question). title()
    return response
def open_file(file_name, mode):
    """Открывает файл"""
    try:
        the_file = open(file_name, mode, encoding = "utf-8")
    except IOError as e:
        print("Невозможно открыть файл", file_name, ", работа программы будет завершена!\n")
        input("Нажмите Enter чтобы выйти\n\n")
        sys.exit()
    else:
        return the_file
def next_line(the_file):
    """Возвращает в отформатированном виде очередную строку игрового файла"""
    line = the_file.readline()
    line = line.replace("/","\n")
    return line
def next_block(the_file):
    """возвращает очередной блок данных из игрового файла"""
    category = next_line(the_file)
    question = next_line(the_file)
    point = next_line(the_file)
    if point:
        point = int(point[0])
    answer = []
    for i in range(4):
        answer.append(next_line(the_file))
    correct = next_line(the_file)
    if correct:
        correct = correct[0]
    explanation = next_line(the_file)
    return category, question, answer, correct, explanation, point
def welcome(title):
    """Приветствует игрока и сообщает ему тему игры"""
    print("\t\tДобро пожаловать в игру \"Викторина\"!\n")
    print("\t\t",title ,"\t\t")
def records_list(name, score, file_name, mode):
    """Добавляет рекорды в файл"""
    records_list = open("records_list.dat", "ab")
    record = (score, name)
    pickle.dump(record, records_list)
    records_list.close()
def demonstration_records(file_name, mode):
    """Выводит список рекордов на экран"""
    print_record =[]
    with open(file_name, mode) as records_list:
        while True:
            try:
                record = pickle.load(records_list)
                print_record.append(record)
            except:
                break                            
    print_record.sort(reverse=True)
    print_record = print_record[:3]
    for i in print_record:
        score, name = i
        print("\t\t", name,"\t\t", score, "баллов\n\n")
    
        
def main():
    instruction()
    end_game = None
    while end_game != "НЕТ":
        viktorin_file = open_file("Викторина.txt", "r")
        title = next_line(viktorin_file)
        welcome(title)
        name = acquaintance("Давайте знакомиться. Как вас зовут?:")
        score = 0
        category = None
        while True:
            category, question, answer, correct, explanation, point = next_block(viktorin_file)
            if not category:
                break
            print(category)
            print(question)
            for i in range(4):
                print("\t", i + 1, "-", answer[i])
            answer = input("Ваш ответ:")
            if answer == correct:
                print("Правильный ответ!", end = "")
                score += point
            else:
                print("Неправильный ответ!")
                print("Правильный ответ - ", correct)
            print(explanation)
            print("Ваш счет:", score,"\n\n")
        records_list(name, score, "records_list.dat", "ab")        
        viktorin_file.close()
        print("Это был последний вопрос!")
        print("На вашем счету ", score, "очков")
        print("\n\n\t\t\tВОТ НАШИ РЕКОРДЕСМЕНЫ!!!\n\n")
        demonstration_records("records_list.dat", "rb")
        end_game = ask_yes_no("""\n\n\t\t\tЕсли желаете отдать ход другому игроку, введите \"да\"
                            если желаете закончить игру введите \"нет\".""")
main()
input("Нажмите Enter чтобы выйти\n\n")
    
А вот здесь с помощью текстового файла( тут пришлось поломать голову над сортировкой)):
 import sys
def instruction():
    """Загружает инструкцию с игрой"""
    print("""\t\t\tДобро пожаловать в сказку!
        \tВам будут заданы вопросы и к ним предложены варианты ответов
        отвечаете правильно, получаете указанное количество баллов, если нет
        \t\tне получаете ничего. Желаю вам сказочной удачи!\n\n""")
def ask_yes_no(question):
    """Задает вопрос с ответом "ДА" или "НЕТ"."""
    response = None
    while response not in ("ДА", "НЕТ"):
       response  = input(question).upper()
    return response
def acquaintance(question):
    """Знакомство с пользователем"""
    response = None
    while not response:
        response = input(question). title()
    return response
def open_file(file_name, mode):
    """Открывает файл"""
    try:
        the_file = open(file_name, mode, encoding = "utf-8")
    except IOError as e:
        print("Невозможно открыть файл", file_name, ", работа программы будет завершена!\n")
        input("Нажмите Enter чтобы выйти\n\n")
        sys.exit()
    else:
        return the_file
def next_line(the_file):
    """Возвращает в отформатированном виде очередную строку игрового файла"""
    line = the_file.readline()
    line = line.replace("/","\n")
    return line
def next_block(the_file):
    """возвращает очередной блок данных из игрового файла"""
    category = next_line(the_file)
    question = next_line(the_file)
    point = next_line(the_file)
    if point:
        point = int(point[0])
    answer = []
    for i in range(4):
        answer.append(next_line(the_file))
    correct = next_line(the_file)
    if correct:
        correct = correct[0]
    explanation = next_line(the_file)
    return category, question, answer, correct, explanation, point
def welcome(title):
    """Приветствует игрока и сообщает ему тему игры"""
    print("\t\tДобро пожаловать в игру \"Викторина\"!\n")
    print("\t\t",title ,"\t\t")
def apps_record(name,score,file_name, mode):
    """Добавляет рекорд"""
    record = open("records.txt", "a", encoding="utf-8")
    score = str(score)
    line = ("Игрок \t" + name + "\t" + score + " \tбаллов\n")
    record.write(line)
    record.close()
def sort_balls(rec_list):
    """Предоставляет аргумент для сортировки списка рекордов"""
    i = int(rec_list[-11:-9])
    return i
def print_records(file_name, mode):
    """Выводит рекорды на экран"""
    name_records = open("records.txt", "r", encoding="utf-8")
    records = name_records.readlines()
    records.sort(key=sort_balls, reverse=True)
    records = records[:5]
    for i in records:    
        print(i)
def main():
    instruction()
    end_game = None
    while end_game != "НЕТ":
        viktorin_file = open_file("Викторина.txt", "r")
        title = next_line(viktorin_file)
        welcome(title)
        name = acquaintance("Давайте знакомиться. Как вас зовут?:")
        score = 0
        category = None
        while True:
            category, question, answer, correct, explanation, point = next_block(viktorin_file)
            if not category:
                break
            print(category)
            print(question)
            for i in range(4):
                print("\t", i + 1, "-", answer[i])
            answer = input("Ваш ответ:")
            if answer == correct:
                print("Правильный ответ!", end = "")
                score += point
            else:
                print("Неправильный ответ!")
                print("Правильный ответ", correct)
            print(explanation)
            print("Ваш счет:", score,"\n\n")
        apps_record(name,score,"records.txt", "a")
        print("t\t\t\n\nВОТ НАШИ РЕКОРДЕСМЕНЫ!!!\n\n")
        print_records("records.txt", "r")
        viktorin_file.close()
        print("Это был последний вопрос!")
        print("На вашем счету ", score, "очков")
        end_game = ask_yes_no("""\n\n\t\t\tЕсли желаете отдать ход другому игроку, введите \"да\"
                            если желаете закончить игру введите \"нет\".""")
main()
input("Нажмите Enter чтобы выйти\n\n")
Rodegast
Стремись к тому что бы функции возвращали только данные определённого типа.
Т.к. в случае возврата None выпадет исключение?
Rodegast
> Т.к. в случае возврата None выпадет исключение?

Да. Например при вызове функции я ожидаю получить строку, а мне приходит None. В результате возникает ошибка TypeError. Это очень распространённая ситуация.
FishHook
Rodegast
Да. Например при вызове функции я ожидаю получить строку, а мне приходит None. В результате возникает ошибка TypeError. Это очень распространённая ситуация.

Ок, что должна вернуть функция если в процессе выполнения не удалось сформировать строку-ответ?
Rodegast
> Ок, что должна вернуть функция если в процессе выполнения не удалось сформировать строку-ответ?

Пустую строку или вызвать исключение.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB