Программирование на Python 3. Руководство издательство СимволПлюс
![]()
|
da 4020 License: DA 4020 Seats: 2 Mileage: 97181 Owner: Jonathan Lynn (C)ar (M)ileage (O)wner (N)ew car (S)top server (Q)uit [c]: License [DA 4020]: z This license is not registered Start of license: z No licence starts with Z Start of license: a (1) A04 4HE (2) A37 4791 (3) ABK3035 Enter choice (0 to cancel): 3 License: ABK3035 Seats: 5 Mileage: 17719 Owner: Anthony Jay Упражнения 507 Для решения этого упражнения необходимо будет удалить одну строку и добавить порядка двадцати строк. Потребуется немного поломать голову, чтобы предоставить пользователю возможность выйти или продолжить на каждом этапе. Обязательно протестируй те новые функциональные возможности в разных ситуациях (от сутствие номеров, начинающихся с указанной строки; имеется все го один номер, начинающийся с указанной строки; имеется два или более номеров, начинающихся с указанной строки). Решение при водится в файле car_registration_ans.py. 11 Программирование приложений баз данных Большинство разработчиков программного обеспечения под термином база данных подразумевают СУРБД (система управления реляцион ными базами данных). Для хранения данных эти системы используют таблицы (по своему строению подобные электронным таблицам), стро ки которых соответствуют записям, а столбцы – полям. Манипулиро вание данными, хранящимися в этих таблицах, производится с помо щью инструкций, написанных на языке SQL (Structured Query Langua ge – язык структурированных запросов). Язык Python включает в себя API (Application Programming Interface – прикладной программный интерфейс) для работы с базами данных SQL и обычно распространяет ся с поддержкой базы данных SQLite 3. Существует еще одна разновидность баз данных – DBM (Database Ma nager – система управления базами данных), в которой данные хра нятся в виде произвольного числа элементов ключзначение. В стан дартной библиотеке Python имеется несколько интерфейсов для рабо ты с разными реализациями DBM, включая характерные для операци онной системы UNIX. Базы данных DBM работают по принципу словарей в языке Python, за исключением того, что обычно они хра нятся на диске, а не в памяти, а их ключами и значениями всегда яв ляются объекты типа bytes, размер которых может ограничиваться. В первом разделе этой главы рассматривается модуль shelve, предос тавляющий удобный интерфейс DBM, позволяя использовать строко вые ключи и любые (поддающиеся консервированию) объекты в каче стве значений. Если имеющихся в наличии баз данных DBM и SQLite окажется недос таточно, в каталоге пакетов Python Package Index, pypi.python.org/py pi , можно найти множество пакетов, предназначенных для работы • Базы данных DBM • Базы данных SQL Базы данных DBM 509 с различными базами данных, включая bsddb DBM («Berkeley DB»), объектнореляционные отображения, такие как SQLAlchemy (www. sqlalchemy.org ), и интерфейсы к популярным клиент/серверным ба зам данных, таким как DB2, Informix, Ingres, MySQL, ODBC и Post greSQL. В этой главе мы реализуем две версии программы ведения списка фильмов на дисках DVD, в котором будут храниться название фильма, год выхода, продолжительность в минутах и имя режиссера. Первая версия программы для хранения информации использует базу данных DBM (с помощью модуля shelve), а вторая версия – базу данных SQLite. Обе программы могут также загружать и сохранять информа цию в простом формате XML, обеспечивая возможность, например, экспортировать сведения о фильмах из одной программы и импорти ровать их в другую. Версия, использующая базу данных SQL, обладает немного более широкими возможностями, чем версия, использующая базу данных DBM, и имеет более ясную организацию. Базы данных DBM Модуль shelve представляет собой обертку вокруг DBM, позволяя нам взаимодействовать с базой данных DBM, как с обычным словарем, в котором в качестве ключей допускается использовать только строки, а в качестве значений – объекты, допускающие возможность консер вирования. За кулисами модуль shelve преобразует клю чи и значения в объекты типа bytes и обратно. Поскольку модуль shelve основан на использовании лучшей из доступ ных баз данных DBM, есть вероятность, что файл DBM, сохраненный на одной машине, не будет читаться на другой, если на другой машине отсутствует поддержка той же самой DBM. Наиболее типичное реше ние такой проблемы состоит в том, чтобы обеспечить возможность им порта и экспорта данных в формате XML для файлов, которые должны быть переносимыми с машины на машину. Именно это мы и реализу ем в программе dvdsdbm.py в этом разделе. В качестве ключей мы будем использовать названия фильмов на дис ках DVD, а в качестве значений – кортежи, в которых будут храниться имя режиссера, год и продолжительность фильма. Благодаря модулю shelve нам не придется выполнять какихлибо преобразований дан ных, и мы можем воспринимать объект DBM как обычный словарь. Так как по своей структуре программа похожа на интерактивные про граммы, управляемые с помощью меню, которые мы уже видели ра нее, мы сосредоточимся исключительно на аспектах, связанных с про граммированием DBM. Ниже приводится фрагмент из функции main(), где был опущен программный код, выполняющий обработку меню: Тип данных bytes , стр. 344 510 Глава 11. Программирование приложений баз данных db = None try: db = shelve.open(filename, protocol=pickle.HIGHEST_PROTOCOL) finally: if db is not None: db.close() Здесь открывается (или создается, если он еще не существует) указан ный файл DBM в режиме для чтения и для записи. Значение каждого элемента сохраняется в файле в виде объекта, законсервированного с использованием указанного протокола консервирования. Сущест вующие элементы можно будет прочитать, даже если они были сохра нены с использованием меньшего номера протокола, поскольку интер претатор в состоянии определять правильный номер протокола при чтении законсервированных объектов. В конце функции файл DBM за крывается, в результате происходит очистка внутреннего кэша DBM, производится запись всех изменений на диск и собственно закрытие файла. Программа предоставляет возможность добавлять, редактировать, просматривать, импортировать и экспортировать данные. Мы пропус тим процедуры импортирования и экспортирования данных в формате XML, поскольку они очень похожи на те, что мы рассматривали в гла ве 7. Точно так же мы опустим большую часть программного кода реа лизации пользовательского интерфейса, кроме операции добавления, потому что мы видели его прежде в других контекстах. def add_dvd(db): title = Console.get_string("Title", "title") if not title: return director = Console.get_string("Director", "director") if not director: return year = Console.get_integer("Year", "year", minimum=1896, maximum=datetime.date.today().year) duration = Console.get_integer("Duration (minutes)", "minutes", minimum=0, maximum=60*48) db[title] = (director, year, duration) db.sync() Этой функции, как и любой другой, вызываемой из меню программы, передается в качестве единственного параметра объект DBM (db). Большая часть функции связана с получением данных о диске DVD и только в последней строке производится сохранение элемента ключ значение в файле DBM, где в качестве ключа используется название фильма, а в качестве значения – кортеж с именем режиссера, годом выпуска и продолжительностью (который консервируется средствами модуля shelve). Базы данных DBM 511 Для сохранения непротиворечивости, свойственной языку Python, ме ханизмы DBM предоставляют тот же самый API, что и словари, по этому нам не придется осваивать новый синтаксис помимо функции shelve.open() , которую мы уже видели выше, и метода shelve.Shelf. sync() , который используется для очистки внутреннего кэша модуля shelve и синхронизации данных, находящихся в дисковом файле с по следними изменениями, – в данном случае просто добавляется новый элемент. def edit_dvd(db): old_title = find_dvd(db, "edit") if old_title is None: return title = Console.get_string("Title", "title", old_title) if not title: return director, year, duration = db[old_title] db[title] = (director, year, duration) if title != old_title: del db[old_title] db.sync() Чтобы отредактировать сведения о диске, пользователь должен снача ла выбрать диск, с которым он будет работать. Для этой операции тре буется получить только название, так как названия служат ключами, а значения хранят остальные данные. Необходимая для этого функцио нальность будет востребована и в других местах (например, при удале нии DVD), поэтому мы вынесли ее в отдельную функцию find_dvd(), которую мы рассмотрим следующей. Если диск найден, мы получаем от пользователя изменения, используя существующие значения как значения по умолчанию, чтобы повысить скорость взаимодействия. (Мы опустили большую часть программного кода, выполняющего взаимодействие с пользователем, так как в большинстве своем он ос тался тем же, что используется в функции добавления нового диска.) В конце мы сохраняем данные точно так же, как и в функции добавле ния. Если название не изменялось, эта операция будет иметь эффект перезаписи значения, ассоциированного с ключом, но если название изменилось, будет создана новая пара ключзначение, и в этом случае необходимо удалить оригинальный элемент. def find_dvd(db, message): message = "(Start of) title to " + message while True: matches = [] start = Console.get_string(message, "title") if not start: return None for title in db: if title.lower().startswith(start.lower()): 512 Глава 11. Программирование приложений баз данных matches.append(title) if len(matches) == 0: print("There are no dvds starting with", start) continue elif len(matches) == 1: return matches[0] elif len(matches) > DISPLAY_LIMIT: print("Too many dvds start with {0}; try entering " "more of the title".format(len(matches))) continue else: for i, match in enumerate(sorted(matches, key=str.lower)): print("{0}: {1}".format(i + 1, match)) which = Console.get_integer("Number (or 0 to cancel)", "number", minimum=1, maximum=len(matches)) return matches[which 1] if which != 0 else None Чтобы упростить и максимально ускорить поиск названия, пользова телю предлагается ввести один или несколько начальных символов названия. Получив начало названия, функция выполняет итерации по данным в DBM и создает список найденных совпадений. Если име ется всего одно совпадение, оно возвращается, а если имеется несколь ко совпадений (но не более чем целочисленное значение DISPLAY_LIMIT, которое устанавливается гдето в другом месте программы), то они вы водятся в алфавитном порядке без учета регистра символов, с поряд ковыми номерами перед ними, чтобы пользователь мог сделать выбор простым вводом числа. (Функция Console.get_integer() принимает 0, даже если значение аргумента minimum больше нуля, благодаря чему значение 0 может использоваться как признак отмены операции. Эту особенность поведения можно отключить, для чего достаточно пере дать аргумент allow_zero=False. Мы не можем использовать для отме ны простое нажатие клавиши Enter, потому что ввод пустой строки рас сматривается как ввод значения по умолчанию.) def list_dvds(db): start = "" if len(db) > DISPLAY_LIMIT: start = Console.get_string("List those starting with " "[Enter=all]", "start") print() for title in sorted(db, key=str.lower): if not start or title.lower().startswith(start.lower()): director, year, duration = db[title] print("{0} ({1}) {2} minute{3}, by {4}".format( title, year, duration, Util.s(duration), director)) Вывод списка всех дисков (или только тех, названия которых начина ются с определенной подстроки) реализуется простым обходом всех элементов в базе данных. Базы данных SQL 513 Функция Util.s() определена как s = lambda x: "" if x == 1 else "s"; здесь она возвращает символ «s», если продолжительность фильма превышает одну минуту. def remove_dvd(db): title = find_dvd(db, "remove") if title is None: return ans = Console.get_bool("Remove {0}?".format(title), "no") if ans: del db[title] db.sync() Удаление диска заключается в том, чтобы отыскать диск, который пользователь желает удалить, запросить подтверждение и в случае его получения выполнить удаление элемента из DBM. Теперь мы знаем, как открыть (или создать) файл DBM с помощью мо дуля shelve, как добавлять в него элементы, редактировать элементы, выполнять итерации по элементам и удалять элементы. К сожалению, в нашей базе данных имеется один недостаток. Имена режиссеров могут повторяться, что легко может приводить к несоот ветствиям, например, имя режиссера Danny DeVito для одного фильма может быть введено, как «Danny De Vito», а для другого, как «Danny deVito». Одно из решений этой проблемы состоит в том, чтобы созда вать два файла DBM. Главный – с названиями в качестве ключей и зна чениями (год, продолжительность, идентификатор режиссера) и файл с режиссерами, ключами в котором являются идентификаторы режис серов (например, целые числа), а значениями – имена. Мы устраним этот недостаток в следующем разделе, где версия программы, работаю щей с базой данных SQL, будет использовать две таблицы: в одной бу дет храниться информация о дисках, а во второй – о режиссерах. Базы данных SQL Интерфейсы к наиболее популярным базам данных SQL доступны в виде модулей сторонних разработчиков, а по умолчанию в составе Python поставляется модуль sqlite3 (и база данных SQLite 3), поэтому к созданию приложений баз данных можно приступать сразу же. База данных SQLite – это облегченная база данных SQL, в которой отсутст вуют многие особенности, которые имеются, например, в PostgreSQL, но ее очень удобно использовать для создания прототипов, и во многих случаях предоставляемых ею возможностей оказывается вполне дос таточно. С целью упростить миграцию с одной базы данных на другую, в PEP 249 (Python Database API Specification v2.0) дается спецификация API, которая называется DBAPI 2.0, которой должны следовать интерфей сы к базам данных; модуль sqlite3, к примеру, следует этой специфи 514 Глава 11. Программирование приложений баз данных кации, но не все модули сторонних разработчиков соблюдают ее. Спе цификацией API определяются два основных объекта – объект соеди нения и объект курсора, а API, который они должны поддерживать, приводится в табл. 11.1 и в табл. 11.2. В случае с модулем sqlite3 его объекты соединения и курсора предоставляют множество дополни тельных атрибутов и методов сверх требований, предъявляемых спе цификацией DBAPI 2.0. Версия программы, использующая базу данных SQL, находится в фай ле dvdssql.py. Программа предусматривает хранение имен режиссеров отдельно от информации о дисках, чтобы избежать повторений, и пред лагает дополнительный пункт меню, дающий пользователю получить список режиссеров. Структура двух таблиц показана на рис. 11.1. Раз мер этой программы составляет чуть меньше 300 строк, тогда как раз мер программы dvdsdbm.py из предыдущего раздела составляет чуть меньше 200 строк. Такое различие в основном обусловлено необходи мостью использовать запросы SQL вместо простых операций со слова рем, а также необходимостью создавать таблицы в базе данных при первом запуске программы. Таблица 11.1. Методы объекта соединения в соответствии со спецификацией DBAPI 2.0 Синтаксис Описание db.close() Закрывает соединение с базой данных (представленной объек том db, который возвращается вызовом функции connect()) db.commit() Подтверждает любую, ожидающую подтверждения, транзак цию в базе данных и ничего не делает, если база данных не поддерживает транзакции db.cursor() Возвращает объект курсора базы данных, посредством которо го могут выполняться запросы db.rollback() Откатывает любую, ожидающую подтверждения, транзакцию в базе данных до состояния, в котором база данных находи лась на момент начала транзакции, и ничего не делает, если база данных не поддерживает транзакции dvds id title year duration director_id directors id name Рис. 11.1. Структура базы данных дисков DVD Базы данных SQL 515 Таблица 11.2. Методы и атрибуты объекта курсора в соответствии со спецификацией DBAPI 2.0 Функция main() напоминает одноименную функцию из предыдущей версии программы, только на этот раз она вызывает нашу функцию connect() , чтобы установить соединение с базой данных. def connect(filename): create = not os.path.exists(filename) db = sqlite3.connect(filename) if create: cursor = db.cursor() cursor.execute("CREATE TABLE directors (" "id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, " Синтаксис Описание c.arraysize Число строк (доступно для чтения/записи), которое будет возвращено методом fetchmany(), если аргумент size не определен c.close() Закрывает курсор c – эта операция выполняется автома тически, когда поток управления покидает область види мости курсора c.description Последовательность (только для чтения), представленная кортежем из 7 элементов (name, type_code, display_size, internal_size, precision, scale, null_ok) , описывающая очередной столбец курсора c c.execute(sql, params) Выполняет запрос SQL, находящийся в строке sql, заме щая каждый символзаполнитель соответствующим па раметром из последовательности или отображения params , если имеется c.executemany (sql, seq_of_params) Выполняет запрос SQL по одному разу для каждого эле мента в последовательности последовательностей или отображений seq_of_params; этот метод не должен исполь зоваться для выполнения операций, создающих наборы результатов (таких как инструкции SELECT) c.fetchall() Возвращает последовательность всех строк, которые еще не были извлечены (это могут быть все строки) c.fetchmany(size) Возвращает последовательность строк (каждая строка са ма по себе является последовательностью); по умолчанию аргумент size принимает значение c.arraysize c.fetchone() Возвращает следующую строку из набора результатов, полученных в результате запроса, или None, если все ре зультаты были исчерпаны. Возбуждает исключение, если набор результатов отсутствует c.rowcount Доступный только для чтения счетчик строк для послед ней операции (такой как SELECT, INSERT, UPDATE или DELETE) или –1, если счетчик недоступен или не имеет смысла |