Кеннет Рейтц – Автостопом по Python (страница 28)
>>> with open('tmp.csv', 'w') as outfile:
… ····outfile.write(data.csv)
…
64
>>> newdata = tablib.Dataset()
>>> newdata.csv = open('tmp.csv'). read()
>>> print(newdata.yaml)
- {First name: Black, Last name: Knight}
- {First name: Killer, Last name: Rabbit}
Структура файлов, находящихся в каталоге tablib/, выглядит так:
tablib
|- __init__.py
|- compat.py
|- core.py
|- formats/
|- packages/
Каталоги tablib/formats/ и tablib/packages/ рассматриваются в следующих разделах.
Python поддерживает строки документации на уровне модулей, как и строки документации, которые мы уже описали, — строковые литералы, являющиеся первым выражением в функции, классе или методе. На сайте Stack Overflow приведены полезные советы о том, как задокументировать модули (http://stackoverflow.com/a/2557196). Для нас это означает, что существует еще один способ исследовать исходный код — мы можем ввести команду head *.py в терминальной оболочке, находясь на верхнем уровне пакета, чтобы показать все строки документации модуля. Вот что мы увидим для библиотеки Tablib:
Мы узнали следующее.
Документация Tablib (http://docs.python-tablib.org/) содержит хороший пример использования Sphinx (http://www.sphinx-doc.org/en/stable/tutorial.html), поскольку это маленькая библиотека, которая применяет много расширений для Sphinx.
Документация к текущей версии Sphinx находится на странице документации Tablib (http://docs.python-tablib.org/). Если вы хотите построить документацию самостоятельно (пользователям Windows понадобится команда male — она старая, но работает как надо), сделайте следующее:
(venv)$ pip install sphinx
(venv)$ cd docs
(venv)$ make html
(venv)$ open _build/html/index.html # Для просмотра результата.
Sphinx предоставляет несколько вариантов тем оформления (http://www.sphinx-doc.org/en/stable/theming.html), которые настраиваются с помощью стандартных шаблонов представления кода и стилей CSS. Шаблоны Tablib для левой боковой панели находятся в каталоге docs/_templates/ в файле basic/layout.html. Вы можете найти этот файл в папке стилей Sphinx, введя в командной строке следующую команду:
(venv)$ python — c 'import sphinx.themes;print(sphinx.themes.__path__)'
Продвинутые пользователи также могут выполнять поиск в docs/_themes/kr/, пользовательском стиле, который расширяет базовую структуру. Его можно выбрать, добавив каталог _themes/ в системный путь, установив необходимые значения свойствам html_theme_path = ['_themes'] и html_theme = 'kr' в docs/conf.py.
Для включения в ваш код документации API, которая генерируется автоматически на основе строк документации, используйте autoclass::. Вам нужно скопировать форматирование строк документации в Tablib, чтобы это сработало:
.. autoclass:: Dataset
····:inherited-members:
Для получения этой функциональности следует ответить «да» на вопрос о включении расширения Sphinx autodoc при запуске sphinx-quickstart, чтобы создать новый проект Sphinx. Директива: inherited-members: также добавит документацию для атрибутов, унаследованных от классов-предков.
Примеры из структуры Tablib
Главная особенность, которую мы хотим подчеркнуть в Tablib, — отсутствие использования классов в модулях в tablib/formats/ (это идеально иллюстрирует наше утверждение, что не нужно везде применять классы). Далее мы приведем фрагменты кода, демонстрирующие использование синтаксиса декоратора в Tablib, а также задействуем класс property (https://docs.python.org/library/functions.html#property) для создания унаследованных атрибутов вроде ширины и высоты набора данных. Помимо этого, покажем, как динамически он регистрирует форматы файлов, чтобы избежать дупликации шаблонного кода для разных типов формата (CSV, YAML и т. д.).
В последних двух подразделах мы рассмотрим, как Tablib использует зависимости, полученные от третьей стороны, а затем обсудим свойство __slots__ объектов нового класса. Вы можете пропустить эти разделы и при этом продолжать жить счастливой питонской жизнью.
Каталог форматов содержит все определенные для ввода/вывода форматы файлов. Имена модулей _csv.py, _tsv.py, _json.py, _yaml.py, _xls.py, _xlsx.py, _ods.py и _xls.py начинаются с нижнего подчеркивания — это указывает пользователю библиотеки, что свойства не предназначены для непосредственного использования. Мы можем перейти в каталог formats и выполнять поиск классов и функций. Команда grep ^class formats/*.py показывает отсутствие определений классов, а команда grep ^def formats/*.py — что каждый модуль содержит одну или несколько следующих функций:
• detect(stream) определяет формат файла, основываясь на содержимом потока;
• dset_sheet(dataset, ws) форматирует клетки для таблиц Excel;
• export_set(dataset) экспортирует набор данных в заданный формат, возвращая отформатированную строку в новом формате (для Excel возвращает объект bytes или бинарную строку в Python 2);
• import_set(dset, in_stream, headers=True) заменяет содержимое набора данных содержимым входного потока;
• export_book(databook) экспортирует объекты Datasheet в Databook в заданном формате, возвращая объект типа string или bytes;
• import_book(dbook, in_stream, headers=True) заменяет содержимое databook содержимым входного потока.
Это примеры применения модулей как пространств имен (в конце концов, они же являются отличной штукой) для разделения функций вместо того, чтобы использовать ненужные классы. Мы узнаем предназначение каждой функции по ее имени, например formats._csv.import_set(), formats._tsv.import_set() и formats._json.import_set() импортируют наборы данных из файлов в формате CSV, TSV и JSON соответственно. Другие функции отвечают за экспорт данных и определение формата файла (где это возможно) для каждого доступного Tablib формата.
Tablib — наша первая библиотека, в которой используется синтаксис декораторов Python, описанный в подразделе «Декораторы» раздела «Структурируем проект» главы 4. Синтаксисом предусмотрено указывать символ @ перед именем функции, вся конструкция размещается над другой функцией. Декоратор изменяет (или декорирует) функцию, которая находится под ним. В следующем фрагменте кода свойство изменяет функции Dataset.height и Dataset.width, делая их дескрипторами — классами, в которых определен хотя бы один из следующих методов: __get__(), __set__() или __delete__() (геттер, сеттер и метод удаления). Например, поиск атрибута Dataset.height приведет к срабатыванию функции-геттера, сеттера или удаления в зависимости от контекста применения атрибута. Такое поведение присуще только новым классам (их мы вскоре обсудим). Для получения более подробной информации о дескрипторах обратитесь к довольно полезному руководству по Python по адресу https://docs.python.org/3/howto/descriptor.html.
Так выглядят атрибуты height и width при использовании:
>>> import tablib
>>> data = tablib.Dataset()
>>> data.header = ("amount", "ingredient")
>>> data.append(("2 cubes", "Arcturan Mega-gin"))
>>> data.width
2
>>> data.height
1
>>>
>>> data.height = 3
Traceback (most recent call last):
····File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Доступ к data.height можно получить так же, как и к любому другому атрибуту, но изменить его значение вы не можете — оно высчитывается на основе данных и всегда актуально. Такой дизайн API весьма эргономичен: конструкцию data.height проще ввести на клавиатуре, чем data.get_height(); понятно, что означает data.height. Поскольку значение этого свойства выводится на основе данных (значение свойства нельзя задать, для него определена только функция-геттер), можно не переживать, что значение свойства рассинхронизируется с реальными данными.
Декоратор свойства можно применить только к атрибутам классов и только к тем классам, которые наследуют от base object object (например, class MyClass(object), а не class MyClass() — в Python 3 всегда выполняется наследование от объекта).