HOW-TO: Программа на Python, Часть 8 Сравнение версий

Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Следующая версия
Предыдущая версия
fullcircle:34:python_ч_8 [2010/06/22 20:17]
создано
fullcircle:34:python_ч_8 [2015/02/02 20:25] (текущий)
Строка 3: Строка 3:
 //​Автор — Грэг Валтерс (Greg Walters)// //​Автор — Грэг Валтерс (Greg Walters)//
 </​style>​ </​style>​
 +
  
   - [[..:​27:​python_ч_1|Программа на Python — часть 1]]   - [[..:​27:​python_ч_1|Программа на Python — часть 1]]
Строка 13: Строка 14:
   - [[..:​34:​python_ч_8|Программа на Python — часть 8]]   - [[..:​34:​python_ч_8|Программа на Python — часть 8]]
   - [[..:​35:​python_ч_9|Программа на Python — часть 9]]   - [[..:​35:​python_ч_9|Программа на Python — часть 9]]
 +  - [[..:​36:​python_ч_10|Программа на Python — часть 10]]
 +  - [[..:​37:​python_ч_11|Программа на Python — часть 11]]
 +  - [[..:​38:​python_ч_12|Программа на Python — часть 12]]
 +  - [[..:​39:​python_ч_13|Программа на Python — часть 13]]
 +  - [[..:​40:​python_ч_14|Программа на Python — часть 14]]
 +
 +В этот раз мы продолжим работать над нашей базой рецептов,​ начатой в прошлом номере. Эта часть будет длинной,​ с большим количеством кода, поэтому запаситесь терпением — и вперёд. И не пытайтесь сойти на ходу. Мы уже создали базу данных. Теперь мы хотим отображать её содержимое,​ добавлять и удалять его. Как это сделать?​ Обо всём по порядку. Мы начнём с приложения,​ запускаемого в терминале,​ поэтому нам нужно создать меню. Так же мы создадим класс, который будет содержать операции с нашей базой. Давайте начнём с кусочка программы,​ показанного вверху справа.
 +<​code>#​!/​usr/​bin/​python
 +#​------------------------------------------------------
 +# Cookbook.py
 +# Created for Beginning Programming Using Python #8
 +# and Full Circle Magazine
 +#​------------------------------------------------------
 +import apsw
 +import string
 +import webbrowser
 +
 +class Cookbook:
 +
 +def Menu():
 +    cbk = Cookbook() # Initialize the class
 +
 +Menu()</​code>​
 +
 +Теперь сделаем макет меню. Класс ''​Cookbook''​ пока оставим пустым. Функция ''​Menu()''​ будет достаточно большим циклом и будет отображать список вариантов,​ которые пользователь может выполнить. Мы будем использовать цикл ''​while''​. Измените код подпрограммы так, как показано внизу справа.
 +<​code>​def Menu():
 +    cbk = Cookbook() # Initialize the class
 +    loop = True
 +    while loop == True:
 +        print '​==================================================='​
 +        print ' ​              ​RECIPE DATABASE'​
 +        print '​==================================================='​
 +        print ' 1 - Show All Recipes'​
 +        print ' 2 - Search for a recipe'​
 +        print ' 3 - Show a Recipe'​
 +        print ' 4 - Delete a recipe'​
 +        print ' 5 - Add a recipe'​
 +        print ' 6 - Print a recipe'​
 +        print ' 0 - Exit'
 +        print '​==================================================='​
 +        response = raw_input('​Enter a selection -> '​)</​code>​
 +
 +Затем мы заполним меню структурой ''​if|elif|else'',​ которая показана наверху следующей страницы.
 +<​code> ​       if response == '​1':​ # Show all recipes
 +            pass
 +        elif response == '​2':​ # Search for a recipe
 +            pass
 +        elif response == '​3':​ # Show a single recipe
 +            pass
 +        elif response == '​4':​ # Delete Recipe
 +            pass
 +        elif response == '​5':​ # Add a recipe
 +            pass
 +        elif response == '​6':​ # Print a recipe
 +            pass
 +        elif response == '​0':​ # Exit the program
 +            print '​Goodbye'​
 +            loop = False
 +        else:
 +            print '​Unrecognized command. ​ Try again.'</​code>​
 +
 +Давайте ещё раз быстро просмотрим код нашей функции. Всё начинается с отображения опций, которые пользователь может выбирать. Мы устанавливаем значение переменной ''​loop''​ равным ''​True''​ и затем используем цикл ''​while'',​ который будет продолжаться до тех пор, пока значение ''​loop''​ не станет равным ''​False''​. Также мы используем функцию ''​raw_input()''​ чтобы пользователь мог ввести необходимый вариант. Для того чтобы можно было протестировать наше меню, нужно немного доработать наш класс, добавив функцию ''​_init_()'':​
 +<​code>​def __init__(self):​pass</​code>​
 +
 +Теперь сохраните свою программу в тот же каталог,​ что и базу, которую мы создали в прошлый раз, и запустите её. Вы должны увидеть что-то,​ похожее на текст с картинки вверху справа.
 +<​code>/​usr/​bin/​python -u  "/​home/​greg/​python_examples/​APSW/​cookbook/​cookbook_stub.py"​
 +===================================================
 +               ​RECIPE DATABASE
 +===================================================
 +1 - Show All Recipes
 +2 - Search for a recipe
 +3 - Show a Recipe
 +4 - Delete a recipe
 +5 - Add a recipe
 +6 - Print a recipe
 +0 - Exit
 +===================================================
 +Enter a selection -></​code>​
 +
 +Программа должна просто печатать меню снова и снова, пока вы не введёте «0», затем напечатать «GoodBye» и завер-шить работу. Теперь мы можем начинать работать над метода-ми класса Cookbook. Их нам нуж-но несколько:​ метод, отобража-ющий всю информацию из базы данных,​ метод для поиска рецептов по базе, метод для отображения информации об определённом рецепте,​ исполь-зуя все три таблицы,​ метод, который удаляет рецепт из базы, метод, позволяющий добавлять рецепты,​ и метод, печатающий выбранный рецепт на принтере. Метод PrintAllRecipe не должен принимать никаких параметров кроме (self), так же как и SearchforRecipe и EnterNew. Методам PrintSingleRecipe,​ DeleteRecipe и PrintOut необхо-димо знать рецепт,​ с которым нужно работать,​ поэтому нам нужен параметр,​ который мы назовем «which». Используйте команду «pass» вместо тела ещё не написанных методов. В классе Cookbook создайте следующие функции:​
 +
 +<​code>​def PrintAllRecipes(self):​pass
 +def SearchForRecipe(self):​pass
 +def PrintSingleRecipe(self,​which):​pass
 +def DeleteRecipe(self,​which):​pass
 +def EnterNew(self):​pass
 +def PrintOut(self,​which):​pass</​code>​
 +
 +Для некоторых пунктов меню нам необходимо вывести весь список рецептов из таблицы Recipe — чтобы пользователь мог выбрать нужные ему рецеп-ты. Это будут пункты 1, 3, 4 и 6. Так что измените эти пункты,​ заменив команду pass вызовом функции cbk.PrintAllRecipes(). Те-перь наша обработка запроса выг-лядит примерно так, как показа-но вверху следующей страницы.
 +
 +Ещё надо написать код метода __init__. Замените его следующими строками:​
 +
 +<​code>​def __init__(self):​
 +global connection
 +global cursor
 +self.totalcount = 0
 +connection=apsw.Connection("​cookbook.db3"​)
 +cursor=connection.cursor()</​code>​
 +
 +Сначала создаются две глобальные переменные для соединения и курсора. Мы можем использовать их в любом месте класса Cookbook. Затем создаётся переменная self.totalcount,​ которая используется для подсчета количества рецептов. Мы будем использовать эту переменную позже. И, наконец,​ создаётся соединение и курсор.
 +
 +Следующим шагом мы испра-вим метод PrintAllRecipes() класса Cookbook. Так как мы завели две глобальные переменные для соединения и курсора,​ нам не нужно заново создавать их в каждом методе. Также давайте улучшим вывод заголовков на-шего списка рецептов. Для этого мы будем использовать команду форматирования «%s». Нам надо, чтобы заголовки выглядели примерно так:
 +
 +<​code>​Item Name Serves Source</​code>​
 +
 +Наконец,​ нам надо создать ещё несколько SQL-команд,​ сде-лать запрос к базе и отобразить результаты. Большинство из этого рассказывалось в прошлой статье.
 +
 +<​code>​sql = '​SELECT * FROM Recipes'​
 +cntr = 0
 +for x in
 +cursor.execute(sql):​
 +cntr += 1
 +print '%s %s %s %s' %(str(x[0]).rjust(5),​x[1].ljust(30),​
 +x[2].ljust(20),​x[3].ljust(30))
 +print '​-------------'​
 +self.totalcount = cntr</​code>​
 +
 +Переменная cntr будет счи-тать количество рецептов,​ отображаемых пользователю. Теперь метод завершён. Ниже показан его полный код. Посмо-трите,​ не пропустили ли вы что-нибудь.
 +
 +Учтите,​ что мы используем кортеж,​ который возвращает функция cursor.execute() из ASPW. Мы печатаем pkID в качестве пункта для каждого рецепта. Это позволяет нам выбрать нужный рецепт позже. Когда вы запустите программу,​ то увидите меню, и после выбора пункта 1 получите.
 +
 +Это то, что нам нужно, за тем исключением,​ что если вы запускаете приложение в чём-то вроде Dr.Python, то программа не приостанавливается. Давай-те добавим паузу до нажатия пользователем клавиши,​ чтобы он смог посмотреть на вывод пару секунд. Ещё давайте выве-дем полное количество рецептов из переменной,​ созданной чуть раньше. Добавьте этот код в конец кода 1-го пункта меню:
 +
 +<​code>​print 'Total Recipes - %s' %cbk.totalcount
 +
 +print '​-------------------'​
 +
 +res = raw_input('​Press A Key -> '​)</​code>​
 +
 +Мы на время пропустим опцию №2 (поиск рецепта) и займёмся №3 (вывод одного рецепта). Давайте сначала разберёмся с меню. Мы покажем список всех рецептов,​ как в опции 1, и затем попросим пользователя выбрать один из них. Чтобы убедиться,​ что мы не получим ошибок из-за плохих введённых данных,​ мы будем использовать конструк-цию Try|Except. Напечатаем приглашение пользователю (Select a recipe → ), затем, если он ввёл корректный запрос,​ мы вызовем метод PrintSingleRecipe() нашего класса Cookbook с пара-метром pkID из нашей таблицы Recipe. А если введённые поль-зователем данные — не число, то мы вызовем исключение ValueError, которое отловим при помощи except ValueError: перехват показан справа.
 +
 +Теперь поработаем над методом PrintSingleRecipe. Мы снова начинаем с соединения и курсора,​ затем создаем SQL-запрос. В этом случае мы используем «"​SELECT * FROM Recipes WHERE pkID = %s" % str(which)» где, which — значение,​ которое мы хотим найти. Затем мы красиво оформляем результаты на экране,​ снова из кортежа,​ возвращённого APSW. В этом случае мы используем x как список,​ и затем вставляем все его значения в кортеж. Посколь-ку макет таблицы такой: pkID/name/ servings/​source,​ мы можем использовать x[0],​x[1],​x[2] и x[3] как подробности. Затем нам надо выбрать все записи из таблицы ингредиентов где recipeID (наш ключ из таблицы «recipes») равен переменной pkID, которую мы только что исполь-зовали. Мы проходим циклом по возвращённому кортежу,​ печатаем каждый ингредиент,​ и, наконец,​ получаем инструк-ции из таблицы «instructions» — так же, как мы это делали с таблицей «ingredients». Наконец,​ мы ждём пока пользователь нажмёт клавишу,​ чтобы он мог увидеть рецепт на экране. ​
 +
 +Сейчас у нас закончены два метода из шести. Так что давай-те вернёмся к функции поиска рецепта и снова начнём с меню. К счастью,​ в этот раз нам нужно только вызвать метод поиска в классе,​ так что просто напиши-те вместо команды pass:
 +
 +<​code>​cbk.SearchForRecipe()</​code>​
 +
 +Теперь заполним наш код поиска. В классе Cookbook замените наши заглушки для SearchForRecipe кодом, показан-ным на странице 12.
 +
 +Здесь происходит очень много чего. После создания соединения и курсора,​ мы отображаем поисковое меню. Мы собираемся предложить пользователю три пункта для поиска и один для выхода из функции. Мы можем разрешить пользователю искать по имени рецепта,​ по его содержанию и по слову в списке ингредиентов. Из-за этого мы не можем ис-пользовать функцию отображения,​ созданную только что, и нам понадобится заново писать функции вывода данных на экран. Для первых двух пунктов нам нужно исполь-зовать простой SELECT со словом LIKE. Если мы используем обоз-реватель баз данных,​ например,​ SQLite Database Browser, то наш запрос использует подстановоч-ные символы «%». Таким образом,​ для поиска рецептов,​ содержа-щих «rice» в имени, наш запрос будет выглядеть так:
 +
 +<​code>​SELECT * FROM Recipes WHERE name like '​%rice%'</​code>​
 +
 +Однако,​ поскольку символ «%» — подстановочный в нашей строке,​ мы должны использовать %% в нашем тексте. Хуже того, мы используем подстановочный символ,​ чтобы вставить слово которое ищет пользователь. Поэтому мы должны сделать его «%%%s%%». Извините,​ если ничего не понятно. Третий запрос называется выражением JOIN. Давайте взглянем на него поближе:​
 +
 +<​code>​sql = "​SELECT r.pkid,​r.name,​r.servings,​r.source,​i.ingredients ​
 +FROM Recipes r LEFT JOIN ingredients i ON (r.pkid = i.recipeid) ​
 +WHERE i.ingredients LIKE '​%%%s%%'​ GROUP BY r.pkid"​ %response</​code>​
 +
 +Мы выбираем всё из таблицы рецептов,​ соединяя выбранные элементы с соответствующими им ингредиентами,​ так как из таблицы ingredients выбираются только те ингредиенты,​ у кото-рых recipeID равен pkID из таб-лицы recipe. Затем ищем ингре-диент при помощи LIKE и, нако-нец,​ группируем результаты по параметру pkID из таблицы recipe, чтобы избежать вывода дубликатов. Если вы помните,​ перцы во втором рецепте встре-чаются дважды (лук и чуточку перца),​ один раз зелёные и один раз красные. Это может создать путаницу для наших пользова-телей. Наше меню использует
 +
 +<​code>​searchin = raw_input('​Enter Search Type -> ')
 +
 +if searchin != '​4':</​code>​
 +
 +который означает:​ если значение,​ которое ввёл пользователь не равно 4, то выполняем некото-рые действия,​ а если оно равно 4, то ничего не делаем,​ просто пропускаем эти действия. Обра-тите внимание,​ что, для того чтобы написать «не равно»,​ я использовал «!=» а не «<>​».*В Python 2.x можно использовать оба варианта,​ однако в Python 3.x второй вариант выдаст син-таксическую ошибку. Об измене-ниях в Python 3.x мы больше поговорим в следующих статьях. А сейчас используйте «!=», чтобы облегчить переход к Python 3.x в будущем. Наконец,​ мы снова выводим данные на экран. То, что увидит пользова-тель,​ показано на странице 13.
 +
 +Вы можете видеть,​ как здорово программа показывает результат. Теперь пользователь может вернуться обратно в меню и выбрать пункт 3, чтобы показать любой рецепт,​ кото-рый захочет. Сейчас мы добавим ещё рецептов к нашей базе данных. Мы просто добавим одну строку к нашей функции меню для вызова функции EnterNew:
 +
 +<​code>​cbk.EnterNew()</​code>​
 +
 +Функцию EnterNew() из класса Cookbook нужно завершить кодом, который можно найти по ссылке:​ http://​pastebin.com/​f1d868e63.
 +
 +
 +Начнём с объявления списка «ings», в котором будут ингре-диенты. Затем попросим пользо-вателя ввести название,​ источ-ник и количество порций. После этого начинается цикл, в кото-ром мы по очереди считываем ингредиенты и помещаем их в список «ings». Когда пользова-тель введёт 0, цикл прекратит-ся и начнётся сбор инструкций. После этого мы выводим соб-ранные данные и просим поль-зователя подтвердить их верность перед сохранением. Как и в прошлый раз, мы используем команду INSERT INTO и возвращаемся в меню. Нужно помнить об использова-нии одинарных кавычек в записях. ОБЫЧНО проблем с ингредиентами или инструкц-иями возникать не должно,​ но вот в названии или источнике кавычка встретиться может. Каждую одиночную кавычку нужно экранировать. Делаем мы это при помощи string.replace,​ и по этой причине мы и импор-тировали библиотеку string.*В коде для меню под пунктом 4 нужно добавить код из верхне-го блока на следующей стра-нице.
 +
 +Далее добавьте в метод DeleteRecipe() класса Cookbook код из нижнего блока на следующей странице.
 +
 +Пройдёмся быстро по проце-дуре удаления. Сперва пользо-вателю задаётся вопрос,​ какой рецепт нужно удалить (в самом меню), и этот номер pkID пере-даётся в удаляющий метод. После этого пользователю предлагается подтвердить своё решение. Если пользователь вводит «Y» (string.upper(resp) == '​Y'​),​ то мы создаём sql-запрос для удаления. Заметьте,​ что в этот раз нужно удалить записи из всех трёх таблиц. Можно, конечно,​ произвести удаление только из таблицы рецептов,​ но в таком случае в остальных двух таблицах появятся осиротевшие записи,​ что не есть хорошо. Когда мы удаляем запись из таблицы рецептов,​ мы используем поле pkID. В двух других таблицах нужно использовать поле recipeID.
 +
 +Наконец,​ разберёмся с выво-дом рецептов на экран. Мы соз-дадим ОЧЕНЬ простой HTML-файл и откроем его в браузере,​ чтобы рецепты можно было распечатать оттуда. Именно поэтому нам нужна библиотека webbrowser. Вставьте в меню для шестого действия код, который приведён в начале следующей страницы.
 +
 +Мы снова выводим список всех рецептов и даём возмож-ность пользователю выбрать тот, который нужно распечатать. В класс Cookbook добавим метод, который мы назовём PrintOut. Его код показан в правой верх-ней части следующей страницы.
 +
 +Код начинается с команды fi = open([filename],'​w'​),​ которая создаёт новый файл. После этого мы выбираем из таблицы рецептов информацию,​ которую пишем в файл командой fi.write. Для названия будем использо-вать тэг <​h1></​h1>,​ а для источника и количества порций — тэг <h2>. После этого список ингредиентов оформляется в виде списка <​li></​li>,​ и выво-дятся инструкции. Кроме этого нужно использовать простые запросы к базе данных,​ кото-рые мы уже изучили. В конце мы закрываем файл командой fi.close() и выполняем команду webbrowser.open([filename]) с именем файла, который мы только что создали. После этого пользователь может распеча-тать всё, что ему необходимо.
 +
 +На текущий момент это самое большое наше приложение! На своём сайте я разместил полный исходный код и образец базы данных,​ если вы не скачали её в прошлом месяце. Если вы не хотите всё это печатать,​ или у вас есть проблемы,​ заходите на мой сайт www.thedesignated geek.com и скачивайте код.
 +
 +
 +=====Коды =====
 +
 +<​code>#​!/​usr/​bin/​python
 +#​------------------------------------------------------
 +# Cookbook.py
 +# Created for Beginning Programming Using Python #8
 +# and Full Circle Magazine
 +#​------------------------------------------------------
 +import apsw
 +import string
 +import webbrowser
 +
 +class Cookbook:
 +
 +def Menu():
 +    cbk = Cookbook() # Initialize the class
  
 +Menu()
  
 +def Menu():
 +    cbk = Cookbook() # Initialize the class
 +    loop = True
 +    while loop == True:
 +        print '​==================================================='​
 +        print ' ​              ​RECIPE DATABASE'​
 +        print '​==================================================='​
 +        print ' 1 - Show All Recipes'​
 +        print ' 2 - Search for a recipe'​
 +        print ' 3 - Show a Recipe'​
 +        print ' 4 - Delete a recipe'​
 +        print ' 5 - Add a recipe'​
 +        print ' 6 - Print a recipe'​
 +        print ' 0 - Exit'
 +        print '​==================================================='​
 +        response = raw_input('​Enter a selection -> ')
 +        if response == '​1':​ # Show all recipes
 +            pass
 +        elif response == '​2':​ # Search for a recipe
 +            pass
 +        elif response == '​3':​ # Show a single recipe
 +            pass
 +        elif response == '​4':​ # Delete Recipe
 +            pass
 +        elif response == '​5':​ # Add a recipe
 +            pass
 +        elif response == '​6':​ # Print a recipe
 +            pass
 +        elif response == '​0':​ # Exit the program
 +            print '​Goodbye'​
 +            loop = False
 +        else:
 +            print '​Unrecognized command. ​ Try again.'​
 +            ​
 +/​usr/​bin/​python -u  "/​home/​greg/​python_examples/​APSW/​cookbook/​cookbook_stub.py"​
 +===================================================
 +               ​RECIPE DATABASE
 +===================================================
 +1 - Show All Recipes
 +2 - Search for a recipe
 +3 - Show a Recipe
 +4 - Delete a recipe
 +5 - Add a recipe
 +6 - Print a recipe
 +0 - Exit
 +===================================================
 +Enter a selection -> 
 +            ​
 +</​code>​
  
 --------------------------------------- ---------------------------------------