Чистыйкод дляпродолжающи х
Скачать 7.85 Mb.
|
14 Проекты для тренировки До настоящего момента в этой книге я рассказывал о при- емах написания удобочитаемого питонического кода. Теперь настало время применить эти приемы на практике; рассмо- трим исходный код двух игр командной строки: «Ханойская башня» и «Четыре в ряд». Это небольшие проекты, и они работают в текстовом режиме, чтобы не переусложнять задачу, но они неплохо демонстрируют принципы, о которых я рассказал ранее. Для форматирования кода я использовал программу Black, описанную в разделе «Black: бескомпромиссная система форматирования кода», с. 79. Имена переменных были выбраны в соответствии с рекомендациями из главы 4. Код написан в питоническом стиле, о котором шла речь в главе 6. Кроме того, я написал комментарии и doc-строки по правилам из главы 11. Так как программы невелики, а объектно-ориентированное программирование (ООП) в книге еще не рассматривалось, я написал эти два проекта без классов — о них я расскажу чуть позже, в главах 15–17. В этой главе приведен полный исходный код этих двух проектов — с полным ана- лизом. Объяснения относятся не столько к тому, как работает код (для понимания этого достаточно базового понимания синтаксиса Python), а скорее к тому, почему этот код написан именно так, а не иначе. Тем не менее разные разработчики могут иметь разные мнения относительно того, как писать код и какой код следует счи- тать питоническим. Безусловно, ничто не мешает вам оспаривать и критиковать исходники, представленные в этих проектах. После того как вы прочитаете код проекта в книге, я рекомендую вам самостоятель- но ввести этот код и несколько раз выполнить программы, чтобы понять, как они работают. Затем попытайтесь заново реализовать их с нуля. Ваш код не обязательно должен полностью повторять тот, что приведен в книге, но переписывая его, вы Головоломка «Ханойская башня» 289 получите представление о принимаемых решениях и компромиссах, на которые приходится идти программисту. Головоломка «Ханойская башня» Даны три стержня, на один из которых нанизаны диски, причем диски отличаются размером и лежат меньший на большем. Задача состоит в том, чтобы перенести пирамиду из дисков на другой стержень за наименьшее число ходов (рис. 14.1). При этом действуют три ограничения. 1. Диски можно перемещать по одному. 2. Игрок может перекладывать диски только с верха стопки на верх другой стопки. 3. Нельзя переложить больший диск на меньший. Рис. 14.1. Головоломка «Ханойская башня» Решение головоломки часто используется в компьютерной науке как задача для изучения рекурсивных алгоритмов. Наша программа не будет решать головоломку; она будет выводить изображение головоломки, чтобы пользователь мог решить ее. За дополнительной информацией о головоломке «Ханойская башня» обращайтесь на https://ru.wikipedia.org/wiki/Ханойская_башня. Вывод результатов Программа рисует башни в ASCII-графике, для изображения дисков используются текстовые символы. Приложение выглядит примитивно по сравнению с современ- ными программами, но такой подход сохраняет простоту реализации, потому что 290 Глава 14.Проекты для тренировки для взаимодействия с пользователем достаточно вызовов print() и input() . Ниже показан примерный результат запуска программы. Текст, введенный игроком, обо- значен жирным шрифтом. THE TOWER OF HANOI, by Al Sweigart al@inventwithpython.com Move the tower of disks, one disk at a time, to another tower. Larger disks cannot rest on top of a smaller disk. More info at https://en.wikipedia.org/wiki/Tower_of_Hanoi || || || @_1@ || || @@_2@@ || || @@@_3@@@ || || @@@@_4@@@@ || || @@@@@_5@@@@@ || || A B C Enter the letters of "from" and "to" towers, or QUIT. (e.g., AB to moves a disk from tower A to tower B.) > AC || || || || || || @@_2@@ || || @@@_3@@@ || || @@@@_4@@@@ || || @@@@@_5@@@@@ || @_1@ A B C Enter the letters of "from" and "to" towers, or QUIT. (e.g., AB to moves a disk from tower A to tower B.) --snip-- || || || || || @_1@ || || @@_2@@ || || @@@_3@@@ || || @@@@_4@@@@ || || @@@@@_5@@@@@ A B C You have solved the puzzle! Well done! Для n дисков решение головоломки требует минимум 2 n – 1 ходов. Таким образом, решение для башни с пятью дисками состоит из 31 хода: AC, AB, CB, AC, BA, BC, AC, AB, CB, CA, BA, CB, AC, AB, CB, AC, BA, BC, AC, BA, CB, CA, BA, BC, AC, AB, CB, AC, BA, BC и AC. Если вам понадобится усложнить пример для самостоятельного решения, увеличьте переменную TOTAL_DISKS в программе с 5 до 6. Головоломка «Ханойская башня» 291 Исходный код Откройте в редакторе или IDE новый файл и введите приведенный ниже код. Со- храните файл с именем towerofhanoi.py """THE TOWER OF HANOI, by Al Sweigart al@inventwithpython.com Головоломка с перемещением дисков.""" import copy import sys TOTAL_DISKS = 5 # Чем больше дисков, тем сложнее головоломка. # Изначально все диски находятся на стержне A: SOLVED_TOWER = list(range(TOTAL_DISKS, 0, -1)) def main(): """Проводит одну игру Ханойская башня.""" print( """THE TOWER OF HANOI, by Al Sweigart al@inventwithpython.com Move the tower of disks, one disk at a time, to another tower. Larger disks cannot rest on top of a smaller disk. More info at https://en.wikipedia.org/wiki/Tower_of_Hanoi """ ) """Словарь towers содержит ключи "A", "B" и "C", и значения - списки, представляющие стопку дисков. Список содержит целые числа, представляющие диски разных размеров, а начало списка представляет низ башни. Для игры с 5 дисками список [5, 4, 3, 2, 1] представляет заполненную башню. Пустой список list [] представляет башню без дисков. В списке [1, 3] больший диск находится на меньшем диске, такая конфигурация недопустима. Список [3, 1] допустим, так как меньшие диски могут размещаться на больших.""" towers = {"A": copy.copy(SOLVED_TOWER), "B": [], "C": []} while True: # Один ход для каждой итерации цикла. # Вывести башни и диски: displayTowers(towers) # Запросить ход у пользователя: fromTower, toTower = getPlayerMove(towers) # Переместить верхний диск с fromTower на toTower: disk = towers[fromTower].pop() towers[toTower].append(disk) # Проверить, решена ли головоломка: if SOLVED_TOWER in (towers["B"], towers["C"]): displayTowers(towers) # Вывести башни в последний раз. 292 Глава 14.Проекты для тренировки print("You have solved the puzzle! Well done!") sys.exit() def getPlayerMove(towers): """Запрашивает ход у пользователя. Возвращает (fromTower, toTower).""" while True: # Пока пользователь не введет допустимый ход. print('Enter the letters of "from" and "to" towers, or QUIT.') print("(e.g., AB to moves a disk from tower A to tower B.)") print() response = input("> ").upper().strip() if response == "QUIT": print("Thanks for playing!") sys.exit() # Убедиться в том, что пользователь ввел допустимые обозначения башен: if response not in ("AB", "AC", "BA", "BC", "CA", "CB"): print("Enter one of AB, AC, BA, BC, CA, or CB.") continue # Снова запросить ход. # Более содержательные имена переменных: fromTower, toTower = response[0], response[1] if len(towers[fromTower]) == 0: # Башня fromTower не может быть пустой: print("You selected a tower with no disks.") continue # Снова запросить ход. elif len(towers[toTower]) == 0: # На пустую башню можно переместить любой диск: return fromTower, toTower elif towers[toTower][-1] < towers[fromTower][-1]: print("Can't put larger disks on top of smaller ones.") continue # Снова запросить ход. else: # Допустимый ход, вернуть выбранные башни: return fromTower, toTower def displayTowers(towers): """Выводит три башни с дисками.""" # Вывести три башни: for level in range(TOTAL_DISKS, -1, -1): for tower in (towers["A"], towers["B"], towers["C"]): if level >= len(tower): displayDisk(0) # Вывести пустой стержень без диска. else: displayDisk(tower[level]) # Вывести диск. print() # Вывести обозначения башен A, B и C: Головоломка «Ханойская башня» 293 emptySpace = " " * (TOTAL_DISKS) print("{0} A{0}{0} B{0}{0} C\n".format(emptySpace)) def displayDisk(width): """Выводит диск заданной ширины. Ширина 0 означает отсутствие диска.""" emptySpace = " " * (TOTAL_DISKS - width) if width == 0: # Вывести сегмент стержня без диска: print(f"{emptySpace}||{emptySpace}", end="") else: # Вывести диск: disk = "@" * width numLabel = str(width).rjust(2, "_") print(f"{emptySpace}{disk}{numLabel}{disk}{emptySpace}", end="") # Если программа была запущена (а не импортирована), начать игру: if __name__ == "__main__": main() Запустите программу и сыграйте несколько раз, чтобы получить представление о том, что она делает, прежде чем читать объяснения исходного кода. Чтобы про- верить возможные опечатки, скопируйте код в сетевую программу diff по адресу https://inventwithpython.com/beyond/diff/. Написание кода А теперь рассмотрим исходный код и посмотрим, как в нем применяются приемы и шаблоны, описанные в книге. Начало программы: """THE TOWER OF HANOI, by Al Sweigart al@inventwithpython.com Головоломка с перемещением дисков.""" Программа начинается с многострочного комментария, который служит doc- строкой для модуля towerofhanoi . Встроенная функция help() использует эту информацию для описания модуля: >>> import towerofhanoi >>> help(towerofhanoi) Help on module towerofhanoi: NAME towerofhanoi DESCRIPTION THE TOWER OF HANOI, by Al Sweigart al@inventwithpython.com Головоломка с перемещением дисков. 294 Глава 14.Проекты для тренировки FUNCTIONS displayDisk(width) Выводит диск заданной ширины. --snip-- При необходимости в doc-строку модуля можно добавить больше слов или даже абзацев. Я ограничился минимумом текста, потому что программа очень проста. После doc-строки модуля следуют команды import : import copy import sys Black форматирует их как отдельные команды вместо одной команды import copy , sys . При таком подходе будет проще отслеживать добавление или удаление им- портированных модулей в системах контроля версий (таких как Git), которые отслеживают изменения, вносимые программистом. Затем определяются константы, требуемые программе: TOTAL_DISKS = 5 # Чем больше дисков, тем сложнее головоломка. # Изначально все диски находятся на стержне A: SOLVED_TOWER = list(range(TOTAL_DISKS, 0, -1)) Мы определяем константы в начале файла, чтобы сгруппировать их, и делаем их глобальными. Имена записываем в верхнем регистре согласно «змеиной» схеме, чтобы пометить их как константы. Константа TOTAL_DISKS определяет, сколько дисков используется в головоломке. Переменная SOLVED_TOWER содержит пример списка, содержащего решение голово- ломки: в списке указаны все диски, самый большой расположен внизу, а самый ма- ленький — наверху. Это значение генерируется на основании значения TOTAL_DISKS и для пяти дисков список имеет вид [5, 4, 3, 2, 1] Обратите внимание: в файле отсутствуют аннотации типов. Дело в том, что типы всех переменных, параметров и возвращаемых значений автоматически определя- ются на основании кода. Например, константе TOTAL_DISKS присваивается целое значение 5. По нему системы проверки типов (такие как Mypy) определяют, что TOTAL_DISKS будет содержать только целые числа. Мы определяем функцию main() , которая вызывается программой в конце файла: def main(): """Проводит одну игру Ханойская башня.""" print( """THE TOWER OF HANOI, by Al Sweigart al@inventwithpython.com Головоломка «Ханойская башня» 295 Move the tower of disks, one disk at a time, to another tower. Larger disks cannot rest on top of a smaller disk. More info at https://en.wikipedia.org/wiki/Tower_of_Hanoi """ ) Функции также могут содержать doc-строки. Обратите внимание на doc-строку main() под командой def . Чтобы просмотреть эту doc-строку, выполните команды import towerofhanoi и help(towerofhanoi.main) из интерактивной оболочки. Далее следует комментарий, который описывает структуру данных, используемую для представления башни, потому что она занимает центральное место в работе программы: """Словарь towers содержит ключи "A", "B" и "C" и значения - списки, представляющие стопку дисков. Список содержит целые числа, представляющие диски разных размеров, а начало списка представляет низ башни. Для игры с 5 дисками список [5, 4, 3, 2, 1] представляет заполненную башню. Пустой список list [] представляет башню без дисков. В списке [1, 3] больший диск находится на меньшем диске, такая конфигурация недопустима. Список [3, 1] допустим, так как меньшие диски могут размещаться на больших.""" towers = {"A": copy.copy(SOLVED_TOWER), "B": [], "C": []} Список SOLVED_TOWER используется как стек — одна из простейших структур данных, используемых при разработке. Стек представляет собой упорядоченный список значений, а его состояние может изменяться только добавлением (занесе- нием в стек) или удалением (извлечением из стека) значений на вершине (начале) стека. Эта структура данных идеально подходит для представления башен в нашей программе. Список Python можно преобразовать в стек; для этого нужно исполь- зовать метод append() для включения элементов и метод pop() для их извлечения и избегать изменения списка любыми другими способами. Конец списка будет рассматриваться как вершина стека. Каждое целое число в списке towers представляет один диск определенного разме- ра. Например, в игре с пятью дисками список [5, 4, 3, 2, 1] представляет полную стопку дисков от самого большего ( 5 ) внизу до самого маленького ( 1 ) наверху. Также обратите внимание на то, что в комментарии приведены примеры допусти- мого и недопустимого списка. Внутри функции main() находится бесконечный цикл, в котором выполняется один ход нашей головоломки: while True: # Один ход для каждой итерации цикла. # Вывести башни и диски: displayTowers(towers) 296 Глава 14.Проекты для тренировки # Запросить ход у пользователя: fromTower, toTower = getPlayerMove(towers) # Переместить верхний диск с fromTower на toTower: disk = towers[fromTower].pop() towers[toTower].append(disk) За один ход игрок смотрит на текущее состояние башен и вводит свой ход. Затем программа обновляет структуру данных towers . Подробности выполнения этих операций скрыты в функциях displayTowers() и getPlayerMove() . Благодаря со- держательным именам функция main() дает общее представление о том, что делает программа. Следующие строки проверяют, решил ли игрок головоломку, для чего решение из SOLVED_TOWER сравнивается с towers["B"] и towers["C"] : # Проверить, решена ли головоломка: if SOLVED_TOWER in (towers["B"], towers["C"]): displayTowers(towers) # Вывести башни в последний раз. print("You have solved the puzzle! Well done!") sys.exit() Сравнивать с towers["A"] не нужно, потому что в начале игры стержень уже со- держит завершенную башню; чтобы решить головоломку, игрок должен постро- ить башню на стержне B или C. Обратите внимание: SOLVED_TOWER используется повторно для генерирования начальных башен и проверки того, решил ли игрок головоломку. Так как SOLVED_TOWER является константой, можно быть уверенным в том, что SOLVED_TOWER всегда будет иметь значение, присвоенное в начале кода. Используемое условие эквивалентно SOLVED_TOWER == towers["B"] or SOLVED_TOWER == towers["C"] , но записывается короче — эту идиому Python я рассматривал в главе 6. Если условие истинно, значит, игрок решил головоломку и программа завершается. В противном случае цикл продолжается следующим ходом. Функция getPlayerMove() запрашивает у игрока ход и проверяет его по игровым правилам: def getPlayerMove(towers): """Запрашивает ход у пользователя. Возвращает (fromTower, toTower).""" while True: # Пока пользователь не введет допустимый ход. print('Enter the letters of "from" and "to" towers, or QUIT.') print("(e.g., AB to moves a disk from tower A to tower B.)") print() response = input("> ").upper().strip() Мы начинаем бесконечный цикл, который продолжается до выполнения одного из двух условий: либо цикл прерывается командой return (с выходом из функ- ции), либо программа будет завершена вызовом sys.exit() . Первая часть цикла Головоломка «Ханойская башня» 297 предлагает игроку ввести ход в виде двух букв — для башни, с которой перемещается диск и на которую он перемещается. Обратите внимание на инструкцию input("> ").upper().strip() , которая получает ввод с клавиатуры от игрока. Вызов input("> ") запрашивает текст у игрока с вы- водом приглашения > . Символ показывает, что игрок должен что-то ввести. Если программа не выведет приглашение, то игрок может подумать, что она зависла. Строка, полученная от input() , преобразуется к верхнему регистру вызовом ме- тода upper() . Это позволяет игроку вводить обозначения башен как в верхнем, так и в нижнем регистре — например, 'a' или 'A' для башни A. Затем для строки в верхнем регистре вызывается метод strip() для удаления пробельных символов в начале и конце строки на случай, если пользователь случайно добавил пробел при вводе хода. Такие удобства несколько упрощают работу с программой для пользователя. Все еще внутри функции getPlayerMove() мы проверяем данные, введенные поль- зователем: if response == "QUIT": print("Thanks for playing!") sys.exit() # Убедиться в том, что пользователь ввел допустимые обозначения башен: if response not in ("AB", "AC", "BA", "BC", "CA", "CB"): print("Enter one of AB, AC, BA, BC, CA, or CB.") continue # Снова запросить ход. Если пользователь вводит 'QUIT' (в произвольном регистре и даже с пробелами в начале или конце строки благодаря вызовам upper() и strip() ), программа за- вершается. Также функция getPlayerMove() могла бы вернуть 'QUIT' , чтобы указать вызывающей стороне на необходимость вызвать sys.exit() , вместо того чтобы вы- зывать sys.exit() в getPlayerMove() . Но это усложнило бы возвращаемое значение getPlayerMove() : функция должна возвращать либо кортеж с двумя строками (для хода игрока), либо одну строку 'QUIT' . Функция, которая возвращает значения одного типа данных, более понятна, чем та, которая может возвращать значения многих возможных типов. Эта тема обсуждалась в разделе «Возвращаемые значения всегда должны иметь один тип данных», с. 212. Из трех башен можно составить только шесть комбинаций «с какой башни — на какую башню». Несмотря на тот факт, что мы жестко зафиксировали все шесть значений в условии, проверяющем ход, такой код читается намного проще, чем что-нибудь вроде конструкции вида len(response) != 2 or response[0] not in 'ABC' or response[1] not in 'ABC' or response[0] == response[1] . С учетом этого факта подход с жестко фиксированными вариантами оказывается наиболее пря- молинейным. 298 Глава 14.Проекты для тренировки Как правило, использование «магических» значений вроде "AB" , "AC" и т. д. считается нежелательным, потому что они работают, только пока в программе используются три стержня. Но хотя количество дисков можно отрегулировать изменением константы TOTAL_DISKS , крайне маловероятно, что в игре увеличится количество стержней. В данном случае запись всех возможных перемещений в программе допустима. Две новые переменные fromTower и toTower создаются как содержательные имена для данных. Они не имеют функционального назначения, но лучше читаются, чем response[0 ] и response[1] : # Более содержательные имена переменных: fromTower, toTower = response[0], response[1] Затем мы проверяем, есть ли для созданных пользователем башен допустимый ход: if len(towers[fromTower]) == 0: # Башня fromTower не может быть пустой: print("You selected a tower with no disks.") continue # Снова запросить ход. elif len(towers[toTower]) == 0: # На пустую башню можно переместить любой диск: return fromTower, toTower elif towers[toTower][-1] < towers[fromTower][-1]: print("Can't put larger disks on top of smaller ones.") continue # Снова запросить ход. Если ход недопустим, команда continue возвращает управление в начало цикла, где игроку снова предлагается ввести ход. Затем мы проверяем, не пуста ли башня toTower . Если она пуста, возвращается fromTower, toTower , чтобы подчеркнуть, что ход допустим, потому что диск всегда можно поместить на пустой стержень. Первые два условия проверяют, что к моменту проверки третьего условия towers[toTower] и towers[fromTower] не будут пустыми и не вызовут ошибки IndexError . Условия были упорядочены так, чтобы их порядок предотвращал IndexError и дополни- тельные проверки. Важно, чтобы ваши программы обрабатывали любой недопустимый ввод от поль- зователя или потенциальные ошибочные ситуации. Пользователи могут не знать, что нужно ввести, или могут допустить опечатку при вводе. Также файлы могут быть неожиданно удалены или в базе данных может произойти сбой. Ваши про- граммы должны обладать достаточной устойчивостью к аномальным ситуациям; в противном случае возможны аварийные завершения или позднее могут возник- нуть коварные ошибки. Если ни одно из условий не равно True , getPlayerMove() возвращает fromTower, toTower : else: # Допустимый ход, вернуть выбранные башни: return fromTower, toTower Головоломка «Ханойская башня» 299 В Python команды return всегда возвращают одно значение. Хотя может показаться, что эта команда return возвращает два значения, Python в действительности воз- вращает один кортеж с двумя значениями, что эквивалентно return (fromTower, toTower) . Программисты на языке Python часто опускают круглые скобки в этом контексте. Круглые скобки определяют кортеж в меньшей степени, чем запятые. Обратите внимание: программа вызывает функцию getPlayerMove() только один раз из функции main() . Функция не избавляет от дублирования кода — самой рас- пространенной причины для использования функций. Нет никаких причин, по которым весь код getPlayerMove() нельзя было бы разместить в функции main() Но функции также могут использоваться как механизм структурирования кода по отдельным блокам, для чего в данном случае и задействована getPlayerMove() С этой функцией main() не будет слишком длинной и громоздкой. Функция displayTowers() выводит диски на башнях A, B и C в аргументе towers : def displayTowers(towers): """Выводит три башни с дисками.""" # Вывести три башни: for level in range(TOTAL_DISKS, -1, -1): for tower in (towers["A"], towers["B"], towers["C"]): if level >= len(tower): displayDisk(0) # Вывести пустой стержень без диска. else: displayDisk(tower[level]) # Вывести диск. print() Для вывода каждого диска в башне функция зависит от функции displayDisk() , которая будет рассмотрена следующей. Цикл for level проверяет каждый воз- можный диск, а цикл for tower проверяет башни A, B и C. Функция displayTowers() вызывает displayDisk() для вывода каждого диска с за- данной шириной, или при передаче 0 выводится только стержень без диска: # Вывести обозначения башен A, B и C: emptySpace = " " * (TOTAL_DISKS) print("{0} A{0}{0} B{0}{0} C\n".format(emptySpace)) На экране выводятся метки A, B и C. Эта информация помогает игроку различать баш- ни, а также подчеркивает, что башни обозначаются буквами A, B и C, а не 1, 2 и 3, или «Левая», «Средняя» и «Правая». Я не стал использовать цифры 1, 2 и 3 для пометки башен, чтобы игроки не путали эти числа с числовыми обозначениями размера дисков. Переменной emptySpace присваивается количество пробелов между метками, которое в свою очередь вычисляется на основании TOTAL_DISKS , потому что чем больше дисков в игре, тем больше разделяющее их расстояние. Вместо f-строк, как в print(f'{emptySpace} A{emptySpace}{emptySpace} B{emptySpace}{emptySpace} 300 Глава 14.Проекты для тренировки C\n') , используется метод строк format() . Это позволяет применить один и тот же аргумент emptySpace всюду, где в соответствующей строке встречается {0} , в резуль- тате чего код получается более коротким и лучше читается, чем в версии с f-строкой. Функция displayDisk() выводит один диск заданной ширины. Если диск отсут- ствует, выводится только стержень: def displayDisk(width): """Выводит диск заданной ширины. Ширина 0 означает отсутствие диска.""" emptySpace = " " * (TOTAL_DISKS - width) if width == 0: # Вывести сегмент стержня без диска: print(f"{emptySpace}||{emptySpace}", end="") else: # Вывести диск: disk = "@" * width numLabel = str(width).rjust(2, "_") print(f"{emptySpace}{disk}{numLabel}{disk}{emptySpace}", end="") Изображение диска состоит из начального пробела, символов @ в количестве, равном ширине диска, двух символов ширины (с начальным символом подчеркивания, если ширина задается одной цифрой), еще одной серии символов @ и завершающего пробела. Чтобы вывести только пустой стержень, достаточно вывести начальный пробел, две вертикальные черты и завершающий пробел. В результате для вывода следующей башни потребуются шесть вызовов displayDisk() с шестью разными аргументами ширины: || @_1@ @@_2@@ @@@_3@@@ @@@@_4@@@@ @@@@@_5@@@@@ Обратите внимание на то, как функции displayTowers() и displayDisk() разделяют обязанности по выводу башен. Хотя функция displayTowers() решает, как интер- претировать структуры данных, представляющие каждую башню, она зависит от displayDisk() для отображения каждого диска на башне. Разбиение программы на меньшие функции упрощает тестирование каждой части. Если программа не- правильно выводит диски, то, скорее всего, проблема в displayDisk() . Если диски следуют в неправильном порядке, то, вероятно, проблема в displayTowers() В любом случае объем кода, который необходимо отладить, будет намного меньше. Для вызова функции main() используется стандартная идиома Python: # Если программа была запущена (а не импортирована), начать игру: if __name__ == "__main__": main() Игра «Четыре в ряд» 301 Python автоматически присваивает переменной __name__ значение '__main__' , если игрок запускает программу towerofhanoi.py напрямую. Но если кто-то импортирует программу как модуль командой import towerofhanoi , то __name__ будет присво- ено значение 'towerofhanoi' . Строка if __name__ == '__main__': будет вызывать функцию main() , если кто-то запускает нашу программу, тем самым начиная игру. Но если вы хотите просто импортировать программу как модуль, чтобы, допустим, вызывать ее отдельные функции для модульного тестирования, то это условие дает результат False , и функция main() не вызывается. |