Программирование на Python 3. Руководство издательство СимволПлюс
Скачать 3.74 Mb.
|
393 e ENCODING, encoding=ENCODING кодировка (ASCII..UTF32) [по умолчанию: UTF8] конец перевода) С помощью этой программы при наличии файла, созданного объек том BinaryRecordFile, в котором хранятся записи в формате " 10байтовая строка байтов), установив размер блока в соответствии с размером одной записи (15 байтов, включая байт состояния), можно было бы получить ясное представление о содержимом фай ла. Например: xdump.py b15 test.dat Block Bytes UTF8 characters 00000000 02000000 00416C70 68610000 000000 .....Alpha..... 00000001 01140000 00427261 766F0000 000000 .....Bravo..... 00000002 02280000 00436861 726C6965 000000 .(...Charlie... 00000003 023C0000 0044656C 74610000 000000 .<...Delta..... 00000004 02500000 00456368 6F000000 000000 .P...Echo...... Каждый байт представлен двумя шестнадцатеричными цифрами; пробел между группами из четырех байтов (между группами из восьми шестнадцатеричных цифр) добавляется исключительно ра ди удобочитаемости. В этом примере видно, что вторая запись («Bravo») была удалена, потому что ее байт состояния имеет значе ние 0x01, а не 0x02, используемое для обозначения непустых и не удаленных записей. Для обработки параметров командной строки используйте модуль optparse . (Указав «тип» параметра, можно заставить модуль opt parse выполнять преобразование значения параметра с размером блока из строкового представления в целочисленное.) Может ока заться совсем непросто правильно выводить строку заголовка для произвольно заданного размера блока и строки символов в послед нем блоке, поэтому обязательно проверьте работу программы с раз ными размерами блоков (например, 8, 9, 10, …, 40). Кроме того, не забывайте, что в файлах переменной длины последний блок может оказаться коротким. Для обозначения непечатаемых символов ис пользуйте точку, как показано в примере. Программу можно уместить менее чем в 70 строк, распределенных на две функции. Пример решения приводится в файле xdump.py. 8 Усовершенствованные приемы программирования В этой главе мы рассмотрим широкий диапазон различных приемов программирования и представим множество дополнительных, усовер шенствованных синтаксических конструкций, поддерживаемых язы ком Python. Некоторые сведения, приводимые в этой главе, отличают ся высокой сложностью, но имейте в виду, что большая часть дополни тельных приемов используется нечасто и при первом прочтении вы можете лишь ознакомиться с ними, чтобы получить о них общее пред ставление, и перечитать материал внимательнее, когда в этом возник нет необходимость. В первом разделе главы более подробно рассматриваются особенности процедурного программирования на языке Python. Раздел начинается с демонстрации решения уже описанных ранее задач новым способом и затем возвращается к теме генераторов, которая кратко рассматри валась в главе 6. Затем в этом разделе рассматриваются приемы дина мического программирования – загрузка модулей по имени во время выполнения и выполнение произвольного программного кода. После этого изложение возвращается к теме локальных (вложенных) функ ций, которая расширена описанием использования ключевого слова nonlocal и рекурсивных функций. Ранее мы видели, как можно ис пользовать предопределенные декораторы языка Python, а в этом раз деле мы узнаем, как создавать собственные декораторы. Завершается раздел обсуждением аннотаций функций. Во втором разделе содержатся новые сведения об объектноориентиро ванном программировании. Он начинается с представления механиз ма слотов (__slots__), предназначенного для уменьшения объема памя • Улучшенные приемы процедурного программирования • Улучшенные приемы объектноориентированного программирования • Функциональное программирование Улучшенные приемы процедурного программирования 395 ти, занимаемой каждым объектом. Затем он демонстрирует, как орга низовать доступ к атрибутам без использования свойств. В этом разде ле также будут представлены функторы (объекты, которые могут вызываться подобно функциям) и менеджеры контекста – они исполь зуются совместно с ключевым словом with и во многих случаях (напри мер, при работе с файлами) могут использоваться вместо конструкций try ... except ... finally , замещая их более простыми конструкция ми try ... except. В этом разделе также будет показано, как создавать свои собственные менеджеры контекста, и будут представлены допол нительные улучшенные особенности объектноориентированного про граммирования, включая декораторы классов, абстрактные базовые классы, множественное наследование и метаклассы. В третьем разделе вводится несколько фундаментальных понятий функционального программирования и представлены некоторые по лезные функции из модулей functools, itertools и operator. В этом раз деле также будет показано, как использовать возможность частичной подготовки функций для упрощения программного кода. Предыдущие главы предоставили нам «комплект стандартных инстру ментов языка Python». Эта глава берет все, что мы уже рассматривали, и превращает в «комплект усовершенствованных инструментов», в ко тором присутствуют все прежние инструменты (приемы программиро вания и синтаксические конструкции) плюс множество новых, которые могут сделать программирование проще, легче и эффективнее. Некото рые инструменты являются взаимозаменяемыми, например, некоторые задачи можно решать с помощью декораторов классов или метаклассов, тогда как другие, такие как дескрипторы, при разных способах исполь зования дают различные результаты. Некоторые описываемые здесь ин струменты, такие как менеджеры контекста, мы будем использовать по стоянно, другие – время от времени, только в определенных ситуациях, когда они способны предложить лучшее решение. Улучшенные приемы процедурного программирования Большая часть этого раздела посвящена дополнительным возможно стям, касающимся процедурного программирования и функций, но самый первый подраздел в этом отношении стоит особняком, так как в нем представлены полезные приемы программирования, основанные на уже имеющихся у нас знаниях, без введения новых синтаксиче ских конструкций. Ветвление с использованием словарей Как уже отмечалось ранее, функции – это объекты, как и все осталь ное в языке Python, а имена функций – это ссылки на объекты, кото 396 Глава 8. Усовершенствованные приемы программирования рые указывают на функции. Если записать имя функции без скобок, интерпретатор будет считать, что подразумевается ссылка на объект, благодаря чему имеется возможность передавать такие ссылки на объекты точно так же, как ссылки на любые другие объекты. Этот факт можно использовать для замены условных инструкций if, со держащих множественные предложения elif, единственным вызовом функции. В главе 11 мы будем рассматривать интерактивную консольную про грамму с именем dvdsdbm.py, которая имеет следующее меню: (A)dd (E)dit (L)ist (R)emove (I)mport e(X)port (Q)uit В программе имеется функция, которая получает символ, выбранный пользователем, и возвращает только допустимый символ, в данном случае «a», «e», «l», «r», «i», «x» или «q». Ниже приводятся два экви валентных фрагмента программного кода, которые, в зависимости от сделанного выбора, вызывают соответствующую функцию: if action == "a": add_dvd(db) elif action == "e": edit_dvd(db) elif action == "l": list_dvds(db) elif action == "r": remove_dvd(db) elif action == "i": import_(db) elif action == "x": functions = dict(a=add_dvd, e=edit_dvd, export(db) l=list_dvds, r=remove_dvd, elif action == "q": i=import_, x=export, q=quit) quit(db) functions[action](db) Выбор, сделанный пользователем, хранится в виде строки из одного символа в переменной action, а ссылка на используемую базу данных – в переменной db. В имя функции import_() включен завершающий символ подчеркивания, чтобы отличить ее от инструкции import. Фрагмент справа создает словарь, ключами которого являются допус тимые варианты выбора, а значениями – ссылки на функции. Вторая инструкция в этом фрагменте извлекает ссылку на функцию, соответ ствующую выбранному действию, и вызывает ее с помощью оператора вызова (), передавая аргумент db. Фрагмент справа не только короче, но и легко масштабируется (в словаре может быть гораздо больше эле ментов) без потерь производительности, в отличие от фрагмента слева, скорость работы которого зависит от того, сколько условий в предло жениях elif придется проверить, прежде чем будет найдена требуемая функция. Улучшенные приемы процедурного программирования 397 Этот прием уже использовался в программе convertincidents.py из пре дыдущей главы – в методе import_(), как показано в выдержке из этого метода ниже: call = {(".aix", "dom"): self.import_xml_dom, (".aix", "etree"): self.import_xml_etree, (".aix", "sax"): self.import_xml_sax, (".ait", "manual"): self.import_text_manual, (".ait", "regex"): self.import_text_regex, (".aib", None): self.import_binary, (".aip", None): self.import_pickle} result = call[extension, reader](filename) Всего метод содержит 13 строк программного кода. Значение extension определяется в самом методе, а значение reader передается вызываю щей программой. Ключами словаря являются двухэлементные корте жи, а значениями – методы. Если бы в этом случае использовались ин струкции if, реализация метода выросла бы до 22 строк, а понятие масштабируемости к реализации было бы вообще неприменимо. Выражениягенераторы и функциигенераторы В главе 6 мы познакомились с функциямигенераторами и методамигенераторами. Кроме того, существует воз можность создавать еще и выражениягенераторы. Син таксически они очень похожи на генераторы списков, единственное отличие состоит в том, что они заключают ся не в квадратные скобки, а в круглые. Ниже приводит ся синтаксис выраженийгенераторов в общем виде: (expression for item in iterable) (expression for item in iterable if condition) В предыдущей главе мы создавали методыгенераторы, используя ин струкцию yield. Ниже приводятся два эквивалентных фрагмента про граммного кода, демонстрирующие, как простой цикл for ... in, со держащий выражение yield, можно превратить в генератор: def items_in_key_order(d): def items_in_key_order(d): for key in sorted(d): return ((key, d[key]) yield key, d[key] for key in sorted(d)) Обе функции возвращают генератор, который воспроизводит список элементов «ключзначение» для заданного словаря. Если потребуется получить сразу весь список элементов, возвращаемый функциями ге нератор можно передать функции list() или tuple(), или, наоборот, выполнять итерации через генератор, извлекая элементы по мере не обходимости. Генераторы представляют собой средство выполнения отложенных вычислений, то есть значения вычисляются, только когда они дейст Функции генераторы, стр. 324 398 Глава 8. Усовершенствованные приемы программирования вительно необходимы. Такой подход может оказаться гораздо эффек тивнее, чем, например, вычисление содержимого огромного списка за один раз. Некоторые генераторы могут воспроизводить столько значе ний, сколько потребуется – без ограничения сверху. Например: def quarters(next_quarter=0.0): while True: yield next_quarter next_quarter += 0.25 Эта функция будет возвращать числа 0.0, 0.25, 0.5 и т. д. до бесконеч ности. Ниже показано, как можно было бы использовать такой гене ратор: result = [] for x in quarters(): result.append(x) if x >= 1.0: break Применение инструкции break здесь очень существенно – без нее цикл for ... in был бы бесконечным. После выхода из цикла переменная result будет содержать список [0.0, 0.25, 0.5, 0.75, 1.0]. Всякий раз, когда вызывается функция quarters(), она возвращает ге нератор, начинающий счет с 0.0 и на каждом шаге увеличивающий значение на 0.25, но как быть, если требуется, чтобы генератор начал воспроизводить последовательность с текущего значения? Сделать это можно, передав требуемое значение в генератор, как показано в новой версии функциигенератора: def quarters(next_quarter=0.0): while True: received = (yield next_quarter) if received is None: next_quarter += 0.25 else: next_quarter = received Выражение yield поочередно возвращает каждое значение вызываю щей программе. Кроме того, если будет вызван метод send() генерато ра, то переданное значение будет принято функциейгенератором в ка честве результата выражения yield. Ниже показано, как можно ис пользовать новую функциюгенератор: result = [] generator = quarters() while len(result) < 5: x = next(generator) if abs(x 0.5) < sys.float_info.epsilon: x = generator.send(1.0) result.append(x) Улучшенные приемы процедурного программирования 399 Здесь создается переменная, хранящая ссылку на генератор, и вызы вается встроенная функция next(), которая извлекает очередной эле мент из указанного ей генератора. (Того же эффекта можно было бы достичь вызовом специального метода __next__() генератора, в данном случае следующим образом: x = generator.__next__().) Если значение равно 0.5, генератору передается значение 1.0 (которое немедленно возвращается обратно). На этот раз в результате будет получен список [0.0, 0.25, 1.0, 1.25, 1.5] В следующем подразделе мы рассмотрим программу magicnum bers.py , которая обрабатывает файлы, полученные в виде аргументов командной строки. К сожалению, в операционной системе Windows командная оболочка (cmd.exe) не обеспечивает расширения шаблон ных символов в именах файлов (также называется подстановкой имен файлов , file globbing), поэтому, если программу запустить в Windows с аргументом *.*, в список sys.argv попадет не список всех файлов в те кущем каталоге, а сам текст «*.*». Эта проблема была решена за счет создания двух различных функций get_files(), одной – для Windows и другой – для UNIX. В обеих функциях используются генераторы, как показано ниже: if sys.platform.startswith("win"): def get_files(names): for name in names: if os.path.isfile(name): yield name else: for file in glob.iglob(name): if not os.path.isfile(file): continue yield file else: def get_files(names): return (file for file in names if os.path.isfile(file)) В обоих случаях функция ожидает получить в виде аргумента список имен файлов, например, sys.argv[1:]. В Windows функция выполняет обход всех имен в списке. Если очеред ное имя является именем файла, функция возвращает его; если это не имя файла (обычно имя каталога), то используется функция glob.ig lob() из модуля glob, возвращающая итератор имен файлов, соответст вующих указанному имени после расширения шаблонных символов. Для обычных имен, таких как autoexec.bat, возвращается итератор, воспроизводящий единственный элемент (имя), а для имен, содержа щих шаблонные символы, таких как *.txt, возвращается итератор, ко торый воспроизводит все имена файлов, соответствующие шаблону (в данном случае – все имена файлов с расширением .txt). (Существует также функция glob.glob(), возвращающая не итератор, а список.) 400 Глава 8. Усовершенствованные приемы программирования В операционной системе UNIX подстановка на место шаблонных сим волов выполняется самой командной оболочкой, поэтому функция просто возвращает генератор всех полученных имен файлов. 1 Функциигенераторы могут использоваться для создания сопрограмм – функций, котрые имеют несколько точек входа и выхода (выражений yield ) и которые могут приостанавливаться и возобновляться в опреде ленных точках (опять же в местах, где находятся выражения yield). Сопрограммы часто используются в качестве более простой и с мень шими накладными расходами альтернативы многопоточному про граммированию. В каталоге пакетов Python Package Index (pypi.py thon.org/pypi ) имеется несколько модулей сопрограмм. Динамическое выполнение программного кода и динамическое импортирование В некоторых случаях бывает проще написать программный код, кото рый генерирует другой программный код, чем писать напрямую весь необходимый программный код. А в некоторых случаях бывает удоб нее дать пользователю возможность вводить свой программный код (например, функции в электронных таблицах) и позволить интерпре татору Python выполнить введенный программный код, чем тратить время на создание синтаксического анализатора; хотя подобная воз можность выполнять произвольный программный код влечет за собой угрозу безопасности. Другой случай, когда может пригодиться воз можность динамического выполнения программного кода, – поддерж ка архитектуры расширений, добавляющих в программу новые функ циональные возможности. Недостаток расширяемой архитектуры со стоит в том, что не вся необходимая функциональность встроена в про грамму непосредственно (что может осложнить развертывание программного продукта и добавить риск потери отдельных расшире ний). Но в этом есть и свои преимущества, так как расширения могут обновляться по отдельности и могут поставляться отдельно от про граммы, – например, пополняя ее новыми возможностями, которые не были предусмотрены первоначально. Динамическое выполнение программного кода Самый простой способ выполнить выражение заключается в использо вании встроенной функции eval(), с которой впервые мы встретились в главе 6. Например: x = eval("(2 ** 31) 1") # x == 2147483647 1 Функция glob.glob() не обладает такими широкими возможностями, как, скажем, командная оболочка bash в UNIX, – хотя функция и поддержива ет шаблонные символы *, ? и синтаксическую конструкцию [], но она не поддерживает синтаксис {}. |