Главная страница
Навигация по странице:

  • Модуль TextUtil

  • Программирование на Python 3. Руководство издательство СимволПлюс


    Скачать 3.74 Mb.
    НазваниеРуководство издательство СимволПлюс
    Дата10.11.2022
    Размер3.74 Mb.
    Формат файлаpdf
    Имя файлаПрограммирование на Python 3.pdf
    ТипРуководство
    #780382
    страница28 из 74
    1   ...   24   25   26   27   28   29   30   31   ...   74

    Собственные модули
    Поскольку модули – это всего лишь файлы с расширением .py, они соз
    даются без особых формальностей. В этом разделе мы рассмотрим два нестандартных модуля. Первый модуль, TextUtil (в файле TextUtil.py),
    содержит всего три функции: is_balanced(), возвращающую True, если в строке, переданной ей, соблюдена парность скобок разных типов; shor
    ten()
    (продемонстрированную ранее, на стр. 209) и simplify(), способ
    ную удалять лишние пробелы и другие символы из строки. При рассмот
    рении этого модуля мы также покажем, как использовать программный код в строках документирования в качестве модульных тестов.
    Второй модуль, CharGrid (в файле CharGrid.py), содержит сетку симво
    лов и позволяет «рисовать» линии, прямоугольники и текст в сетке и отображать сетку в консоли. Этот модуль демонстрирует некоторые приемы, с которыми мы не сталкивались ранее, и является более ти
    пичным примером более крупных и более сложных модулей.
    Модуль TextUtil
    Структура этого модуля (и большинства других модулей) немного от
    личается от структуры программы. Первая строка модуля – это строка
    «shebang», вслед за которой следует несколько строк комментариев
    (обычно упоминание об авторских правах и информация о лицензион
    ном соглашении). Затем, как правило, следует строка в тройных ка

    238
    Глава 5. Модули вычках, в которой дается краткий обзор содержимого модуля, часто с несколькими примерами использования – это строка документиро
    вания модуля. Ниже приводится начало файла TextUtil.py (правда, без комментария с упоминанием о лицензионном соглашении):
    #!/usr/bin/env python3
    # Copyright (c) 2008 Qtrac Ltd. All rights reserved.
    """
    Этот модуль предоставляет несколько функций манипулирования строками.
    >>> is_balanced("(Python (is (not (lisp))))")
    True
    >>> shorten("The Crossing", 10)
    'The Cro...'
    >>> simplify(" some text with spurious whitespace ")
    'some text with spurious whitespace'
    """
    import string
    Строку документирования этого модуля можно сделать доступной программам (или другим модулям), если импортировать модуль как
    TextUtil.__doc__
    . Вслед за строкой документирования следуют инст
    рукции импортирования, в данном случае – единственная инструк
    ция, и далее находится остальная часть модуля.
    Мы уже видели полный текст функции shorten(), поэто
    му не будем повторно воспроизводить его здесь. И по
    скольку в настоящее время нас интересуют модули, а не функции, мы продемонстрируем только программный код функции is_balanced(), хотя функцию simplify() при
    ведем полностью, вместе со строкой документирования.
    Ниже приводится функция simplify(), разбитая на две части:
    def simplify(text, whitespace=string.whitespace, delete=""):
    r"""Возвращает текст, из которого удалены лишние пробелы.
    Параметр whitespace  это строка символов, каждый из которых считается символом пробела.Если параметр delete не пустой, он должен содержать строку, и тогда все символы, входящие в состав строки delete, будут удалены из строки результата.
    >>> simplify(" this and\n that\t too")
    'this and that too'
    >>> simplify(" Washington D.C.\n")
    'Washington D.C.'
    >>> simplify(" Washington D.C.\n", delete=",;:.")
    'Washington DC'
    >>> simplify(" disemvoweled ", delete="aeiou")
    'dsmvwld'
    """
    Функция
    shorten()
    , стр. 209

    Модули и пакеты
    239
    Вслед за строкой с инструкцией def следует строка доку
    ментирования функции, первая строка которой в соот
    ветствии с соглашениями является коротким одностроч
    ным описанием; за ней следуют пустая строка, более подробное описание и затем несколько примеров, запи
    санных так, как если бы они выполнялись в интерактив
    ной оболочке. Поскольку в строке документирования присутствуют кавычки, мы должны либо экранировать их символом обратного слеша, либо, как в данном слу
    чае, использовать «сырую» строку в тройных кавычках.
    result = []
    word = ""
    for char in text:
    if char in delete:
    continue elif char in whitespace:
    if word:
    result.append(word)
    word = ""
    else:
    word += char if word:
    result.append(word)
    return " ".join(result)
    Список result используется для хранения «слов» – строк, не имеющих пробельных или удаляемых символов. Внутри функции выполняются итерации по символам в параметре text, с пропуском удаляемых сим
    волов. Если встречается пробельный символ и в переменной word со
    держится хотя бы один символ, полученное слово добавляется в спи
    сок result, после чего в переменную word записывается пустая строка;
    в противном случае пробельный символ пропускается. Любые другие символы добавляются к создаваемому слову. В конце функция возвра
    щает единственную строку, содержащую все слова из списка result,
    разделенные пробелом.
    Функция is_balanced() следует тому же шаблону: за строкой с инст
    рукцией def находится строка документирования с коротким одно
    строчным описанием, пустой строкой, полным описанием и несколь
    кими примерами, вслед за которой идет сам программный код. Ниже приводится только программный код функции, без строки документи
    рования:
    def is_balanced(text, brackets="()[]{}<>"):
    counts = {}
    left_for_right = {}
    for left, right in zip(brackets[::2], brackets[1::2]):
    assert left != right, "the bracket characters must differ"
    counts[left] = 0
    «Сырые» строки, стр. 85

    240
    Глава 5. Модули left_for_right[right] = left for c in text:
    if c in counts:
    counts[c] += 1
    elif c in left_for_right:
    left = left_for_right[c]
    if counts[left] == 0:
    return False counts[left] = 1
    return not any(counts.values())
    Функция создает два словаря. Ключами словаря counts являются сим
    волы открывающих скобок («(», «[», «{» и «<»), а значениями – целые числа. Ключами словаря left_for_right являются символы закрываю
    щих скобок («)», «]», «}» и «>»), а значениями – соответствующие им символы открывающих скобок. Сразу после создания словарей функ
    ция начинает выполнять итерации по символам в параметре text. Вся
    кий раз, когда встречается символ открывающей скобки, соответст
    вующее ему значение в словаре count увеличивается на 1. Точно так же, когда встречается символ закрывающей скобки, функция опреде
    ляет соответствующий ему символ открывающей скобки. Если счет
    чик для этого символа равен 0, это означает, что была встречена лиш
    няя закрывающая скобка, поэтому можно сразу же возвращать False;
    в противном случае счетчик уменьшается на 1. По окончании просмот
    ра текста, если все открывающие скобки имеют парные им закрываю
    щие скобки, все счетчики должны быть равны 0, поэтому, если хотя бы один счетчик не равен 0, функция возвращает False; в противном случае она возвращает True.
    До этого момента рассматриваемый модуль ничем не отличался от лю
    бого другого файла с расширением .py. Если бы файл TextUtil.py был программой, вполне возможно, что в нем присутствовали бы и другие функции, а в конце стоял бы единственный вызов одной из этих функ
    ций, запускающий обработку. Но так как это модуль, который предна
    значен для того, чтобы его импортировали, одних определений функ
    ций вполне достаточно. Теперь любая программа или модуль смогут импортировать модуль TextUtil и использовать его:
    import TextUtil text = " a puzzling conundrum "
    text = TextUtil.simplify(text) # text == 'a puzzling conundrum'
    Если нам потребуется сделать модуль TextUtil доступным определенной программе, нам достаточно будет поместить файл TextUtil.py в один ка
    талог с программой. Сделать файл TextUtil.py доступным для всех на
    ших программ можно несколькими способами. Первый состоит в том,
    чтобы поместить модуль в подкаталог sitepackages, находящийся в де
    реве каталогов, куда был установлен Python (в системе Windows это обычно каталог C:\Python30\Lib\sitepackages, но в Mac OS X и других

    Модули и пакеты
    241
    версиях UNIX путь к этому каталогу будет иным). Данный каталог находится в пути поиска Python, поэтому интерпретатор всегда будет отыскивать любые модули, находящиеся здесь. Второй способ заклю
    чается в создании каталога, специально предназначенного для наших собственных модулей, которые мы предполагаем использовать в на
    ших программах, и добавлении пути к этому каталогу в переменную окружения PYTHONPATH. Третий способ состоит в том, чтобы поместить модуль в локальный подкаталог sitepackages – каталог %APPDATA%/
    Python/Python30/sitepackages
    в Windows, и

    /.local/lib/python3.0/site
    packages
    в UNIX (включая Mac OS X), который находится в пути поис
    ка Python. Второй и третий подходы предпочтительнее, так как в этих двух случаях ваш программный код будет храниться отдельно от офи
    циальной версии Python.
    Иметь модуль TextUtil само по себе уже неплохо, но если в конечном счете предполагается использовать его во множестве программ, то на
    верняка хотелось бы пребывать в уверенности, что он работает именно так, как заявлено. Один из самых простых способов состоит в том, что
    бы выполнить примеры, которые приводятся в строках документиро
    вания, и убедиться, что они дают ожидаемые результаты. Сделать это можно, добавив всего три строки в конец файла модуля:
    if __name__ == "__main__":
    import doctest doctest.testmod()
    Всякий раз, когда выполняется импортирование модуля, интерпрета
    тор создает для него переменную с именем __name__ и сохраняет имя модуля в этой переменной. Имя модуля – это просто имя файла .py,
    только без расширения. Поэтому в данном случае, когда модуль будет импортироваться, переменная __name__ получит значение "TextUtil"
    и условие в инструкции if не будет соответствовать True, то есть две по
    следние строки выполняться не будут. Это означает, что последние три строки ничего не меняют, когда модуль импортируется.
    Всякий раз, когда файл с расширением .py запускается как програм
    ма, интерпретатор Python создает в программе переменную с именем
    __name__
    и записывает в нее строку "__main__". То есть, если мы запус
    тим
    файл TextUtil.py как программу, интерпретатор запишет в пере
    менную __name__ строку "__main__", условие в инструкции if вернет True и две последние строки будут выполнены.
    Функция doctest.testmod() с помощью механизма интроспекции Py
    thon выявляет все функции в модуле и их строки документирования,
    после чего пытается выполнить все фрагменты программного кода, ко
    торые приводятся в строках документирования. При запуске модуля таким способом вывод на экране появится только при наличии ошибок.
    Сначала это может привести в замешательство, так как создается впе
    чатление, будто вообще ничего не происходит; но если интерпретатору

    242
    Глава 5. Модули передать ключ командной строки

    v
    , на экране может появиться при
    мерно следующее:
    Trying:
    is_balanced("(Python (is (not (lisp))))")
    Expecting:
    True ok
    Trying:
    simplify(" disemvoweled ", delete="aeiou")
    Expecting:
    'dsmvwld'
    ok
    4 items passed all tests:
    3 tests in __main__
    5 tests in __main__.is_balanced
    3 tests in __main__.shorten
    4 tests in __main__.simplify
    15 tests in 4 items.
    15 passed and 0 failed.
    Test passed.
    Мы использовали многоточия, чтобы показать, что было опущено мно
    жество строк. Если в модуле имеются функции (или классы, или мето
    ды), не имеющие тестов, при запуске интерпретатора с ключом

    v они будут перечислены. Обратите внимание, что модуль doctest обнару
    жил тесты как в строке документирования модуля, так и в строках до
    кументирования функций.
    Примеры в строках документирования, которые могут выполняться как тесты, называют доктестами (doctests). Обратите внимание, что при написании доктестов мы вызываем функцию simplify(), не ис
    пользуя полное квалифицированное имя (поскольку доктесты нахо
    дятся непосредственно в самом модуле). За пределами модуля, после выполнения инструкции import TextUtil, мы должны использовать квалифицированные имена, например, TextUtil.is_balanced().
    В следующем подразделе мы увидим, как реализовать более полноцен
    ные тесты – в частности, проверку случаев, когда ожидаются отказы, –
    например, когда неверные входные данные должны приводить к воз
    буждению исключения. Мы также рассмотрим некоторые другие про
    блемы, связанные с созданием модулей, включая инициализацию мо
    дуля, учет различий между платформами и обеспечение возможности импортировать программами или модулями, при использовании син
    таксиса from module import *, только тех объектов, которые мы хотим сделать общедоступными.

    Модули и пакеты
    243
    Модуль CharGrid
    Модуль CharGrid хранит в памяти сетку символов. Он предоставляет функции, позволяющие «рисовать» в сетке линии, прямоугольники и текст, а также функции отображения сетки в консоли. Ниже приво
    дятся доктесты из строки документирования модуля:
    >>> resize(14, 50)
    >>> add_rectangle(0, 0, *get_size())
    >>> add_vertical_line(5, 10, 13)
    >>> add_vertical_line(2, 9, 12, "!")
    >>> add_horizontal_line(3, 10, 20, "+")
    >>> add_rectangle(0, 0, 5, 5, "%")
    >>> add_rectangle(5, 7, 12, 40, "#", True)
    >>> add_rectangle(7, 9, 10, 38, " ")
    >>> add_text(8, 10, "This is the CharGrid module")
    >>> add_text(1, 32, "Pleasantville", "@")
    >>> add_rectangle(6, 42, 11, 46, fill=True)
    >>> render(False)
    Функция CharGrid.add_rectangle() принимает четыре обязательных ар
    гумента: номер строки и номер столбца верхнего левого угла, а также номер строки и номер столбца правого нижнего угла. В пятом необяза
    тельном аргументе можно определить символ, который будет исполь
    зоваться для рисования сторон прямоугольника, а в шестом аргументе типа Boolean можно указать, следует ли выполнять заливку прямо
    угольника (тем же самым символом, который используется для рисо
    вания сторон). В первом вызове третий и четвертый аргументы переда
    ются путем распаковывания двухэлементного кортежа (ширина и вы
    сота), который возвращает функция CharGrid.get_size().
    По умолчанию, прежде чем вывести содержимое сетки, функция Char
    Grid.render()
    очищает экран, но чтобы предотвратить это, ей можно передать значение False, что и было сделано в данном случае. Ниже приводится изображение сетки, полученной в результате выполнения доктестов:
    %%%%%*********************************************
    % % @@@@@@@@@@@@@@@ *
    % % @Pleasantville@ *
    % % ++++++++++ @@@@@@@@@@@@@@@ *
    %%%%% *
    * ################################# *
    * ################################# **** *
    * ## ## **** *
    * ## This is the CharGrid module ## **** *
    * ! ## ## **** *
    * ! | ################################# **** *
    * ! | ################################# *
    * | *
    **************************************************

    244
    Глава 5. Модули
    Модуль CharGrid начинается точно так же, как и модуль TextUtil –
    со строки «shebang», с упоминания об авторских правах и лицензион
    ном соглашении. В строке документирования модуля приводится его описание, вслед за которым находятся доктесты, упомянутые выше.
    Следующий ниже программный код начинается двумя инструкциями импорта: одна импортирует модуль sys, а другая – модуль subprocess.
    Модуль subprocess подробно будет рассматриваться в главе 9.
    В модуле используется две тактики обработки ошибок. Некоторые функции имеют параметр типа char, то есть фактически строку, содер
    жащую единственный символ. Нарушение этого требования рассмат
    ривается как фатальная ошибка программирования, поэтому для про
    верки длины аргументов используется инструкция assert. Передача номеров строк и столбцов со значениями, выходящими за пределы сет
    ки, хотя и считается ошибкой, но рассматривается как нормальная си
    туация, поэтому в подобных случаях возбуждается наше собственное исключение.
    Теперь мы рассмотрим наиболее показательные и наиболее важные фрагменты программного кода модуля, начав с исключений:
    class RangeError(Exception): pass class RowRangeError(RangeError): pass class ColumnRangeError(RangeError): pass
    Ни одна из функций в модуле, возбуждающих исключения, не возбу
    ждает исключение RangeError, они всегда возбуждают конкретное ис
    ключение в зависимости от того, номер строки или столбца вышел за пределы сетки. Но, используя существующую иерархию исключений,
    мы даем пользователю модуля возможность выбирать, будет ли он об
    рабатывать конкретные исключения или перехватывать их по базово
    му классу RangeError. Обратите также внимание на то, что внутри док
    тестов используются неквалифицированные имена исключений, но когда модуль импортируется инструкцией import CharGrid, необходимо использовать полные квалифицированные имена исключений: Char
    Grid.RangeError
    , CharGrid.RowRangeError и CharGrid.ColumnRangeError.
    _CHAR_ASSERT_TEMPLATE = ("char must be a single character: '{0}' "
    "is too long")
    _max_rows = 25
    _max_columns = 80
    _grid = []
    _background_char = " "
    Здесь определяются некоторые частные данные для использования внутри модуля. Имена частных переменных начинаются с символа подчеркивания, поэтому, когда модуль будет импортироваться инст
    рукцией from CharGrid import *, ни одна из этих переменных не будет импортирована. (Как вариант, можно было бы использовать список
    __all__
    .) Переменная _CHAR_ASSERT_TEMPLATE – это строка, предназначен
    ная для вызова метода str.format(), – позднее мы увидим, что такой

    Модули и пакеты
    245
    прием широко используется для генерации сообщений об ошибках в инструкциях assert. Назначение остальных переменных будет пояс
    няться по мере того, как мы будем сталкиваться с ними.
    if sys.platform.startswith("win"):
    def clear_screen():
    subprocess.call(["cmd.exe", "/C", "cls"])
    else:
    def clear_screen():
    subprocess.call(["clear"])
    clear_screen.__doc__ = """Clears the screen using the underlying \
    window system's clear screen command"""
    Очистка экрана консоли в разных системах выполняется поразному.
    В Windows необходимо выполнить программу cmd.exe с соответствую
    щими аргументами, а в большинстве систем UNIX запускается про
    грамма clear. Функция subprocess.call() из модуля subprocess позволя
    ет запускать внешние программы, поэтому мы можем использовать ее для очистки экрана с учетом особенностей системы. Строка sys.plat
    form хранит имя операционной системы, под управлением которой вы
    полняется программа, например, «win32» или «linux2». Поэтому один из способов учесть различия между платформами – определить функ
    цию clear_screen(), как показано ниже:
    def clear_screen():
    command = (["clear"] if not sys.platform.startswith("win") else
    ["cmd.exe", "/C", "cls"])
    subprocess.call(command)
    Недостаток такого подхода заключается в том, что, даже зная, что тип платформы не изменится в процессе работы программы, мы все равно вынуждены выполнять проверку при каждом вызове функции.
    Чтобы избежать необходимости проверки типа операционной системы,
    под управлением которой выполняется программа, при каждом вызове функции clear_screen(), мы создаем платформозависимую функцию clear_screen()
    на этапе импортирования модуля и с этого момента по
    стоянно используем ее. Это возможно благодаря тому, что в языке Py
    thon инструкция def является самой обычной инструкцией – когда ин
    терпретатор достигает условной инструкции if, он выполняет либо первую, либо вторую инструкцию def, динамически создавая ту или иную версию функции clear_screen(). Так как определение функции находится за пределами какойлибо другой функции (или класса,
    о чем будет рассказываться в следующей главе), она попрежнему ос
    тается в глобальной области видимости и обращаться к ней можно так же, как к любой другой функции в модуле.
    После создания функции мы явно определяем строку документирова
    ния для нее – такой прием позволяет избежать необходимости дважды записывать одну и ту же строку документирования в двух местах,
    а, кроме того, иллюстрирует, что строка документирования – это всего

    1   ...   24   25   26   27   28   29   30   31   ...   74


    написать администратору сайта