Программирование на Python 3. Руководство издательство СимволПлюс
![]()
|
246 Глава 5. Модули лишь атрибут функции. В число прочих атрибутов входят имя модуля функции и ее собственное имя. def resize(max_rows, max_columns, char=None): """Изменяет размер сетки, очищает содержимое и изменяет символ фона, если аргумент char не равен None """ assert max_rows > 0 and max_columns > 0, "too small" global _grid, _max_rows, _max_columns, _background_char if char is not None: assert len(char) == 1, _CHAR_ASSERT_TEMPLATE.format(char) _background_char = char _max_rows = max_rows _max_columns = max_columns _grid = [[_background_char for column in range(_max_columns)] for row in range(_max_rows)] Эта функция использует инструкцию assert для обеспечения полити ки выявления ошибок программирования; в первом случае – ошибки при попытке установить размеры сетки меньше, чем 1 ×1. Если символ фона определен, применяется еще одна инструкция assert, чтобы га рантировать, что эта строка содержит точно один символ; в противном случае возбуждается исключение с текстом сообщения из шаблона _CHAR_ASSERT_TEMPLATE , в котором поле {0} замещается полученной стро кой char. К сожалению, мы вынуждены использовать инструкцию global, пото му что внутри этой функции приходится изменять глобальные пере менные. Это как раз тот случай, когда на помощь может прийти объ ектноориентированное программирование, с которым мы познако мимся в главе 6. Содержимое для переменной _grid создается с помощью двух вложенных друг в друга генераторов списков. При ем с применением оператора дублирования списка, та кой как [[char] * columns] * rows, не даст должного ре зультата, потому что внутренние списки будут представ лять собой всего лишь поверхностные копии одного и то го же списка. Вместо генераторов списков можно было бы использовать вложенные циклы for ... in: _grid = [] for row in range(_max_rows): _grid.append([]) for column in range(_max_columns): _grid[1].append(_background_char) Но такой фрагмент сложнее для понимания и гораздо длиннее, чем ге нераторы списков. Поскольку основной целью нашего рассмотрения является реализа ция модуля, мы рассмотрим лишь одну функцию рисования, чтобы Генераторы списков, стр. 142 Модули и пакеты 247 получить представление о том, как это рисование выполняется. Ниже приводится функция add_horizontal_line(), поделенная на две части: def add_horizontal_line(row, column0, column1, char=""): """Добавляет в сетку горизонтальную линию, используя указанный символ >>> add_horizontal_line(8, 20, 25, "=") >>> char_at(8, 20) == char_at(8, 24) == "=" True >>> add_horizontal_line(31, 11, 12) Traceback (most recent call last): RowRangeError """ Строка документирования содержит два теста, один из которых, как предполагается, будет проходить успешно, а другой будет возбуждать исключение. Задавая в доктестах исключения, необходимо вставлять строку «Traceback»; она всегда одна и та же и сообщает модулю doctest , что ожидается исключение. Затем взамен строк с диагностиче скими сообщениями (количество которых может быть разным) следу ет указать многоточие и завершить тест строкой с именем исключе ния, которое ожидается получить. Функция char_at() – одна из тех, что предоставляется этим модулем; она возвращает символ в заданной позиции строки и столбца сетки. assert len(char) == 1, _CHAR_ASSERT_TEMPLATE.format(char) try: for column in range(column0, column1): _grid[row][column] = char except IndexError: if not 0 <= row <= _max_rows: raise RowRangeError() raise ColumnRangeError() Реализация функции начинается с той же проверки длины аргумента char , которая производилась и в функции resize(). Вместо того чтобы явно проверять аргументы с номерами строки и столбцов, функция ра ботает в предположении, что аргументы имеют допустимые значения. Если изза обращения к несуществующей строке или столбцу возбуж дается исключение IndexError, функция перехватывает его и возбуж дает соответствующее исключение, характерное для модуля. Такой стиль программирования соответствует выражению «проще попро сить прощения, чем разрешения» и считается более свойственным программированию на языке Python, чем стиль «осмотрись, прежде чем прыгнуть», при котором проверки выполняются заранее. Реализа ция с опорой на исключения вместо предварительной проверки явля ется более эффективной, когда исключения возникают достаточно редко. (Контрольные инструкции assert мы не относим к стилю «ос мотрись, прежде чем прыгнуть», потому что такие ошибки никогда не 248 Глава 5. Модули должны возникать и они часто убираются из окончательной версии программного кода.) Практически в самом конце модуля, после определения всех функций, имеется единственный вызов функции resize(): resize(_max_rows, _max_columns) Этот вызов инициализирует сетку с размерами по умолчанию (25 ×80), чем обеспечивает безопасное использование модуля в импортирующем программном коде. Без этого вызова импортирующая программа или модуль должны были бы явно вызывать функцию resize() для ини циализации сетки, что вынуждало бы программистов помнить об этом факте и приводило бы к множественным попыткам инициализации. if __name__ == "__main__": import doctest doctest.testmod() Последние три строки в модуле являются обычными для модулей, ис пользующих модуль doctest для выполнения доктестов. Модуль CharGrid имеет один существенный недостаток: он поддержи вает только одну сетку символов. Одно из решений, избавляющих от этого недостатка, заключается в создании коллекции сеток, но это по путно означает, что пользователи модуля должны были бы указывать ключ или индекс требуемой сетки во всех вызовах функций, чтобы идентифицировать сетку, над которой выполняется операция. В слу чаях, когда возникает необходимость иметь несколько экземпляров объекта, лучшее решение состоит в том, чтобы определить класс (соб ственный тип данных) и создать столько экземпляров (объектов дан ного типа), сколько потребуется. Дополнительное преимущество реа лизации на основе класса заключается в том, что мы можем отказать ся от использования инструкции global, сохраняя данные в виде атри бутов (статических элементов) класса. Как создавать классы, мы узнаем в следующей главе. Обзор стандартной библиотеки языка Python Стандартная библиотека языка Python обычно описывается, как «ба тарейки, входящие в комплект поставки», и обеспечивает доступ к ши рокому кругу функциональных возможностей, насчитывая в своем со ставе свыше 200 пакетов и модулей. Этот раздел представляет собой краткий обзор того, что может предложить стандартная библиотека, разделенный на тематические подразделы; из обзора исключены паке ты и модули, представляющие слишком узконаправленный интерес, а также модули, характерные для той или иной платформы. В ходе описания будут демонстрироваться небольшие примеры, чтобы дать представление, что представляют собой те или иные пакеты и модули, Обзор стандартной библиотеки языка Python 249 а перекрестные ссылки будут указывать страницы в книге, где можно найти дополнительные сведения об этих пакетах и модулях. Обработка строк Модуль string содержит ряд полезных констант, таких как string.ascii_ letters и string.hexdigits. Кроме того, он предоставляет класс string. Formatter , на основе которого можно создать подкласс, обеспечивающий собственные средства форматирования. 1 Модуль textwrap может ис пользоваться для расстановки в тексте символов перевода строки, что бы ограничить ширину строк заданным значением, а также для уменьшения отступов. Модуль struct содержит функции упаковывания и рас паковывания чисел, логических значений и строк в/из объекты типа bytes, используя их двоичное представле ние. Это может потребоваться для организации передачи данных между программой и низкоуровневыми библио теками, написанными на языке C. Модули struct и text wrap используются программой convertincidents.py, опи сываемой в главе 7. Модуль difflib содержит классы и методы сравнения последователь ностей, таких как строки, способные воспроизводить результаты срав нения как в стандартных форматах «diff», так и в формате HTML. Самый мощный модуль в языке Python, связанный с обработкой строк, – это модуль re (regular expression – регулярные выражения). Он подробно будет рассматриваться в главе 12. Класс io.StringIO может использоваться для создания объектов, подоб ных строкам, которые ведут себя как текстовые файлы, размещенные в памяти. Это может быть удобно, когда необходимо использовать про граммный код, выполняющий запись в файл, для записи в строку. Пример: класс io.StringIO Выполнить запись в текстовый файл в языке Python можно разными способами. Один из способов состоит в использовании метода write() объекта файла, другой – в использовании функции print() с именован ным аргументом file, указывающим на объект файла, открытый для записи. Например: print("An error message", file=sys.stdout) sys.stdout.write("Another error message\n") 1 Создание подкласса (subclassing), или специализация класса (spezializing), означает создание собственного типа данных (класса) на основе (на базе, based on ) другого класса. Эта тема подробно рассматривается в главе 6. Тип данных bytes , стр. 344 Модуль struct , стр. 349 250 Глава 5. Модули В обоих случаях текст сообщения будет выведен в sys.stdout – объект файла, представляющий «стандартный поток вывода», который обыч но связан с консолью и отличается от sys.stderr, «стандартного потока вывода сообщений об ошибках», только тем, что при работе с послед ним используется небуферизованный вывод. (Интерпретатор автома тически создает и открывает sys.stdin, sys.stdout и sys.stderr при за пуске программы.) По умолчанию функция print() добавляет символ перевода строки, хотя такое ее поведение можно изменить с помощью именованного аргумента end, передав в нем пустую строку. В некоторых ситуациях бывает удобно перехватывать и записывать в строку все то, что предназначено для вывода в файл. Добиться этого можно с помощью класса io.StringIO, экземпляр которого можно ис пользовать как обычный объект файла, который записывает данные в строку. Если при создании объекта io.StringIO была указана началь ная строка, его также можно использовать для чтения, как если бы это был файл. Выполнив инструкцию import io, мы сможем использовать io.StringIO для перехвата любой информации, предназначенной для вывода в объ ект файла, такой как sys.stdout: sys.stdout = io.StringIO() Если эту строку поместить в начало программы, вслед за инструкция ми импорта, но перед любыми инструкциями, использующими sys.stdout , то любой текст, записываемый в sys.stdout, в действитель ности будет передаваться объекту io.StringIO, созданному этой строкой кода и заменившему стандартный объект файла sys.stdout. Теперь при выполнении приведенных выше строк с вызовами print() и sys.std out.write() выводимый ими текст будет попадать в объект io.StringIO, а не на консоль. (Оригинальное значение sys.stdout можно восстано вить в любой момент, для чего достаточно выполнить инструкцию sys.stdout = sys.__stdout__ .) Чтобы получить все строки, записанные в объект io.StringIO, можно вызвать метод io.StringIO.getvalue(). В данном случае вызовом метода sys.stdout.getvalue() можно получить строку, содержащую весь выво дившийся текст. Эту строку можно напечатать, сохранить в файл жур нала или отправить через сетевое соединение, как и любую другую строку. Немного ниже (на стр. 266) мы увидим еще один пример ис пользования класса io.StringIO. Работа с аргументами командной строки Если нам потребуется иметь возможность в программе обрабатывать текст, который может быть получен в результате перенаправления в консоли или находиться в файлах, имена которых перечислены в ко мандной строке, мы можем воспользоваться функцией fileinput.in put() из модуля fileinput. Эта функция выполняет итерации по всем Обзор стандартной библиотеки языка Python 251 строкам, полученным в результате операции перенаправления в кон соли (если они есть), или по всем строкам из файлов, имена которых перечислены в командной строке, как будто это единая последователь ность строк. Модуль может сообщать имя текущего файла и номер строки с помощью функций fileinput.filename() и fileinput.lineno(), а также предоставляет возможность работать с некоторыми типами сжатых файлов. Для работы с параметрами командной строки в стандартной библиоте ке имеется два модуля – optparse и getopt. Модуль getopt популярен, так как он прост в использовании и к тому же давно входит в состав библиотеки. Модуль optparse более новый и обладает более широкими возможностями. Пример: модуль optparse Вспомните описание программы csv2html.py, которое приводилось в главе 2. В упражнениях к этой главе мы предложили расширить программу так, чтобы она могла принимать аргументы командной строки: аргумент «max width», принимающий целое число, и аргумент «format», принимающий строку. В решении (csv2html2_ans.py) для обработки аргументов имеется функция объемом 26 строк. Ниже приводится начало функции main() для csv2html2_opt.py – версии программы, в которой вместо нашей собственной функции для обработки аргументов командной строки используется модуль optparse: def main(): parser = optparse.OptionParser() parser.add_option("w", "maxwidth", dest="maxwidth", type="int", help=("the maximum number of characters that can be " "output to string fields [default: %default]")) parser.add_option("f", "format", dest="format", help=("the format used for outputting numbers " "[default: %default]")) parser.set_defaults(maxwidth=100, format=".0f") opts, args = parser.parse_args() Для обработки аргументов потребовалось всего девять строк про граммного кода плюс строка с инструкцией import optparse. Кроме то го, нам не пришлось явно обрабатывать параметры – h и –– help – эти па раметры обслуживаются самим модулем optparse, который для вывода соответствующего сообщения использует текст из именованных аргу ментов help, где текст «%default» замещается значениями по умолча нию соответствующих параметров. Обратите также внимание, что теперь параметры можно указывать в привычном для системы UNIX стиле – как с помощью коротких, так с помощью длинных имен параметров, начинающихся с символа дефи Пример csv2html. py , стр. 119 252 Глава 5. Модули са. Короткие имена удобны для организации взаимодействий с пользо вателем в консоли, а длинные имена более понятны при использова нии в сценариях командной оболочки. Например, чтобы ограничить максимальную ширину 80 символами, мы можем использовать любой из следующих вариантов определения параметра: – w80 , – w 80 , –– max width=80 или –– maxwidth 80 . После разбора параметров командной стро ки доступ к их значениям можно получить с помощью имен, указы ваемых в аргументах dest, например, opts.maxwidth и opts.format. Все аргументы командной строки, которые не были обработаны (обычно это имена файлов), помещаются в список args. Если в процессе разбора командной строки возникает ошибка, синтак сический анализатор модуля optparse произведет вызов sys.exit(2). Это приведет к завершению программы и возврату операционной сис теме числа 2 в качестве возвращаемого значения программы. Тради ционно значение 2 свидетельствует об ошибке в использовании про граммы, значение 1 используется для индикации об ошибках любого другого типа и значение 0 означает благополучное завершение. Когда функция sys.exit() вызывается без аргументов, операционной системе возвращается значение 0. Математические вычисления и числа В дополнение к встроенным типам чисел int, float и complex библиоте ка предоставляет числовые типы decimal.Decimal и fractions.Fraction. Также в библиотеке имеется три математические библиотеки: math, со держащая стандартные математические функции; cmath – математиче ские функции для работы с комплексными числами; random, содержа щая множество функций генерации случайных чисел. Все эти модули были представлены в главе 2. В модуле numbers имеются различные числовые абстрактные классы языка Python (классы, которые могут наследоваться, но которые не могут использоваться непосредственно). Их удобно использовать для проверки того, что объект, пусть это будет x, принадлежит к любому числовому типу с помощью вызова isinstance(x, numbers.Number) или к какомунибудь определенному типу, например, isinstance(x, num bers.Integral) Специалисты, занимающиеся программированием научных или ин женерных вычислений, найдут полезным пакет NumPy, разрабатывае мый сторонними разработчиками. Этот пакет предоставляет высоко эффективную реализацию многомерных массивов, основных функций линейной алгебры и преобразований Фурье, а также инструменты ин теграции с программным кодом на языках C, C++ и Fortran. Пакет SciPy включает NumPy и дополняет его модулями, предназначенными для выполнения статистических вычислений, обработки сигналов и изображений, модулями с генетическими алгоритмами и многими другими. Оба пакета доступны бесплатно на сайте www.scipy.org. Обзор стандартной библиотеки языка Python 253 Время и дата Модули calendar и datetime содержат функции и классы, предназначен ные для работы с датами и временем. Однако они основаны на абст рактном Григорианском календаре, поэтому они не годятся для рабо ты с датами в календарях, предшествовавших Григорианскому. Дата и время – это очень сложная тема. В разное время и в разных местах использовались разные календари. Продолжительность суток не рав на точно 24 часам, продолжительность года не равна точно 365 дням, существует летнее и зимнее время, а также различные часовые пояса. Класс datetime.datetime (но не в классе datetime.date) предоставляет поддержку работы с часовыми поясами, хотя она не включается по умолчанию. Однако имеются модули сторонних производителей, ко торые с успехом восполняют этот недостаток, например, dateutil (www.labix.org/pythondateutil) и mxDateTime (www.egenix.com/products/ python/mxBase/mxDateTime ). Модуль time используется для работы с отметками времени, которые являются простыми числовыми значениями, представляющими чис ло секунд, прошедших от начала эпохи (19700101T00:00:00 в UNIX). Этот модуль может использоваться для получения на машине отметок текущего времени UTC (Coordinated Universal Time – универсальное глобальное время) или локального времени, учитывающего переход на летнее время, а также для создания строк, представляющих дату, время и дату/время, отформатированных разными способами. Кроме того, он может использоваться для анализа строк, содержащих дату и время. |