Python в системном администрированиии UNIX и Linux [2009] Гифт

До загрузки: 30 сек.



Благодарим, что скачиваете у нас :)

Если, что - то:

  • Поделится ссылкой:
  • Документ найден в свободном доступе.
  • Загрузка документа - бесплатна.
  • Если нарушены ваши права, свяжитесь с нами.
Формат: pdf
Найдено: 13.08.2020
Добавлено: 30.09.2020
Размер: 3.28 Мб

Python
Эффективное решение проблем с помощью языка Python
в системном администрировании
UNIX и Linux
Ноа Гифт, Джереми М. Джонс
Издание демонстрирует возмож ности языка программировани я P ython д л я решени я разнообразны х за дач у прав лени я серверами UNI X и Linux. К а ж да я глава посвящена определенному вопросу, например многозадачности, резервному копированию данных или созданию собственных инструментов командной строки и предлагает практичес­кие методы его решения на языке Python.
Кроме того, авторами была создана дост упная для загрузки и свободно распространяемая вирт уаль ­ ная машина на базе Ubuntu, которая вк лючает исходные тексты кода из книги и способная выпол ­ нять примеры, использующие SNMP, IPython, SQL Alchemy и многие другие у тилиты.
Книга расскажет, как с помощью Python:
Читать текстовые файлы и извлекать информацию
Одновременно запускать задачи, используя потоки выполнения или ветвление процессов
Полу чать информацию и передавать ее другому процессу, используя сетевые механизмы
Создавать интерактивные у тилиты с графическим интерфейсом
Следить за огромными к ластерами машин, взаимодействуя с протоколом SNMP
Овладеть оболочкой IPython, способной с успехом заменить такие командные оболочки, как Bash, Korn или Z­ Shell
Интегрировать «облачные» вычисления в инфраструкт уру своей организации и создавать прило ­ жения для Google App Engine
Решать проблемы резервного копирования с помощью настраиваемых сценариев
Использовать Django, SQL Alchemy и Storm для организации взаимодействий с базами данных
Упаковывать и развертывать приложения и библиотеки.










Python в системном администрировании UNIX и Linux
Категория: Unix
Уровень подготовки читателей: средний
Издательство «Символ-Плюс»
(812) 324-5353, (495) 945-8100
www.symbol.ru
Н. Гифт,

Д. Джонс
Python
ISBN-13 978-5-93286-149- 3
9 785932 861493
« Джереми и Ноа приг лашают к знакомству всех, кто только начинает осваивать я зык Python, будь то опытные специа листы по созданию сценариев на языках командной оболочки и ли отно - сительно с лабо знакомые с програ м мированием вообще.»
– Ру т Сьюэль (Ruth Suehle) и Баша Харрис (Bascha Harris), Red Hat Magazine
Ноа Гифт (Noah Gift) пользуется операционными системами UNIX и Linux у же более 10 лет, работая в Caltech, Disney, Feature Animation и Turner Studios. Он является партнером по бизнесу в компаниях Giftcs, LLC и Cloud Seed Software, LLC.
Джереми М. Джонс ( Jeremy M. Jones) – инженер ­ программист, работающий в компании Predictix. Кроме того, он является автором открытых проектов Munkware, ediplex и podgrabber.
в системном

администрировании

UNIX и Linux

Noah Gift, Jeremy M. Jones
Python
for Unix and Linux
System Administration

Ноа Гифт и Джереми М. Джонс
Python
в системном администрировании
UNIX и Linux
СанктПетербург – Москва
2009

Ноа Гифт и Джереми М. Джонс
Python в системном администрировании
UNIX и Linux
Перевод А. Киселева
Главный редакторА. Галунов
Зав. редакциейН. Макарова
Выпускающий редакторП. Щеголев
РедакторЮ. Бочина
КорректорC. Минин
ВерсткаД. Орлова
Гифт Н., Джонс Д.
Python в системном администрировании UNIX и Linux – Пер. с англ. – СПб.:
СимволПлюс, 2009. – 512 с., ил.
ISBN 9785932861493
Книга «Python в системном администрировании UNIX и Linux» демонстриру
ет, как эффективно решать разнообразные задачи управления серверами UNIX
и Linux с помощью языка программирования Python. Каждая глава посвяще
на определенной задаче, например многозадачности, резервному копированию
данных или созданию собственных инструментов командной строки, и предла
гает практические методы ее решения на языке Python. Среди рассматривае
мых тем: организация ветвления процессов и передача информации между
ними с использованием сетевых механизмов, создание интерактивных утилит
с графическим интерфейсом, организация взаимодействия с базами данных
и создание приложений для Google App Engine. Кроме того, авторы книги соз
дали доступную для загрузки и свободно распространяемую виртуальную ма
шину на базе Ubuntu, включающую исходные тексты примеров из книги и спо
собную выполнять примеры, использующие SNMP, IPython, SQLAlchemy
и многие другие утилиты.
Издание рассчитано на широкий круг специалистов – всех, кто только начина
ет осваивать язык Python, будь то опытные разработчики сценариев на языках
командной оболочки или относительно мало знакомые с программированием
вообще.
ISBN 9785932861493
ISBN 9780596515829 (англ)
© Издательство СимволПлюс, 2009
Authorized translation of the English edition © 2008 O’Reilly Media, Inc. This trans
lation is published and sold by permission of O’Reilly Media, Inc., the owner of all
rights to publish and sell the same.
Все права на данное издание защищены Законодательством РФ, включая право на полное или час
тичное воспроизведение в любой форме. Все товарные знаки или зарегистрированные товарные зна
ки, упоминаемые в настоящем издании, являются собственностью соответствующих фирм.
Издательство «СимволПлюс». 199034, СанктПетербург, 16 линия, 7,
тел. (812) 3245353, www.symbol.ru. Лицензия ЛП N 000054 от 25.12.98.
Налоговая льгота – общероссийский классификатор продукции
ОК 00593, том 2; 953000 – книги и брошюры.
Подписано в печать 12.01.2009. Формат 70×100
1/16. П е ч а т ь о ф с е т н а я .
Объем 32 печ. л. Тираж 1000 экз. Заказ №
Отпечатано с готовых диапозитивов в ГУП «Типография «Наука»
199034, СанктПетербург, 9 линия, 12.

Я посвящаю эту книгу доктору Джозефу Е. Богену (Joseph E. Bogen),
моей матушке и моей супруге Леа – трем людям,
которые любили меня и верили в меня больше всех.
Ноа
Я посвящаю эту книгу моей жене Дебре и моим детям,
Зейну и Юстусу. Вы вдохновляли меня, дарили мне
свои улыбки и проявляли величайшее терпение,
пока я работал над этой книгой. Она по праву может
считаться настолько же вашей, насколько и моей.
Джереми

Оглавление
Предисловие . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Введение. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1. Введение. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Почему Python?
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Мотивация
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Основы
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Выполнение инструкций в языке Python
. . . . . . . . . . . . . . . . . . . . . . . . . 30
Использование функций в языке Python
. . . . . . . . . . . . . . . . . . . . . . . . . 35
Повторное использование программного кода
с помощью инструкции import
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2. IPython . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Установка IPython
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Базовые понятия
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Справка по специальным функциям
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Командная оболочка UNIX
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Сбор информации
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Автоматизация и сокращения
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
В заключение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
3. Текст . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Встроенные компоненты Python и модули
. . . . . . . . . . . . . . . . . . . . . . . 103
Анализ журналов
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
ElementTree
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
В заключение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
4. Создание документации и отчетов. . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Автоматизированный сбор информации
. . . . . . . . . . . . . . . . . . . . . . . . . 160
Сбор информации вручную
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Форматирование информации
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
Распространение информации
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
В заключение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185

8 Оглавление
5. Сети. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Сетевые клиенты
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Средства вызова удаленных процедур
. . . . . . . . . . . . . . . . . . . . . . . . . . . 199
SSH
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
Twisted
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Scapy
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Создание сценариев с использованием Scapy
. . . . . . . . . . . . . . . . . . . . . 219
6. Данные. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
Введение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
Использование модуля OS для взаимодействия с данными
. . . . . . . . . 222
Копирование, перемещение, переименование
и удаление данных
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
Работа с путями, каталогами и файлами
. . . . . . . . . . . . . . . . . . . . . . . . 226
Сравнение данных
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
Объединение данных
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Поиск файлов и каталогов по шаблону
. . . . . . . . . . . . . . . . . . . . . . . . . . 239
Обертка для rsync
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Метаданные: данные о данных
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
Архивирование, сжатие, отображение и восстановление
. . . . . . . . . . 246
Использование модуля tarfile для создания архивов TAR
. . . . . . . . . 246
Использование модуля tarfile для проверки
содержимого файлов TAR
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
7. SNMP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Введение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Краткое введение в SNMP
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
IPython и NetSNMP
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Исследование центра обработки данных
. . . . . . . . . . . . . . . . . . . . . . . . . 260
Получение множества значений с помощью SNMP
. . . . . . . . . . . . . . . 263
Создание гибридных инструментов SNMP
. . . . . . . . . . . . . . . . . . . . . . . 270
Расширение возможностей NetSNMP
. . . . . . . . . . . . . . . . . . . . . . . . . . 271
Управление устройствами через SNMP
. . . . . . . . . . . . . . . . . . . . . . . . . . 275
Интеграция SNMP в сеть предприятия с помощью Zenoss
. . . . . . . . . 276
8. Окрошка из операционных систем. . . . . . . . . . . . . . . . . . . . . . . . . . . 278
Введение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
Кроссплатформенное программирование
на языке Python в UNIX
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
PyInotify
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
OS X
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
Администрирование систем Red Hat Linux
. . . . . . . . . . . . . . . . . . . . . . 298
Администрирование Ubuntu
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
Администрирование систем Solaris
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299

Оглавление 9
Виртуализация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
Облачная обработка данных
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
Использование Zenoss для управления
серверами Windows из Linux
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
9. Управление пакетами. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
Введение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
Setuptools и пакеты Python Eggs
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
Использование easy_install
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
Дополнительные особенности easy_install
. . . . . . . . . . . . . . . . . . . . . . . 318
Создание пакетов
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Точки входа и сценарии консоли
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
Регистрация пакета в Python Package Index
. . . . . . . . . . . . . . . . . . . . . 330
Distutils
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Buildout
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
Использование Buildout
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
Разработка с использованием Buildout
. . . . . . . . . . . . . . . . . . . . . . . . . . 339
virtualenv
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
Менеджер пакетов EPM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
10. Процессы и многозадачность . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
Введение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
Модуль subprocess
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
Использование программы Supervisor
для управления процессами
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361
Использование программы screen
для управления процессами
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
Потоки выполнения в Python
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
Процессы
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
Модуль processing
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
Планирование запуска процессов Python
. . . . . . . . . . . . . . . . . . . . . . . . 382
Запуск демона
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
В заключение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
11. Создание графического интерфейса. . . . . . . . . . . . . . . . . . . . . . . . . 390
Теория создания графического интерфейса
. . . . . . . . . . . . . . . . . . . . . . 390
Создание простого приложения PyGTK
. . . . . . . . . . . . . . . . . . . . . . . . . 392
Создание приложения PyGTK для просмотра
файла журнала вебсервера Apache
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394
Создание приложения для просмотра файла журнала
вебсервера Apache с использованием curses
. . . . . . . . . . . . . . . . . . . . . 398
Вебприложения
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
Django
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404
В заключение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426

10 Оглавление
12. Сохранность данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427
Простая сериализация
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
Реляционная сериализация
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448
В заключение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 458
13. Командная строка. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459
Введение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459
Основы использования потока стандартного ввода
. . . . . . . . . . . . . . . . 460
Введение в optparse
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462
Простые шаблоны использования optparse
. . . . . . . . . . . . . . . . . . . . . . 462
Внедрение команд оболочки в инструменты
командной строки на языке Python
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 470
Интеграция конфигурационных файлов
. . . . . . . . . . . . . . . . . . . . . . . . 477
В заключение
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479
14. Практические примеры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
Управление DNS с помощью сценариев на языке Python
. . . . . . . . . . 480
Использование протокола LDAP для работы с OpenLDAP, Active
Directory и другими продуктами из сценариев на языке Python
. . . . 482
Составление отчета на основе файлов журналов Apache
. . . . . . . . . . . 484
Зеркало FTP
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492
Приложение. Функции обратного вызова . . . . . . . . . . . . . . . . . . . . 496
Алфавитный указатель. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499

Вступительное слово
Я была приятно взволнована предложением выполнить предваритель
ный обзор книги, посвященной использованию языка Python для нужд
системного администрирования. Я вспомнила свои ощущения, когда
впервые познакомилась с языком Python после многих лет програм
мирования на других языках; это было похоже на свежесть весеннего
ветра и согревающее тепло солнца после долгой зимы. Программиро
вание на этом языке оказалось настолько необычайно простым и увле
кательным делом, что мне удавалось заканчивать программы намного
раньше, чем прежде.
Будучи системным администратором, я использовала язык Python
в основном для решения задач системного и сетевого администрирова
ния. Я заранее знала, насколько востребованной будет хорошая книга,
посвященная применению языка Python в системном администриро
вании, и рада сказать, что это в полной мере относится к данной книге.
Авторам, Ноа (Noah) и Джереми (Jeremy), удалось написать интерес
ную и умную книгу о языке Python, который прочно обосновался
в сфере системного администрирования. Я нахожу эту книгу полез
ной и увлекательной.
Две первые главы представляют собой введение в язык программирова
ния Python для системных администраторов (и других), которые еще
не знакомы с ним. Я отношу себя к программистам на языке Python
среднего уровня, поэтому немало нового узнала из этой книги. Я пола
гаю, что даже искушенные программисты найдут здесь несколько но
вых приемов. Особенно я рекомендую прочитать главы, посвященные
сетевому администрированию и управлению сетевыми службами, SN
MP и управлению гетерогенными сетями, потому что в центре их вни
мания находятся нетривиальные и реальные задачи, с которыми сис
темные администраторы сталкиваются ежедневно.

Предисловие
Типографские соглашения
В этой книге используются следующие типографские соглашения:
Курсив
Курсивом выделяются новые термины, адреса URL, адреса элек
тронной почты, имена файлов и их расширения.
Моноширинный шрифт
Используется для оформления листингов программ, для обозначе
ния в тексте таких программных элементов, как имена переменных
или функций, баз данных, типов данных, переменных окружения,
инструкций, ключевых слов, утилит и модулей.
Моноширинный жирный шрифт
Используется для выделения команд и другого текста, который
должен вводиться пользователем.
Моноширинный курсив
Используется для выделения текста, который пользователь должен
заменить своими значениями или значениями, определяемыми
контекстом.
Таким способом выделяются советы, предложения и примеча
ния общего характера.
Таким способом выделяются предупреждения и предостереже
ния.
Использование программного кода примеров
Данная книга призвана оказать вам помощь в решении ваших задач.
Вообще вы можете свободно использовать примеры программного ко
да из этой книги в своих программах и в документации. Вам не нужно
обращаться в издательство за разрешением, если вы не собираетесь
воспроизводить существенные части программного кода. Например,
если вы разрабатываете программу и используете в ней несколько от
рывков программного кода из книги, вам не нужно обращаться за раз

Предисловие 13
решением. Однако в случае продажи или распространения компакт
дисков с примерами из этой книги вам необходимо получить разреше
ние от издательства O’Reilly. Для цитирования данной книги или при
меров из нее при разъяснении какихлибо вопросов получение разре
шения не требуется. При включении существенных объемов про
граммного кода примеров из этой книги в документацию на вашу про
дукцию вам необходимо получить разрешение издательства.
Мы приветствуем, но не требуем добавлять ссылку на первоисточник
при цитировании. Под ссылкой на первоисточник мы подразумеваем
указание авторов, издательства и ISBN. Например: «Python for Unix
and Linux System Administration by Noah Gift and Jeremy M. Jones. Co
pyright 2008 Noah Gift and Jeremy M. Jones, 9780596515829».
За получением разрешения на использование значительных объемов
программного кода примеров из этой книги обращайтесь по адресу:
permissions@oreilly.com.
Safari ® Books Online
Если на обложке технической книги есть пиктограмма
«Safari ® Books Online», это означает, что книга доступна
в Сети через O’Reilly Network Safari Bookshelf.
Safari предлагает намного лучшее решение, чем электронные книги.
Это виртуальная библиотека, позволяющая без труда находить тысячи
лучших технических книг, вырезать и вставлять примеры кода, за
гружать главы и находить быстрые ответы, когда требуется наиболее
верная и свежая информация. Она свободно доступна по адресу http://
safari.oreilly.com.
Отзывы и предложения
С вопросами и предложениями, касающимися этой книги, обращай
тесь в издательство:
O’Reilly Media
1005 Gravenstein Highway North
Sebastopol, CA 95472
8009989938 (в Соединенных Штатах Америки или в Канаде)
7078290515 (международный)
7078290104 (факс)
Список опечаток, файлы с примерами и другую дополнительную ин
формацию вы найдете на сайте книги:
http://www.oreilly.com/9780596515829
Свои пожелания и вопросы технического характера отправляйте по
адресу:
bookquestions@oreilly.com

14 Предисловие
Дополнительную информацию о книгах, обсуждения, Центр ресурсов
издательства O’Reilly вы найдете на сайте:
http://www.oreilly.com
Благодарности
От Ноа
Лист благодарностей этой книги я хочу начать с доктора Джозефа Е. Бо
гена (Joseph E. Bogen), потому что он – человек, который оказал наи
большее влияние на меня в тот момент, когда это было больше всего
необходимо. Я встретил доктора Богена, когда работал в фирме Cal
tech, и он открыл мне глаза на другой мир, давая советы по жизнен
ным ситуациям, психологии, неврологии, математике, по исследова
ниям в области сознания и во многих других областях. Это был умней
ший человек, которого я когдалибо встречал, и я искренне любил его.
Когданибудь я напишу книгу об этом опыте. Я опечален тем, что он не
сможет прочитать ее; его смерть стала для меня большой утратой.
Я хочу сказать спасибо моей жене Леа (Leah), самой лучшей из всех
женщин, встречавшихся мне. Без твоей любви и поддержки мне не
удалось бы написать эту книгу. Ты терпелива, как ангел. Я надеюсь
и дальше идти с тобой по жизни, я люблю тебя. Я также хочу поблаго
дарить моего сына Лиама (Liam), которому полтора года, за то, что тер
пел, пока я работал над этой книгой. Мне пришлось сильно урезать на
ши занятия музыкой и спортом, поэтому я должен вернуть тебе в два
раза больше, мой маленький козленок.
Моей матушке: я люблю тебя и хочу сказать спасибо, что подбадрива
ла меня все время.
Конечно же, я хочу сказать спасибо Джереми М. Джонсу (Jeremy M. Jo
nes), моему соавтору – за то, что согласился написать эту книгу вместе
со мной. Я думаю, из нас получилась отличная команда. У нас разные
стили, но они прекрасно дополняют друг друга, и мы написали хоро
шую книгу. Вы дали мне много новых знаний о языке Python и были
для меня хорошим партнером и другом. Спасибо!
Титус Браун (Titus Brown), которого теперь я должен бы называть док
тором Брауном, был тем, кто разжег во мне первый интерес к языку
Python, когда я встретил его в фирме Caltech. Это еще один пример то
го, какое важное значение может иметь один человек, и я рад считать
его своим «старым» другом, которого не купишь ни за какие деньги.
Он не уставал спрашивать меня: «Почему ты не используешь Py
thon?». И однажды я решил попробовать. Если бы не Титус, я безус
ловно вернулся бы обратно к языкам Java и Perl. Вы можете почитать
его блог по адресу: http://ivory.idyll.org/blog.
У Шеннона Беренса (Shannon Behrens) золотая голова, острый, как
бритва, ум и потрясающее знание языка Python. Я познакомился

Предисловие 15
с Шенноном благодаря Титусу, и мы быстро подружились с ним. Шен
нон – настоящий человек дела, во всех смыслах этого слова, и он дал
мне огромный объем знаний о языке Python, можно даже сказать, ги
гантский. Его помощь во всем, что касалось языка Python, и в редак
тировании этой книги была просто неоценима, и я чрезвычайно обязан
ему за это. Иногда я с ужасом думаю, какой могла бы быть эта книга
без его помощи. Я не могу себе представить компанию, которая может
упустить его, и я надеюсь помочь ему с его первой книгой. Наконец, он
просто удивительный технический рецензент. Вы можете почитать
его блог по адресу: http://jjinux.blogspot.com/.
Еще одним звездным техническим рецензентом был Дуг Хеллманн
(Doug Hellmann). Сотрудничество с ним было исключительно плодо
творным и полезным. Джереми и мне необычайно повезло в том, что
нам удалось заполучить специалиста такого масштаба в качестве ре
цензента. Он не ограничился своим служебным долгом и стал настоя
щей движущей силой. Он был для нас неиссякаемым источником
вдохновения, пока мы вместе с ним работали в компании Racemi. Вы
можете почитать его блог по адресу: http://blog.doughellmann.com/.
Кому еще я хотел бы выразить свою признательность?
Скотту Лирсину (Scott Leerseen) – за обзор книги и за полезные советы
в процессе работы над ней. Я получал огромное удовольствие от жар
ких споров, разгоравшихся вокруг фрагментов программного кода. Но
помните – я всегда прав.
Альфредо Деза (Alfredo Deza) – за работу над настройкой виртуальной
машины с Ubuntu, которая была необходима для работы над книгой.
Твой опыт был для нас очень ценным.
Лайзе Дейли (Liza Daly) – за отзывы к первым черновым наброскам не
которых частей этой книги. Они были чрезвычайно полезными.
Джеффу Рашу (Jeff Rush) – за помощь и советы в работе с Buildout,
Eggs и Virualenv.
Аарону Хиллегассу (Aaron Hillegass), владельцу замечательной обу
чающей компании Big Nerd Ranch, – за ценные советы и помощь во
время работы. Мне крупно повезло, что посчастливилось встретиться
сним.
Марку Лутцу (Mark Lutz), под руководством которого я прошел курс
обучения языку Python и который написал несколько замечательных
книг по языку Python.
Членам сообщества Python в Атланте и участникам проекта PyAtl:
http://pyatl.org – вы многому научили меня. Рик Коупленд (Rick Cope
land), Рик Томас (Rick Thomas), Брендон Родс (Brandon Rhodes), Дерек
Ричардсон (Derek Richardson), Джонатан Ла Кур (Jonathan La Cour),
известный также под псевдонимом Mr. Metaclass, Дрю Смазерс (Drew
Smathers), Кари Халл (Cary Hull), Бернард Меттьюс (Bernard Mat

16 Предисловие
thews), Майкл Лангфорд (Michael Langford) и многие другие, кого я за
был упомянуть. Брендон и Рик Коупленд (Brandon and Rick Copeland)
были в особенности полезны; они являются высококлассными про
граммистами на языке Python. Вы можете почитать блог Брендона по
адресу: http://rhodesmill.org/brandon/.
Григу Георгиу (Grig Gheorghiu) – за то, что делился с нами опытом
системного администратора, за проверку советов и за то, что поддавал
нам пинка, когда это было необходимо.
Моему работодателю, главному техническому директору и основателю
компании Racemi, Чарльзу Уатту (Charles Watt). Я многому научился
у вас и был рад, что вы знаете, когда какие кнопки нажимать. Помни
те, что я всегда готов написать для вас программу, пробежать 26миль
ную дистанцию или проехать 200 миль на велосипеде – только сооб
щите мне, где и когда.
Доктору Нанде Ганесан (Nanda Ganesan), моему наставнику в аспиран
туре Калифорнийского государственного университета в городе Лос
Анджелес (CSULA). Вы многому научили меня в области информаци
онных технологий и в жизни и, кроме того, побуждали меня мыслить
самостоятельно.
Доктору Синди Хейсс (Cindy Heiss) – моему профессору в мою быт
ность студентом. Вы приобщили меня к вебразработке, научили ве
рить в свои силы и, в конечном счете, оказали влияние на мою жизнь,
спасибо!
Шелдону Блокбургеру (Sheldon Blockburger), позволившему мне по
пробовать свои силы в десятиборье в Калифорнийском государствен
ном политехническом университете в городе Сан Луис Обиспо (Cal
Poly SLO). Даже при том, что я не стал членом команды, вы развили во
мне живой дух соперничества, качества борца и научили самодисцип
лине, предоставив мне самому отрабатывать забеги на короткие дис
танции. И поныне еженедельные тренировки позволяют мне не поте
рять форму, в том числе и как программисту.
Брюсу Дж. Беллу (Bruce J. Bell), с которым я работал в Caltech. В тече
ние нескольких лет совместной работы он учил меня программирова
нию, делился своими знаниями операционной системы UNIX, и я очень
признателен ему за это. С его статьями вы можете познакомиться по
адресу: http://www.ugcs.caltech.edu/~bruce/.
Альберто Валезу (Alberto Valez), моему боссу в Sony Imageworks, – за
то, что он был, пожалуй, лучшим боссом из всех, кто у меня когдали
бо был, и за то, что предоставил мне возможность полностью автомати
зировать мою работу.
Монтажеру фильмов Эду Фуллеру (Ed Fuller), который помогал мне
советами и оставался отличным другом все это время.

Предисловие 17
Было много и других людей, оказывавших мне неоценимую помощь
в работе, включая Дженнифер Девис (Jennifer Davis), еще одного дру
га по Caltech, которая предоставила несколько ценных отзывов; не
скольких друзей и коллег по работе в компании Turner – Дуга Уэйка
(Doug Wake), Уэйна Бланкарда (Wayne Blanchard), Сэма Олгуда (Sam
Allgood), Дона Воравонга (Don Voravong); моих друзей и коллег по ра
боте в Disney Feature animation, включая Шина Сомероффа (Sean Som
eroff), Грега Нигла (Greg Neagle) и Бобби Ли (Bobby Lea). Грег Нигл
(Greg Neagle), в частности, очень многому меня научил в Mac OS X.
Спасибо также Дж. Ф. Паниссету (J. F. Panisset), с которым я встре
тился в Sony Imageworks, учившему меня общим принципам разработ
ки. И хотя теперь он главный технический директор, он мог бы счи
таться ценным кадром в любой компании.
Я хотел бы поблагодарить еще несколько человек, оказавших сущест
венное содействие: Майка Вагнера (Mike Wagner), Криса МакДауэлла
(Chris McDowell) и Шона Смута (Shaun Smoot).
Спасибо членам сообщества Python. В первую очередь спасибо Гвидо
Ван Россуму (Guido van Rossum) за создание такого замечательного
языка, за его качества настоящего лидера и за то, что был терпелив со
мной, когда я обращался за советом по поводу этой книги. В сообщест
ве Python есть большое число других знаменитостей, разрабатываю
щих инструменты, которыми я пользуюсь каждый день. Это Ян Би
кинг (Ian Bicking), Фернандо Перез (Fernando Perez) и Вилле Вайнио
(Ville Vainio), Майк Байер (Mike Bayer), Густаво Немейер (Gustavo Ni
emeyer) и другие. Спасибо Дэвиду Бизели (David Beazely) за его заме
чательную книгу и его фантастическое руководство «PyCon 2008 on
Generators». Спасибо всем, кто пишет о языке Python и о системном ад
министрировании. Ссылки на их работы вы сможете отыскать на стра
нице http://wiki.python.org/moin/systems_administration. Спасибо так
же команде проекта Repoze: Тресу Сиверу (Tres Seaver) и Крису Мак
Донаху (Chris McDonough) (http://repoze.org/index.html).
Отдельная благодарность Филиппу Дж. Эби (Phillip J. Eby) за замеча
тельный набор инструментальных средств, за проявленное терпение
и советы по разделу, посвященному использованию библиотеки setup
tools. Спасибо также Джиму Фултону (Jim Fulton) за то, что терпеливо
отвечал на шквал моих вопросов по использованию ZODB и buildout.
Особое спасибо Мартьяну Фассену (Martijn Fassen), который учил ме
ня пользоваться такими продуктами, как ZODB и Grok. Если вам ин
тересно заглянуть в будущее разработки вебприложений на языке Py
thon, обратите внимание на проект Grok: http://grok.zope.org/.
Спасибо сотрудникам журнала «Red Hat Magazine» – Джулии Брис
(Julie Bryce), Джессике Гербер (Jessica Gerber), Баша Харрису (Bascha
Harris) и Рут Сьюл (Ruth Suehle) за то, что позволили мне опробовать
идеи, излагаемые в книге, в форме статей. Спасибо также Майку Мак

18 Предисловие
Крери (Mike McCrery) из IBM Developerworks за то, что предоставил мне
возможность опубликовать в форме статей некоторые идеи из книги.
Я хочу поблагодарить множество людей, которые в разные моменты
моей жизни говорили, что мне чтото не по силам. Почти на каждом
жизненном этапе я встречал людей, которые пытались отговорить ме
ня: начиная с того, что я не смогу поступить в колледж, в который хо
тел бы поступить, и заканчивая тем, что я никогда не смогу изучать
программирование. Спасибо вам за то, что давали мне дополнительный
толчок к воплощению моих мечтаний. Люди способны выстроить свою
жизнь, если они понастоящему верят в себя; я мог бы посоветовать ка
ждому пытаться сделать то, что он действительно хочет сделать.
Наконец, спасибо издательству O’Reilly и Татьяне Апанди (Tatiana
Apandi) за то, что верили в мою способность написать книгу о примене
нии языка Python в системном администрировании. Вы рискнули, пове
рили в меня и в Джереми, и я благодарю вас за это. Пусть ближе к концу
книги Татьяна оставила издательство, чтобы воплотить свои мечты, тем
не менее, мы продолжали чувствовать ее присутствие. Я также хотел бы
отметить нового редактора Джулию Стил (Jilie Steele), которая была
благожелательна и отзывчива. Вы привнесли целое море спокойствия,
что лично я ценил очень высоко. В будущем я надеюсь еще услышать
приятные новости от Джулии и буду счастлив снова работать с ней.
От Джереми
Длинный список благодарностей от Ноа заставил меня почувствовать
себя неблагодарным человеком, потому что мой список не получится
таким длинным, и обескураженным, так как он поблагодарил почти
всех, кому мне тоже хотелось бы сказать спасибо.
В первую очередь я хотел бы вознести слова благодарности Господу Бо
гу, c помощью которого я могу творить и без которого я ничего не смог
бы сделать.
А в земном смысле в первую очередь я хотел бы поблагодарить мою
супругу Дебру (Debra). Ты занимала детей другими делами, пока я ра
ботал над книгой. Ты сделала законом фразу: «Не беспокойте папу, он
работает над своей книгой». Ты подбадривала меня, когда мне это бы
ло необходимо, и ты выделила мне пространство, которое мне так тре
бовалось. Спасибо тебе. Я люблю тебя. Без тебя я не смог бы написать
эту книгу.
Я также хотел поблагодарить моих славных детей, Зейна (Zane) и Юс
туса (Justus) за их терпение по отношению к моей работе над книгой.
Я пропустил большое число поездок с вами в парк Каменная Гора.
Я попрежнему укладывал вас спать, но я не оставался и не засыпал
вместе с вами, как обычно. Последние несколько недель я пропускал
шоу «Kid’s Rock», которое выходит вечером по средам. Я пропустил
так много, но вы терпеливо выдержали все это. Спасибо вам за ваше

Предисловие 19
терпение. И спасибо вам за то, что радовались, когда услышали, что я
почти закончил книгу. Я люблю вас обоих.
Я хочу поблагодарить своих родителей, Чарльза и Линду Джонс (Char
les and Linda Jones), за их поддержку моей работы над этой книгой. Но
больше всего я хочу сказать им спасибо за то, что были для меня при
мером этики, за то, что научили меня работать над собой и с умом тра
тить деньги. Надеюсь, что все это я смогу передать своим детям, Зейну
и Юстусу.
Спасибо Ноа Гифту (Noah Gift), моему соавтору, за то, что втянул меня
в это дело. Оно оказалось тяжелым, тяжелее, чем я думал, и опреде
ленно одно из самых тяжелых, которое мне когдалибо приходилось
делать. Когда вы работаете с человеком над чемто, подобным книге,
и под конец попрежнему считаете его своим другом, я думаю, это дос
таточно характеризует его. Спасибо, Ноа. Эта книга не состоялась бы
без тебя.
Я хочу поблагодарить нашу команду рецензентов. Ноа уже поблагода
рил всех вас, но я хочу еще раз поблагодарить Дуга Хеллмана (Doug
Hellman), Дженнифер Девис (Jennifer Davis), Шеннона Дж. Беренса
(Shannon J. Behrens), Криса МакДауэлла (Chris McDowell), Титуса
Брауна (Titus Brown) и Скотта Лирсина (Scott Leerseen). Вы удиви
тельные люди. Бывали моменты, когда я заходил в тупик, и вы на
правляли мои мысли в нужное русло. Вы привнесли свое видение и по
могли мне увидеть книгу с разных точек зрения. (В основном это отно
сится к вам, Дженнифер. Если глава, посвященная обработке текста,
принесет пользу системным администраторам, то только благодаря
вам.) Спасибо вам всем.
Я хотел бы сказать спасибо нашим редакторам, Татьяне Апанди (Tati
ana Apandi) и Джулии Стил (Julie Steele). Вы взяли на себя рутинный
труд, освободив нас для работы над книгой. Вы обе облегчили нашу
ношу.
Я также хочу выразить свою признательность Фернандо Перезу
(Fernando Perez) и Вилле Вайнио (Villе Vainio) за потрясающие отзы
вы. Надеюсь, что мне удалось воздать должное IPython. И спасибо вам
за IPython. Без него моя жизнь оказалась бы труднее.
Спасибо вам, Дункан МакГреггор (Duncan McGreggor), за помощь с при
мерами использования платформы Twisted. Ваши комментарии были
чрезвычайно полезны. И спасибо, что вы продолжаете работать над
этой замечательной платформой. Я надеюсь, что теперь буду использо
вать ее более широко.
Я благодарю Брема Мулинаара (Bram Moolenaar) и всех тех, кто когда
либо работал над редактором Vim. Почти все слова и теги XML, кото
рые мне пришлось написать, были написаны с помощью Vim. В про
цессе работы над книгой я узнал несколько новых приемов и ввел их

20 Предисловие
в свой повседневный обиход. Редактор Vim позволил мне поднять мою
производительность. Спасибо вам.
Я также хочу сказать спасибо Линусу Торвальдсу (Linus Torvalds),
разработчикам Debian, разработчикам Ubuntu и всем тем, кто когда
либо работал над операционной системой Linux. Почти каждое слово,
которое я напечатал, было напечатано в Linux. Вы обеспечили неверо
ятную простоту настройки новых окружений и проверку различных
идей. Спасибо вам.
Наконец, но ни в коем случае не меньше других, я хочу поблагодарить
Гвидо ван Россума (Guido van Rossum) и всех тех, кто когдалибо рабо
тал над языком программирования Python. Я извлекал выгоду из ва
шего труда на протяжении нескольких последних лет. Два своих по
следних места работы я получил благодаря знанию языка Python.
Язык Python и сообщество его поклонников не раз радовали меня с тех
пор, как я начал использовать этот язык гдето в 2001–2002 годах.
Спасибо вам. Python пришелся мне по душе.

1
Введение
Почему Python?
Если вы системный администратор, вам наверняка пришлось сталки
ваться с Perl, Bash, ksh и некоторыми другими языками сценариев.
Вы могли даже использовать один или несколько языков в своей рабо
те. Языки сценариев часто позволяют выполнять рутинную, утоми
тельную работу со скоростью и надежностью, недостижимой без них.
Любой язык – это всего лишь инструмент, позволяющий выполнить
работу. Ценность языка определяется лишь тем, насколько точно
и быстро с его помощью можно выполнить свою работу. Мы считаем,
что Python представляет собой ценный инструмент именно потому, что
он дает возможность эффективно выполнять нашу работу.
Можно ли сказать, что Python лучше, чем Perl, Bash, Ruby или любой
другой язык? На самом деле очень сложно дать такую качественную
оценку, потому что всякий инструмент очень тесно связан с образом
мышления программиста, использующего его. Программирование –
это субъективный и очень личностный вид деятельности. Язык стано
вится превосходным, только если он полностью соответствует потреб
ностям программиста. Поэтому мы не будем доказывать, что язык Py
thon лучше, но мы объясним причины, по которым мы считаем Python
лучшим выбором. Мы также объясним, почему он так хорошо подхо
дит для решения задач системного администрирования.
Первая причина, по которой мы считаем Python превосходным языком,
состоит в том, что он очень прост в изучении. Если язык не способен бы
стро превратиться для вас в эффективный инструмент, его привлека
тельность резко падает. Неужели вы хотели бы потратить недели или
месяцы на изучение языка, прежде чем вы окажетесь в состоянии на
писать на нем чтолибо стоящее? Это особенно верно для системных ад
министраторов. Если вы – системный администратор, проблемы могут

22 Глава 1. Введение
накапливаться быстрее, чем вы можете разрешать их. С помощью
языка Python вы сумеете начать писать полезные сценарии буквально
спустя несколько часов, а не дней или недель. Если язык не позволяет
достаточно быстро приступить к написанию сценариев, это повод заду
маться в целесообразности его изучения.
Однако язык, пусть и простой в изучении, но не позволяющий решать
сложные задачи, также не стоит потраченных на него усилий. Поэто
му вторая причина, по которой мы считаем Python превосходным язы
ком программирования, заключается в том, что он позволяет решать
такие сложные задачи, какие только можно вообразить. Вам требует
ся строку за строкой просматривать файлы журналов, чтобы выудить
из них какуюто важную информацию? Язык Python в состоянии по
мочь решить эту задачу. Или вам требуется просмотреть файл журна
ла, извлечь из него определенные записи и сравнить обращения с каж
дого IPадреса в этом файле с обращениями в каждом из файлов жур
налов (которые хранятся в реляционной базе данных) за последние
три месяца, а затем сохранить результаты в реляционной базе дан
ных? Вне всяких сомнений это можно реализовать на языке Python.
Язык Python используется для решения весьма сложных задач, таких
как анализ генных последовательностей, для обеспечения работоспо
собности многопоточных вебсерверов и сложнейших статистических
вычислений. Возможно, вам никогда не придется решать подобные за
дачи, но будет совсем нелишним знать, что в случае необходимости
язык поможет вам решать их.
Кроме того, если вы в состоянии выполнять сложнейшие операции, но
удобство сопровождения программного кода оставляет желать лучше
го, это плохой знак. Язык Python ликвидирует проблемы, связанные
с сопровождением программного кода, и он действительно позволяет
выражать сложные идеи простыми языковыми конструкциями. Про
стота программного кода – существенный фактор, который облегчает
дальнейшее его сопровождение. Программный код на языке Python на
столько прост, что позволяет возвращаться к нему спустя месяцы.
И достаточно прост, чтобы можно было вносить изменения в программ
ный код, который раньше нам не встречался. Таким образом, синтак
сис и общие идиомы этого языка настолько ясные, краткие и простые,
что позволяют работать с ним в течение длительных периодов времени.
Следующая причина, по которой мы считаем Python превосходным
языком, заключается в высокой удобочитаемости программного кода.
Блоки программного кода определяются по величине отступов. Отсту
пы помогают взгляду следить за ходом выполнения программы. Кро
ме того, язык Python основан на «использовании слов». Под этим под
разумевается, что хотя в языке Python используются свои специальные
символы, основные его особенности в большинстве своем реализованы
в виде ключевых слов или библиотек. Упор на слова, а не на специаль
ные символы упрощает чтение и понимание программного кода.

Почему Python? 23
Теперь, когда мы выявили некоторые преимущества языка Python,
мы проведем сравнение нескольких фрагментов программного кода на
языках Python, Perl и Bash. Попутно мы познакомимся еще с несколь
кими преимуществами языка Python. Ниже приводится простой при
мер на языке Bash, который выводит все возможные парные комбина
ции символов из набора 1, 2 и символов из набора a, b:
#!/bin/bash
for a in 1 2; do
for b in a b; do
echo "$a $b"
done
done
Вот эквивалентный фрагмент на языке Perl:
#!/usr/bin/perl
foreach $a ('1', '2') {
foreach $b ('a', 'b') {
print "$a $b\n";
}
}
Это самый простой вложенный цикл. А теперь сравним эти реализа
ции с циклом for в языке Python:
#!/usr/bin/env python
for a in [1, 2]:
for b in ['a', 'b']:
print a, b
Далее продемонстрируем использование условных инструкций в Bash,
Perl и Python. Здесь используется простая условная инструкция if/
else, с помощью которой выясняется – является ли заданный путь
к файлу каталогом:
#!/bin/bash
if [ d "/tmp" ] ; then
echo "/tmp is a directory"
else
echo "/tmp is not a directory"
fi
Ниже приводится эквивалентный сценарий на языке Perl:
#!/usr/bin/perl
if ( d "/tmp") {
print "/tmp is a directory\n";
}
else {

24 Глава 1. Введение
print "/tmp is not a directory\n";
}
А ниже – эквивалентный сценарий на языке Python:
#!/usr/bin/env python
import os
if os.path.isdir("/tmp"):
print "/tmp is a directory"
else:
print "/tmp is not a directory"
Еще один фактор, говорящий в пользу превосходства языка Python, –
это простота поддержки объектноориентированного стиля програм
мирования (ООП). А также то обстоятельство, что вас ничто не застав
ляет использовать ООП, если в этом нет необходимости. Но когда по
является потребность в нем, этот стиль оказывается чрезвычайно про
стым в применении. ООП позволяет легко и просто разделить пробле
му на составные функциональные части, объединенные в нечто под
названием «объект». Язык Bash не поддерживает ООП, но Perl и Py
thon поддерживают. Ниже приводится модуль на языке Perl с опреде
лением класса:
package Server;
use strict;
sub new {
my $class = shift;
my $self = {};
$self >{IP} = shift;
$self >{HOSTNAME} = shift;
bless($self);
return $self;
}
sub set_ip {
my $self = shift;
$self >{IP} = shift;
return $self >{IP};
}
sub set_hostname {
my $self = shift;
$self >{HOSTNAME} = shift;
return $self >{HOSTNAME};
}
sub ping {
my $self = shift;
my $external_ip = shift;
my $self_ip = $self >{IP};
my $self_host = $self >{HOSTNAME};
print "Pinging $external_ip from $self_ip ($self_host)\n";

Почему Python? 25
return 0;
}
1;
И далее фрагмент, в котором он используется:
#!/usr/bin/perl
use Server;
$server = Server >new('192.168.1.15', 'grumbly');
$server >ping('192.168.1.20');
Программный код, в котором используется объектноориентирован
ный модуль, достаточно прост. Однако на анализ самого модуля может
потребоваться некоторое время, особенно если вы не знакомы с ООП
или с особенностями реализации его поддержки в языке Perl.
Эквивалентный класс на языке Python и порядок его использования
выглядят, как показано ниже:
#!/usr/bin/env python
class Server(object):
def __init__(self, ip, hostname):
self.ip = ip
self.hostname = hostname
def set_ip(self, ip):
self.ip = ip
def set_hostname(self, hostname):
self.hostname = hostname
def ping(self, ip_addr):
print "Pinging %s from %s (%s)" % (ip_addr, self.ip, self.hostname)
if __name__ == '__main__':
server = Server('192.168.1.20', 'bumbly')
server.ping('192.168.1.15')
Примеры на языках Perl и Python демонстрируют некоторые из фун
даментальных аспектов ООП, и вместе с тем они наглядно показывают
различные особенности, которые используются в этих языках для дос
тижения поставленной цели. Оба фрагмента решают одну и ту же зада
чу, но они отличаются друг от друга. Таким образом, если вы пожелае
те использовать ООП, язык Python предоставит вам такую возмож
ность. И вы достаточно легко и просто сможете включить его в свой ар
сенал.
Другое преимущество Python проистекает не из самого языка, а из его
сообщества. В сообществе пользователей языка Python достигнуто
единодушие по поводу способов решения определенных видов задач,
которые вы должны (или не должны) использовать. Несмотря на то,
что сам язык обеспечивает множество путей достижения одной и той
же цели, соглашения, принятые в сообществе, могут рекомендовать

26 Глава 1. Введение
воздерживаться от использования некоторых из них. Например, инст
рукция from module import * в начале модуля считается вполне допус
тимой. Однако сообщество осуждает такое ее использование и реко
мендует использовать либо инструкцию import module, либо инструк
цию from module import resource. Импортирование всего содержимого
модуля в пространство имен другого модуля может вызвать сущест
венные осложнения, когда вы попытаетесь выяснить принцип дейст
вия модуля и узнать, где находятся вызываемые функции. Это кон
кретное соглашение поможет вам писать более понятный программ
ный код, что позволит тем, кто будет сопровождать его, выполнять
свою работу с большим удобством. Следование общепринятым согла
шениям открывает вам путь к использованию наилучших приемов
программирования. Мы считаем, что это идет только на пользу.
Стандартная библиотека языка Python – это еще одна замечательная
особенность Python. Если применительно к языку Python вы услыши
те фразу «батарейки входят в комплект поставки», это лишь означает,
что стандартная библиотека позволяет решать все виды задач без необ
ходимости искать другие модули для этого. Например, несмотря на их
отсутствие в самом языке, Python обеспечивает поддержку регуляр
ных выражений, сокетов, нескольких потоков выполнения и функции
для работы с датой/временем, синтаксического анализа документов
XML, разбора конфигурационных файлов, функций для работы с фай
лами и каталогами, хранения данных, модульного тестирования,
а также клиентские библиотеки для работы с протоколами http, ftp,
imap, smtp и nntp и многое другое. Сразу после установки Python мо
дули поддержки всех этих функциональных особенностей могут им
портироваться вашими сценариями по мере необходимости. В вашем
распоряжении имеются все перечисленные здесь функциональные
возможности. Весьма впечатляет, что все это поставляется в составе
Python и вам не требуется приобретать чтото еще. Все эти возможно
сти будут чрезвычайно полезны вам при создании своих собственных
программ на языке Python.
Простой доступ к огромному количеству пакетов сторонних произво
дителей – еще одно важное преимущество Python. Помимо множества
библиотек, входящих в стандартную библиотеку языка Python, суще
ствует большое число библиотек и утилит, доступных в Интернете, ко
торые устанавливаются одной командой. В Интернете, по адресу http://
pypi.python.org, существует каталог пакетов Python (Python Package
Index, PyPI), где любой желающий может выкладывать свои пакеты
в общее пользование. К моменту, когда писались эти строки, для за
грузки было доступно более 3800 пакетов. В составе пакетов присутст
вуют IPython, который будет рассматриваться в следующей главе,
Storm (модуль объектнореляционного отображения, который будет
рассматриваться в главе 12) и Twisted, сетевая платформа, которая бу
дет рассматриваться в главе 5, – это только 3 названия из более чем

Почему Python? 27
3800 пакетов. Начав пользоваться PyPI, вы обнаружите, что он совер
шенно необходим вам для поиска и установки полезных пакетов.
Многие из преимуществ языка Python проистекают из его базовой фи
лософии. Если в строке приглашения к вводу Python ввести команду
import this, перед вами появится так называемый «Дзен языка Py
thon» Тима Петерса (Tim Peters):
In [1]: import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one and preferably only one obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea let's do more of those!
( Перевод:
Красивое лучше уродливого.
Явное лучше неявного.
Простое лучше сложного.
Сложное лучше усложненного.
Плоское лучше вложенного.
Разрежённое лучше плотного.
Удобочитаемость важна.
Частные случаи не настолько существенны, чтобы нарушать правила.
Однако практичность важнее чистоты.
Ошибки никогда не должны замалчиваться.
За исключением замалчивания, которое задано явно.
В случае неоднозначности сопротивляйтесь искушению угадать.
Должен существовать один, и, желательно, только один очевидный
способ сделать это.
Хотя он может быть с первого взгляда не очевиден, если ты не голландец.
Сейчас лучше, чем никогда.
Однако, никогда чаще лучше, чем *прямо* сейчас.
Если реализацию сложно объяснить, — это плохая идея.
Если реализацию легко объяснить, — это может быть хорошая идея.
Пространства имён — великолепная идея, их должно быть много!
)

28 Глава 1. Введение
Этот свод правил – не догма, следование которой считается обязатель
ным на всех уровнях разработки языка, но сам дух его, кажется, про
питывает почти все, что происходит в языке и с языком. И мы счита
ем, что это замечательно. Возможно, именно поэтому мы день за днем
стремимся использовать Python. Эта философия совпадает с тем, чего
мы ждем от языка. И если она совпадает с вашими ожиданиями, зна
чит язык Python будет хорошим выбором и для вас.
Мотивация
Если вы только что взяли эту книгу в свои руки в книжном магазине
или читаете введение гденибудь в Интернете, вы, возможно, задаете
себе вопрос – насколько сложно будет изучить язык Python и стоит ли
это делать. Несмотря на растущую популярность Python, многие сис
темные администраторы до сих пор используют в своей работе только
Bash и Perl. Если вы относитесь к их категории, вас наверняка обраду
ет тот факт, что язык Python очень прост в изучении. Хотя это вопрос
личного мнения, но многие убеждены в том, что это самый простой
язык для изучения и преподавания, и точка!
Если вы уже знакомы с языком Python или считаете себя гуру в про
граммировании на другом языке, вы наверняка сможете прямо сейчас
перейти к любой из следующих глав, не читая это введение, и сумеете
разобраться в наших примерах. Мы старались создавать примеры, ко
торые действительно помогут вам выполнять свою работу. В книге
имеются примеры способов автоматического обнаружения и монито
ринга подсетей с помощью SNMP, преобразования в интерактивную
оболочку Python под названием IPython, организации конвейерной
обработки данных, инструментов управления метаданными с помо
щью средств объектнореляционного отображения, сетевых приложе
ний, инструментов командной строки и многого другого.
Если у вас имеется опыт программирования на языке командной обо
лочки, вам тоже нет причин волноваться. Вы также легко и быстро ос
воите Python. Вам нужно лишь желание учиться, определенная доля
любопытства и решимость – те же факторы, которые побудили вас
взять в руки эту книгу и прочитать введение.
Мы понимаем, что среди вас есть и скептики. Возможно, часть из того,
что вы слышали о программировании, напугала вас. Существует одно
общее, глубоко неверное заблуждение, что программированию могут
научиться не все, а только избранная таинственная элита. На самом
деле любой желающий может научиться программировать. Второе, не
менее ложное заблуждение состоит в том, что только получение про
фессионального образования в области информатики открывает чело
веку путь к званию программиста. Однако у некоторых самых талант
ливых программистов нет диплома об образовании в данной области.
Среди компетентных программистов на языке Python имеются люди

Основы 29
с профессиональной подготовкой в области философии, журналисти
ки, диетологии и английского языка. Наличие специального образова
ния не является обязательным требованием для освоения Python, хотя
оно и не повредит.
Еще одно забавное и такое же неверное представление заключается
в том, что программированию можно учиться только в подростковом
возрасте. Хотя это позволяет хорошо себя чувствовать людям, кото
рым посчастливилось встретить в юности когото, кто вдохновил их
заняться программированием, тем не менее, это еще один миф. Очень
полезно начинать изучение программирования в юном возрасте, но
возраст не является препятствием к освоению языка Python. Освоение
Python – это не «игрушка для молодежи», как иногда говорят. Среди
разработчиков существует бесчисленное множество людей, которые
осваивали программирование, будучи в возрасте старше 20, 30, 40 лет
и даже больше.
Если вы добрались до этого места, то надо заметить, что у вас, уважае
мый читатель, есть одно преимущество, которое отсутствует у многих.
Если вы решили взять в руки книгу, рассказывающую об использова
нии языка Python в системном администрировании UNIX и Linux,
значит, вы уже представляете себе, как выполнять команды в команд
ной оболочке. Это огромное преимущество для того, кто решил стать
программистом на языке Python. Наличие знаний о способах выполне
ния команд в терминале – это все, что необходимо для этого введения
в Python. Если вы уверены, что научитесь программированию на язы
ке Python, тогда сразу же переходите к следующему разделу. Если
у вас еще есть сомнения, тогда прочитайте этот раздел еще раз и убеди
те себя в том, что вы в силах овладеть программированием на языке
Python. Это действительно просто, и если вы примете такое решение,
оно изменит вашу жизнь.
Основы
Это введение в язык Python сильно отличается от любого другого, т. к.
мы будем использовать интерактивную оболочку под названием IPy
thon и обычную командную оболочку Bash. Вы должны будете от
крыть два окна терминалов, одно – с командной оболочкой Bash и дру
гое – с интерактивной оболочкой IPython. В каждом примере мы бу
дем сравнивать, как выполняются одни и те же действия с помощью
Python и с помощью Bash. Для начала загрузите требуемую версию
интерактивной оболочки IPython для своей платформы и установите
ее. Получить ее можно на странице http://ipython.scipy.org/moin/
Download. Если по какимто причинам вы не можете получить и уста
новить IPython, то можно использовать обычную интерактивную обо
лочку интерпретатора Python. Вы можете также загрузить копию вир
туальной машины, включающую в себя все примеры программ из кни
ги, а также предварительно настроенную и готовую к работе интер

30 Глава 1. Введение
активную оболочку IPython. Вам достаточно просто ввести команду
ipython, и вы попадете в строку приглашения к вводу.
Как только оболочка IPython будет установлена и запущена, вы долж
ны увидеть примерно следующее:
[ngift@Macintosh 7][H:10679][J:0]# ipython
Python 2.5.1 (r251:54863, Jan 17 2008, 19:35:17)
Type "copyright", "credits" or "license" for more information.
IPython 0.8.2 An enhanced Interactive Python.
? > Introduction and overview of IPython's features.
%quickref > Quick reference.
help > Python's own help system.
object? > Details about 'object'. ?object also works, ?? prints more.
In [1]:
Интерактивная оболочка IPython напоминает обычную командную
оболочку Bash и может выполнять такие команды, как ls, cd и pwd,
а в следующей главе приведены более подробные сведения о IPython.
Эта глава посвящена изучению Python.
В окне терминала с интерактивной оболочкой Python введите следую
щую команду:
In [1]: print "I can program in Python"
I can program in Python
В окне терминала с командной оболочкой Bash введите следующую ко
манду:
[ngift@Macintosh 7][H:10688][J:0]# echo "I can program in Bash"
I can program in Bash
В этих двух примерах не ощущается существенных различий между
Python и Bash. Мы надеемся, что это поможет лишить Python части
его загадочности.
Выполнение инструкций в языке Python
Если вам приходится тратить значительную часть времени на ввод ко
манд в окне терминала, значит, вам уже приходилось выполнять инст
рукции и, возможно, перенаправлять вывод в файл или на вход другой
команды UNIX. Рассмотрим порядок выполнения команды в Bash,
а затем сравним его с порядком, принятым в Python. В окне терминала
с командной оболочкой Bash введите следующую команду:
[ngift@Macintosh 7][H:10701][J:0]# ls l /tmp/
total 0
rw r r 1 ngift wheel 0 Apr 7 00:26 file.txt
В окне терминала с интерактивной оболочкой Python введите следую
щую команду:

Выполнение инструкций в языке Python 31
In [2]: import subprocess
In [3]: subprocess.call(["ls"," l ","/tmp/"])
total 0
rw r r 1 ngift wheel 0 Apr 7 00:26 file.txt
Out[3]: 0
Пример с командной оболочкой Bash не нуждается в пояснениях, так
как это обычная команда ls, но, если ранее вам никогда не приходи
лось сталкиваться с программным кодом на языке Python, пример
с интерактивной оболочкой Python наверняка покажется вам немного
странным. Вы могли бы подумать: «Это еще что за команда import sub
process?». Одна из самых важных особенностей Python заключается
в его возможности импортировать модули или другие файлы, содер
жащие программный код и используемые в новых программах. Если
вам знакома инструкция source в Bash, то вы увидите определенное
сходство с ней. В данном конкретном случае важно понять, что вы им
портировли модуль subprocess и использовали его посредством син
таксической конструкции, показанной выше. Подробнее о том, как
работают subprocess и import, мы расскажем позже, а пока не будем за
думываться о том, почему эта инструкция работает, и обратимся к сле
дующей строке:
subprocess.call(["команда", "аргумент", "другой_аргумент_или_путь"])
Из Python можно выполнить любую команду оболочки, как если бы
она выполнялась командной оболочкой Bash. Учитывая эту информа
цию, можно сконструировать версию команды ls на языке Python. От
кройте предпочтительный текстовый редактор или новую вкладку
в окне терминала, создайте файл с именем pyls.py и сделайте его вы
полняемым с помощью команды chmod +x pyls.py. Содержимое файла
приводится в примере 1.1.
Пример 1.1. Версия команды ls на языке Python
#!/usr/bin/env python
#Версия команды ls на языке Python
import subprocess
subprocess.call(["ls"," l"])
Если теперь запустить этот сценарий, вы получите тот же самый ре
зультат, что и при запуске команды ls
–l из командной строки:
[ngift@Macintosh 7][H:10746][J:0]# ./pyls.py
total 8
rwxr xr x 1 ngift staff 115 Apr 7 12:57 pyls.py
Этот пример может показаться глупым (да он таким и является), но он
наглядно демонстрирует типичное применение Python в системном ад
министрировании. Язык Python часто используется для «обертыва
ния» других сценариев или команд UNIX. Теперь вы уже смогли бы

32 Глава 1. Введение
начать писать некоторые несложные сценарии, помещая в файл ко
манды одну за другой. Рассмотрим простые примеры, которые реали
зованы именно таким способом. Вы можете либо просто скопировать
содержимое примера 1.2, либо выполнить сценарии psysinfo.py и bash>
sysinfo.py, которые можно найти в примерах к этой главе.
Пример 1.2. Сценарий получения информации о системе – Python
#!/usr/bin/env python
#Сценарий сбора информации о системе
import subprocess
#Команда 1
uname = "uname"
uname_arg = " a"
print "Gathering system information with %s command:\n" % uname
subprocess.call([uname, uname_arg])
#Команда 2
diskspace = "df"
diskspace_arg = " h"
print "Gathering diskspace information %s command:\n" % diskspace
subprocess.call([diskspace, diskspace_arg])
Пример 1.3. Сценарий получения информации о системе – Bash
#!/usr/bin/env bash
#Сценарий сбора информации о системе
#Команда 1
UNAME="uname a"
printf "Gathering system information with the $UNAME command: \n\n"
$UNAME
#Команда 2
DISKSPACE="df h"
printf "Gathering diskspace information with the $DISKSPACE command: \n\n"
$DISKSPACE
Если внимательно рассмотреть оба сценария, можно заметить, что они
очень похожи. А если запустить их, будут получены идентичные ре
зультаты. Маленькое примечание: передавать в функции subpro
cess.call команду отдельно от аргумента совершенно необязательно.
Можно использовать, например, такую форму записи:
subprocess.call("df h", shell=True)
Все замечательно, но мы еще не объяснили, как действует инструкция
import и что из себя представляет модуль subprocess. В версии сценария
на языке Python мы выполнили импортирование модуля subprocess,
т. к. он содержит программный код, позволяющий выполнять вызов
команд системы.

Выполнение инструкций в языке Python 33
Как уже упоминалось ранее, импортируя модуль subprocess, мы просто
импортируем файл, содержащий необходимый нам программный код.
Вы можете создать свой собственный модуль, или файл, и неоднократ
но использовать написанный вами программный код, импортируя его
точно так же, как мы импортировали программный код из модуля sub
process. В импортировании нет ничего необычного, просто в результа
те этой операции вы получаете в свое распоряжение файл с некоторым
программным кодом в нем. Одна из замечательных особенностей инте
рактивной оболочки IPython состоит в ее способности заглядывать
внутрь модулей и файлов и получать списки доступных атрибутов. Ес
ли говорить терминами UNIX, это напоминает действие команды ls
в каталоге /usr/bin. Например, если вы оказались в новой системе, та
кой как Ubuntu или Solaris, а привыкли работать с Red Hat, то вы мо
жете выполнить команду ls в каталоге /usr/bin, чтобы узнать – имеет
ся ли в наличии такой инструмент, как wget, curl или lynx. Если вы
хотите воспользоваться инструментом, находящимся в каталоге /usr/
bin, можно просто ввести команду /usr/bin/wget, например.
Ситуация с модулями, такими как subprocess, очень похожа на описан
ную выше. В интерактивной оболочке IPython можно использовать
функцию автодополнения, чтобы увидеть, какие инструменты доступ
ны внутри модуля. Воспользуемся возможностью автодополнения и по
смотрим, какие атрибуты имеются внутри модуля subprocess. Не забы
вайте, что модуль – это всего лишь файл с некоторым программным
кодом внутри него. Ниже показано, что возвращает функция автодо
полнения в IPython для модуля subprocess:
In [12]: subprocess.
subprocess.CalledProcessError subprocess.__hash__ subprocess.call
subprocess.MAXFD subprocess.__init__ subprocess.check_call
subprocess.PIPE subprocess.__name__ subprocess.errno
subprocess.Popen subprocess.__new__ subprocess.fcntl
subprocess.STDOUT subprocess.__reduce__ subprocess.list2cmdline
subprocess.__all__ subprocess.__reduce_ex__ subprocess.mswindows
subprocess.__builtins__ subprocess.__repr__ subprocess.os
subprocess.__class__ subprocess.__setattr__ subprocess.pickle
subprocess.__delattr__ subprocess.__str__ subprocess.select
subprocess.__dict__ subprocess._active subprocess.sys
subprocess.__doc__ subprocess._cleanup subprocess.traceback
subprocess.__file__ subprocess._demo_posix subprocess.types
subprocess.__getattribute__ subprocess._demo_windows
Чтобы воспроизвести этот эффект, вам нужно просто ввести команду:
import subprocess
затем ввести:
subprocess.

34 Глава 1. Введение
и нажать клавишу Ta b, чтобы активизировать функцию автодополне
ния, которая выведет список доступных атрибутов. В третьей колонке
нашего примера можно заметить subprocess.call. Теперь, чтобы полу
чить дополнительную информацию об использовании subprocess.call,
введите команду:
In [13]: subprocess.call?
Type: function
Base Class:
String Form:
Namespace: Interactive
File: /System/Library/Frameworks/Python.framework/Versions/2.5/lib/
python2.5/
subprocess.py
Definition: subprocess.call(*popenargs, **kwargs)
Docstring:
Run command with arguments. Wait for command to complete, then
return the returncode attribute.
(Запускает команду с аргументами. Ожидает ее завершения и возвращает
атрибут returncode)
The arguments are the same as for the Popen constructor. Example:
(Аргументы те же, что и в конструкторе Popen. Например:)
retcode = call(["ls", " l"])
Символ вопросительного знака в данном случае трактуется как обра
щение к странице справочного руководства. Когда требуется узнать,
как работает некоторый инструмент в системе UNIX, достаточно вве
сти команду:
man имя_инструмента
То же и с атрибутом внутри модуля, таким как subprocess.call. Когда
в оболочке IPython после имени атрибута вводится вопросительный
знак, выводится документация, которая была включена в атрибут. Ес
ли подобную операцию выполнить с атрибутами из стандартной биб
лиотеки, вы сможете обнаружить достаточно полезную информацию
по их использованию. Имейте в виду, что существует также возмож
ность обратиться к документации с описанием стандартной библиоте
ки языка Python.
Когда мы смотрим на это описание, в стандартный раздел «Docstring»,
мы видим пример использования атрибута subprocess.call и описание
того, что он делает.
Итог
Теперь вы обладаете объемом знаний, достаточным, чтобы называть
себя программистом на языке Python. Вы знаете, как написать про
стейший сценарий на языке Python, как перевести сценарий с языка
Bash на язык Python, и наконец, вы знаете, как отыскать описание мо

Использование функций в языке Python 35
дулей и атрибутов. В следующем разделе вы узнаете, как организовать
эти простые последовательности команд в функции.
Использование функций в языке Python
В предыдущем разделе мы узнали, как выполняются инструкции, что
само по себе весьма полезно, т. к. это означает, что мы в состоянии ав
томатизировать выполнение некоторых операций, которые раньше
выполнялись вручную. Следующим шагом к нашему программному
коду автоматизации будет создание функций. Если вы еще не знакомы
с функциями в языке Bash или в какомлибо другом языке програм
мирования, то просто представляйте их себе как минисценарии.
Функции позволяют создавать блоки инструкций, которые работают
в группе. Это немного похоже на сценарий Bash с двумя командами,
написанный нами ранее. Одно из отличий состоит в том, что вы може
те включить в сценарий множество функций. В конечном счете можно
весь программный код сценария расположить в функциях и затем за
пускать эти минипрограммы в нужное время в своем сценарии.
Теперь настало время поговорить об отступах. В языке Python строки,
принадлежащие одному и тому же блоку программного кода, должны
иметь одинаковые отступы. В других языках, таких как Bash, когда
определяется функция, ее тело заключается в фигурные скобки. В язы
ке Python все строки в скобках должны иметь одинаковые отступы.
Это может сбивать с толку тех, кто только начинает изучать язык, но
через некоторое время это войдет в привычку и вы заметите, что вы
полнение этого требования повышает удобочитаемость программного
кода. Если при работе с какимилибо примерами из книги у вас появ
ляются ошибки, убедитесь для начала, что в исходных текстах пра
вильно соблюдены отступы. Обычно один шаг отступа принимают рав
ным четырем пробелам.
Рассмотрим, как работают функции в языке Python и Bash. Если у вас
попрежнему открыта интерактивная оболочка IPython, вы можете не
создавать файл сценария на языке Python, хотя это и не возбраняется.
Просто введите следующий текст в строке приглашения оболочки IPy
thon:
In [1]: def pyfunc():
...: print "Hello function"
...:
...:
In [2]: pyfunc
Out[2]:
In [3]: pyfunc()
Hello function
In [4]: for i in range(5):

36 Глава 1. Введение
...: pyfunc()
...:
...:
Hello function
Hello function
Hello function
Hello function
Hello function
В этом примере инструкция print помещена в функцию. Теперь можно
не только вызвать эту функцию позднее, но и вызвать ее столько раз,
сколько потребуется. В строке [4] была использована идиома (прием)
для выполнения функции пять раз. Если раньше вам такой прием не
встречался, постарайтесь понять, что он вызывает функцию пять раз.
То же самое можно сделать непосредственно в командной оболочке
Bash. Ниже демонстрируется один из способов:
bash 3.2$ function shfunc()
> {
> printf "Hello function\n"
> }
bash 3.2$ for (( i=0 ; i < 5 ; i++))
> do
> shfunc
> done
Hello function
Hello function
Hello function
Hello function
Hello function
В примере на языке Bash была создана простая функции shfunc, кото
рая затем была вызвана пять раз, точно так же, как это было сделано
ранее с функцией в примере на языке Python. Примечательно, что
в примере на языке Bash потребовалось больше «багажа», чтобы реа
лизовать то же самое, что и на языке Python. Обратите внимание на от
личия цикла for в языке Bash от цикла for в языке Python. Если это
ваша первая встреча с функциями в Bash или Python, вам следует по
упражняться в создании какихнибудь других функций в окне IPy
thon, прежде чем двигаться дальше.
В функциях нет ничего таинственного, и попытка написать несколько
функций в интерактивной оболочке поможет ликвидировать налет та
инственности в случае, если это ваш первый опыт работы с функция
ми. Ниже приводится пара примеров простых функций:
In [1]: def print_many():
...: print "Hello function"
...: print "Hi again function"
...: print "Sick of me yet"
...:

Использование функций в языке Python 37
...:
In [2]: print_many()
Hello function
Hi again function
Sick of me yet
In [3]: def addition():
...: sum = 1+1
...: print "1 + 1 = %s" % sum
...:
...:
In [4]: addition()
1 + 1 = 2
Итак, у нас за плечами имеется несколько простейших примеров кро
ме тех, что вы попробовали выполнить сами, не правда ли? Теперь мы
можем вернуться к сценарию, который собирает информацию о систе
ме и реализовать его с применением функций, как показано в приме
ре 1.4.
Пример 1.4. Преобразованный сценарий сбора информации о системе
на языке Python: pysysinfo_func.py
#!/usr/bin/env python
#Сценарий сбора информации о системе
import subprocess
#Команда 1
def uname_func():
uname = "uname"
uname_arg = " a"
print "Gathering system information with %s command:\n" % uname
subprocess.call([uname, uname_arg])
#Команда 2
def disk_func():
diskspace = "df"
diskspace_arg = " h"
print "Gathering diskspace information %s command:\n" % diskspace
subprocess.call([diskspace, diskspace_arg])
#Главная функция, которая вызывает остальные функции
def main():
uname_func()
disk_func()
main()
Учитывая наши эксперименты с функциями, можно сказать, что пре
образование предыдущей версии сценария вылилось в то, что мы про
сто поместили инструкции внутрь функций и затем организовали их
вызов с помощью главной функции. Если вы не знакомы с подобным

38 Глава 1. Введение
стилем программирования, тогда, возможно, вы не знаете, что это дос
таточно распространенный прием, когда внутри сценария создается
несколько функций, а затем они вызываются из одной главной функ
ции. Одна из множества причин для такой организации состоит в том,
что, когда вы решите использовать этот сценарий с другой програм
мой, вы сможете выбирать, вызывать ли функции по отдельности или
с помощью главной функции. Суть в том, что решение принимается
после того, как модуль будет импортирован.
Когда нет никакого управления потоком выполнения или главной
функции, весь программный код выполняется немедленно, во время
импортирования модуля. Это может быть и неплохо для одноразовых
сценариев, но если вы предполагаете создавать инструменты много
кратного пользования, тогда лучше будет использовать функции, ко
торые заключают в себе определенные действия, и предусматривать
создание главной функции, которая будет выполнять всю программу
целиком.
Для сравнения также используем функции для предыдущего сцена
рия на языке Bash, выполняющего сбор информации о системе, как
показано в примере 1.5.
Пример 1.5. Преобразованный сценарий сбора информации о системе
на языке Bash: bashsysinfo_func.sh
#!/usr/bin/env bash
#Сценарий сбора информации о системе
#Команда 1
function uname_func ()
{
UNAME="uname a"
printf "Gathering system information with the $UNAME command: \n\n"
$UNAME
}
#Команда 2
function disk_func ()
{
DISKSPACE="df h"
printf "Gathering diskspace information with the $DISKSPACE command:
\n\n"
$DISKSPACE
}
function main ()
{
uname_func
disk_func
}
main

Повторное использование программного кода с помощью инструкции import 39
Взглянув на наш пример на языке Bash, можно заметить немало схо
жего с аналогичным ему сценарием на языке Python. Здесь также соз
даны две функции, которые затем вызываются из главной функции.
Если это ваш первый опыт работы с функциями, то мы могли бы поре
комендовать вам закомментировать вызов главной функции в обоих
сценариях, поставив в начале строки символ решетки (#), и попробо
вать запустить их еще раз. На этот раз в результате запуска сценариев
вы не должны получить ровным счетом ничего, потому что программа
хотя и выполняется, но она не вызывает две свои функции.
Теперь вы можете считать себя программистом, способным писать
простые функции на обоих языках, Bash и Python. Программисты
учатся работая, поэтому сейчас мы настоятельно рекомендуем вам из
менить в обеих программах, на языке Bash и Python, вызовы систем
ных команд своими собственными. Прибавьте себе несколько очков,
если вы добавили в сценарии несколько новых функций и предусмот
рели их вызов из главной функции.
Повторное использование программного
кода с помощью инструкции import
Одна из проблем с освоением чеголибо нового состоит в том, что если
это новое достаточно абстрактная вещь, бывает очень сложно найти ей
применение. Когда в последний раз вам приходилось применять зна
ние математики, полученное в средней школе, в продуктовом магази
не? В предыдущих примерах было показано, как создавать функции,
которые представляют альтернативу простому последовательному вы
полнению команд оболочки. Мы также сообщили, что модуль – это
обычный сценарий или некоторое количество строк программного ко
да в файле. В этом подходе нет ничего сложного, но программный код
должен быть организован определенным способом, чтобы его можно
было повторно использовать в будущих программах. В этом разделе
мы покажем вам, почему это так важно. Давайте импортируем оба
предыдущих сценария сбора информации о системе и выполним их.
Откройте окна с IPython и Bash, если вы закрыли их, чтобы мы могли
быстро продемонстрировать, почему функции играют такую важную
роль с точки зрения повторного использования программного кода.
Один из наших первых сценариев на языке Python представлял собой
простую последовательность команд в файле с именем pysysinfo.py.
В языке Python файл является модулем и наоборот, поэтому мы мо
жем импортировать этот файл сценария в оболочку IPython. Обратите
внимание, вы никогда не должны указывать расширение .py файла
в инструкции импорта. Фактически попытка импорта окончится не
удачей, если расширение будет указано. Итак, мы выполнили импорт
сценария на ноутбуке Ноа Macbook Pro:

40 Глава 1. Введение
In [1]: import pysysinfo
Gathering system information with uname command:
Darwin Macintosh 8.local 9.2.2 Darwin Kernel Version 9.2.2: /
Tue Mar 4 21:17:34 PST 2008; root:xnu 1228.4.31~1/RELEASE_I386 i386
Gathering diskspace information df command:
Filesystem Size Used Avail Capacity Mounted on
/dev/disk0s2 93Gi 88Gi 4.2Gi 96% /
devfs 110Ki 110Ki 0Bi 100% /dev
fdesc 1.0Ki 1.0Ki 0Bi 100% /dev
map hosts 0Bi 0Bi 0Bi 100% /net
map auto_home 0Bi 0Bi 0Bi 100% /home
/dev/disk1s2 298Gi 105Gi 193Gi 36% /Volumes/Backup
/dev/disk2s3 466Gi 240Gi 225Gi 52% /Volumes/EditingDrive
Ух ты! Выглядит круто, правда? Когда импортируется файл, содержа
щий программный код на языке Python, он тут же выполняется. Но
в действительности за всем этим кроется несколько проблем. Если вы
планируете запускать такой программный код на языке Python, его
всегда придется запускать из командной строки как часть сценария
или программы, которую вы пишете. Операция импорта должна по
мочь в воплощении идеи «повторного использования программного
кода». Но вот что интересно: как быть, если нам потребуется получить
только информацию о распределении дискового пространства? В дан
ном сценарии это невозможно. Именно для этого используются функ
ции. Они позволяют контролировать, когда и какие части программы
должны выполняться, чтобы она не выполнялась целиком, как в при
мере выше. Если импортировать сценарий, где эти команды оформле
ны в виде функций, можно увидеть, что мы имеем в виду.
Ниже приводится результат импортирования сценария в терминале
IPython:
In [3]: import pysysinfo_func
Gathering system information with uname command:
Darwin Macintosh 8.local 9.2.2 Darwin Kernel Version 9.2.2: /
Tue Mar 4 21:17:34 PST 2008; root:xnu 1228.4.31~1/RELEASE_I386 i386
Gathering diskspace information df command:
Filesystem Size Used Avail Capacity Mounted on
/dev/disk0s2 93Gi 88Gi 4.2Gi 96% /
devfs 110Ki 110Ki 0Bi 100% /dev
fdesc 1.0Ki 1.0Ki 0Bi 100% /dev
map hosts 0Bi 0Bi 0Bi 100% /net
map auto_home 0Bi 0Bi 0Bi 100% /home
/dev/disk1s2 298Gi 105Gi 193Gi 36% /Volumes/Backup
/dev/disk2s3 466Gi 240Gi 225Gi 52% /Volumes/EditingDrive
Этот результат ничем не отличается от того, что был получен при ис
пользовании сценария без функций. Если вы озадачены, – это хороший
знак. Чтобы понять, почему был получен тот же самый результат, дос

Повторное использование программного кода с помощью инструкции import 41
таточно заглянуть в исходный программный код. Откройте сценарий
pysysinfo_func.py в другой вкладке или в другом окне терминала и най
дите строки:
#Главная функция, которая вызывает остальные функции
def main():
uname_func()
disk_func()
main()
Проблема в том, что функция main, созданная нами в конце предыду
щего раздела, обернулась для нас некоторой неприятностью. С одной
стороны, хотелось бы иметь возможность запускать сценарий из ко
мандной строки, чтобы получать полную информацию о системе, но
с другой стороны, нам совсем не нужно, чтобы модуль выводил чтоли
бо при импортировании. К счастью, потребность использовать модули
как в виде сценариев, выполняемых из командной строки, так и в виде
повторно используемых модулей достаточно часто встречается в языке
Python. Решение этой проблемы состоит в том, чтобы определить, ко
гда следует вызывать главную функцию, изменив последнюю часть
сценария, как показано ниже:
#Главная функция, которая вызывает остальные функции
def main():
uname_func()
disk_func()
if __name__ == "__main__":
main()
Эта «идиома» представляет прием, который обычно используется для
решения данной проблемы. Любой программный код, входящий в со
став блока этой условной инструкции, будет выполняться, только ко
гда модуль запускается из командной строки. Чтобы убедиться в этом,
измените окончание своего сценария или импортируйте исправлен
ную его версию pysysinfo_func_2.py.
Если теперь вернуться к оболочке IPython и импортировать новый
сценарий, вы должны увидеть следующее:
In [1]: import pysysinfo_func_2
На этот раз благодаря нашим исправлениям функция main вызвана не
была. Итак, вернемся вновь к теме повторного использования про
граммного кода: у нас имеется три функции, которые можно использо
вать в других программах или вызывать в интерактивной оболочке
IPython. Вспомните: ранее мы говорили, что было бы неплохо иметь
возможность вызвать только функцию, которая выводит информацию
о распределении дискового пространства. Сначала необходимо вновь
вернуться к одной из возможностей оболочки IPython, которую мы уже
демонстрировали ранее. Вспомните, как мы использовали клавишу Ta b

42 Глава 1. Введение
для получения полного списка атрибутов модуля, доступных для ис
пользования. Ниже показано, как выглядит этот список для нашего
модуля:
In [2]: pysysinfo_func_2.
pysysinfo_func_2.__builtins__ pysysinfo_func_2.disk_func
pysysinfo_func_2.__class__ pysysinfo_func_2.main
pysysinfo_func_2.__delattr__ pysysinfo_func_2.py
pysysinfo_func_2.__dict__ pysysinfo_func_2.pyc
pysysinfo_func_2.__doc__ pysysinfo_func_2.subprocess
pysysinfo_func_2.__file__ pysysinfo_func_2.uname_func
pysysinfo_func_2.__getattribute__
pysysinfo_func_2.__hash__
В этом примере пока можно проигнорировать все имена, содержащие
двойные символы подчеркивания, потому что они представляют спе
циальные методы, описание которых выходит далеко за рамки этого
введения. Поскольку IPython – это обычная командная оболочка, она
обнаружила файл с расширением .pyc, содержащий скомпилирован
ный байткод Python. Отбросив все эти ненужные имена, можно заме
тить в списке имя pysysinfo_func_2.disk_func. Попробуем вызвать ее:
In [2]: pysysinfo_func_2.disk_func()
Gathering diskspace information df command:
Filesystem Size Used Avail Capacity Mounted on
/dev/disk0s2 93Gi 88Gi 4.2Gi 96% /
devfs 110Ki 110Ki 0Bi 100% /dev
fdesc 1.0Ki 1.0Ki 0Bi 100% /dev
map hosts 0Bi 0Bi 0Bi 100% /net
map auto_home 0Bi 0Bi 0Bi 100% /home
/dev/disk1s2 298Gi 105Gi 193Gi 36% /Volumes/Backup
/dev/disk2s3 466Gi 240Gi 225Gi 52% /Volumes/EditingDrive
К настоящему моменту вы вероятно уже заметили, что функция «вы
зывается», или запускается, за счет указания круглых скобок «()» по
сле ее имени. В данном случае мы использовали одну функцию из фай
ла, содержащего три функции: disk_func, uname_func и, наконец, main.
Ага! Мы всетаки сумели найти способ повторного использования на
шего программного кода. Мы импортировали модуль, написанный на
ми ранее, и в интерактивной оболочке выполнили только ту его часть,
которая была необходима. Безусловно, мы точно так же можем запус
тить и две другие функции. Давайте посмотрим на это:
In [3]: pysysinfo_func_2.uname_func()
Gathering system information with uname command:
Darwin Macintosh 8.local 9.2.2 Darwin Kernel Version 9.2.2:
Tue Mar 4 21:17:34 PST 2008; root:xnu 1228.4.31~1/RELEASE_I386 i386
In [4]: pysysinfo_func_2.main()
Gathering system information with uname command:

Повторное использование программного кода с помощью инструкции import 43
Darwin Macintosh 8.local 9.2.2 Darwin Kernel Version 9.2.2:
Tue Mar 4 21:17:34 PST 2008; root:xnu 1228.4.31~1/RELEASE_I386 i386
Gathering diskspace information df command:
Filesystem Size Used Avail Capacity Mounted on
/dev/disk0s2 93Gi 88Gi 4.2Gi 96% /
devfs 110Ki 110Ki 0Bi 100% /dev
fdesc 1.0Ki 1.0Ki 0Bi 100% /dev
map hosts 0Bi 0Bi 0Bi 100% /net
map auto_home 0Bi 0Bi 0Bi 100% /home
/dev/disk1s2 298Gi 105Gi 193Gi 36% /Volumes/Backup
/dev/disk2s3 466Gi 240Gi 225Gi 52% /Volumes/EditingDrive
Если вы были внимательны, вы должны были заметить, что были вы
званы обе оставшиеся функции. Не забывайте, что функция main за
пускает две другие функции.
Часто требуется взять из модуля только часть его программного кода
и повторно использовать его в другом сценарии. Поэтому попробуем
написать еще один сценарий, который использует только одну из
функций. Пример такого сценария приводится в примере 1.6.
Пример 1.6. Повторное использование программного кода посредством
импортирования: new_pysysinfo
#Очень короткий сценарий, использующий программный код из pysysinfo_func_2
from pysysinfo_func_2 import disk_func
import subprocess
def tmp_space():
tmp_usage = "du"
tmp_arg = " h"
path = "/tmp"
print "Space used in /tmp directory"
subprocess.call([tmp_usage, tmp_arg, path])
def main():
disk_func()
tmp_space()
if __name__ == "__main__":
main()
В этом примере мы не только повторно использовали программный
код, написанный ранее, но и использовали специальную синтаксиче
скую конструкцию, которая позволяет импортировать только необхо
димые функции. Вся прелесть повторного использования программно
го кода состоит в том, что мы можем создавать совершенно новые про
граммы, просто импортируя функции из других программ. Обратите
внимание, что в функции main вызывается функция disk_func() из дру
гого модуля, созданного нами, и новая, только что созданная, функ
ция tmp_space() из этого файла.

44 Глава 1. Введение
В этом разделе мы узнали, насколько богатой может быть возмож
ность повторного использования программного кода, и насколько про
сто ее использовать. В двух словах: вы помещаете однудве функции
в файл и затем, если вам требуется иметь возможность запускать сце
нарий из командной строки, используете специальную синтаксиче
скую конструкцию if__name__ == "__main__":. После этого можно либо
импортировать эти функции в оболочке IPython, либо просто исполь
зовать их в другом сценарии. Обладая этой информацией, вы станови
тесь понастоящему опасными. Теперь вы можете создавать на языке
Python довольно сложные модули и использовать их снова и снова при
создании новых инструментов.

2
IPython
Одной из сильных сторон языка Python является его интерактивный
интерпретатор, или оболочка. Оболочка обеспечивает возможность
быстро проверить идею, протестировать функциональные возможно
сти и интерфейсы модулей, с которыми вы работаете, и выполнить ка
киелибо однократные действия, для которых в другом случае при
шлось бы писать сценарии из трех строк. Обычно при программирова
нии на языке Python мы открываем текстовый редактор и интерактив
ную оболочку Python (в действительности оболочку IPython, но к этому
мы вскоре еще вернемся), взаимодействуя с ними обоими, переключа
ясь взадвперед между оболочкой и редактором, часто копируя фраг
менты программного кода из одного окна в другое. При таком подходе
мы можем быстро проверять работу программного кода в интерпрета
торе и вставлять работоспособные и отлаженные фрагменты в тексто
вом редакторе.
В своей основе IPython представляет собой интерактивную оболочку
Python. Эта удивительная оболочка обладает намного более широкими
возможностями по сравнению со стандартной интерактивной оболоч
кой Python. Она позволяет создавать командные окружения, настраи
ваемые в весьма широких пределах; дает возможность встраивать ин
терактивную оболочку Python в любое приложение, написанное на
языке Python и, с определенными ограничениями, может даже ис
пользоваться в качестве системной командной оболочки. В этой главе
мы остановимся на использовании IPython с целью повышения эффек
тивности решения задач, связанных с программированием на языке
Python в *nixоболочках.
За оболочкой IPython стоит весьма сплоченное сообщество. Вы можете
подписаться на почтовую рассылку на странице http://lists.ipython.sci>
py.org/mailman/listinfo/ipython>user. Существует замечательная стра
ница вики (wiki) http://ipython.scipy.org/moin. И как часть этой стра

46 Глава 2. IPython
ницы – сборник рецептов http://ipython.scipy.org/moin/Cookbook. На
любом из этих ресурсов вы можете читать информацию или выклады
вать свою. Еще одна область, где вы можете попробовать приложить
свои знания и умения, – это разработка IPython. Недавно разработка
IPython была переведена на использование распределенной системы
управления версиями исходных текстов, благодаря которой вы може
те получить срез исходных текстов и приступить к их изучению. Если
вы сделаете чтото, что может пригодиться другим, вы можете пере
дать им свои изменения.
Установка IPython
Существует несколько вариантов установки IPython. Первый, самый
традиционный, заключается в получении дистрибутива с исходными
текстами. Страница, откуда можно загрузить IPython, находится по
адресу http://ipython.scipy.org/dist/. К моменту написания этих строк
последней была версия IPython 0.8.2 и близилась к завершению работа
над версией 0.8.3. Чтобы установить IPython из исходных текстов, от
кройте страницу http://ipython.scipy.org/dist/ipython>0.8.2.tar.gz и за
грузите файл .tar.gz. Распаковать этот файл можно с помощью коман
ды tar zxvf ipython
–0.8.2.tar.gz. В разархивированном каталоге будет
ПОРТ РЕТ ЗНА МЕ НИ ТО СТИ: ПРО ЕКТ IPYTHON
Фернандо Перез (Fernando Perez)
Фернандо Перез – кандидат физикоматематиче
ских наук, занимался разработкой числовых ал
горитмов на кафедре прикладной математики
университета в штате Колорадо. В настоящее вре
мя занимается научными изысканиями в инсти
туте неврологии имени Элен Уиллс (Helen Wills)
в Калифорнийском университете в городе Берк
ли, сосредоточившись на разработке новых методов анализа для
нужд моделирования мозговой деятельности и высокоуровне
вых инструментальных средств для научных вычислений. К мо
менту окончания обучения в аспирантуре он оказался вовлечен
в разработку инструментальных средств для научных расчетов
на языке Python. Он начал проект IPython в 2001 году, когда пы
тался разработать эффективный интерактивный инструмент для
решения повседневных научных задач. Благодаря расширяю
щемуся кругу разработчиков этот проект продолжал расти и за
эти годы превратился в инструмент, который будет полезен да
же программистам, далеким от научных исследований.

Установка IPython 47
содержаться файл setup.py. Вызовите интерпретатор Python, передав
ему имя файла setup.py с параметром install (например, python setup.py
install). Эта команда установит библиотеки IPython в каталог site>pack>
ages и создаст сценарий ipython в каталоге scripts. В UNIX это обычно
тот же каталог, где находится исполняемый файл интерпретатора py
thon. Если вы используете python, установленный менеджером пакетов
вашей системы, то этот файл (а, следовательно, и ipython) скорее всего
будет находиться в каталоге /usr/bin. Мы у себя установили IPython
последней версии, которая еще находилась в разработке, поэтому в не
которых примерах вы будете видеть ее номер 0.8.3.
Второй вариант установки IPython заключается в установке пакета
с помощью вашей системы управления пакетами. Для Debian и Ubuntu
имеются доступные для установки пакеты .deb. Установка выполняет
ся простой командой apt
–get install ipython. В Ubuntu библиотеки IPy
thon устанавливаются в соответствующее местоположение (/usr/share/
python>support/ipython, куда помещается набор файлов .pth и символи
ческих ссылок, обеспечивающих корректную работу пакета). Кроме
ПОРТ РЕТ ЗНА МЕ НИ ТО СТИ: ПРО ЕКТ IPYTHON
Вилле Вайнио (Ville Vainio)
Вилле Вайнио получил степень бакалавра инфор
матики в 2003 году в Сатакунтском университете
прикладных наук, на технологическом факультете
в городе Пори, Финляндия. К моменту начала ра
боты над этой книгой был нанят в качестве про
граммиста в отдел смартфонов компании Digia Plc,
где разрабатывает программное обеспечение на
языке C++ для платформы Symbian OS, разработанной компа
ниями Nokia и UIQ. Во время учебы работал программистом
в Cimcorp Oy, разрабатывал программное обеспечение на языке
Python для взаимодействия с промышленными роботами.
Вилле – давний приверженец IPython, а с 2006 года стал храни
телем стабильной ветки IPython (серия 0.x). Его работа в проек
те IPython началась с серии исправлений и улучшений IPython
в части системной командной оболочки для Windows, и эксплуа
тация этой системной командной оболочки до сих пор остается
в центре его внимания. Живет вместе со своей невестой в городе
Пори, Финляндия, и пишет дипломный проект на получение
степени магистра в местном филиале технологического универ
ситета г. Тампере, посвященный ILeo, который является про
граммным мостом между IPython и Leo и превращает Leo в ноут
бук с полноценной поддержкой IPython.

48 Глава 2. IPython
того в каталог /usr/bin/python устанавливается двоичный выполняе
мый файл ipython.
Третий вариант установки IPython заключается в использовании па
кета Python. Вы могли даже не предполагать, что в Python существует
такая вещь, как пакет, и, тем не менее, это так. Пакеты в языке Py
thon – это файлы с расширением .egg, представляющие собой архивы
формата ZIP. Пакеты можно устанавливать с помощью утилиты
easy_install. Одна из замечательных особенностей утилиты easy_in
stall заключается в том, что она проверяет центральный репозитарий
пакетов и отыскивает необходимый пакет. За кулисами происходит
несколько больше, чем только поиск пакета, однако для пользователя
установка выполняется очень просто. Репозитарий называется ката
логом пакетов Python (Python Package Index), или PyPI для краткости
(хотя некоторые нежно называют его Python CheeseShop (сырная лав
ка Python)). Чтобы воспользоваться утилитой easy_install, необходи
мо зарегистрироваться в системе под учетной записью, которая обла
дает правом записи в каталог site>packages, и запустить команду
easy_install ipython.
Четвертый вариант заключается в использовании IPython вообще без
установки. «Что?», – можете вы спросить. Дело в том, что если загру
зить дистрибутив с исходными текстами и просто запустить сценарий
ipython.py из корневого каталога с набором файлов, то вы получите ра
ботающий экземпляр загруженной версии IPython. Этот способ подой
дет тем, кто не желает загромождать свой каталог site>packages, хотя
вам придется учитывать некоторые ограничения этого варианта. Если
вы запускаете IPython из каталога, куда вы распаковали файл дистри
бутива, и при этом не изменили переменную окружения PYTHONPATH, вы
не сможете использовать этот продукт как библиотеку.
Базовые понятия
После того как оболочка IPython будет установлена, и вы в первый раз
запустите команду ipython, вы увидите примерно следующее:
jmjones@dink:~$ ipython
**********************************************************************
Welcome to IPython. I will try to create a personal configuration directory
where you can customize many aspects of IPython's functionality in:
(Добро пожаловать в IPython. Я попробую создать персональный каталог
с настройками, где вы сможете настраивать разные аспекты IPython:)
/home/jmjones/.ipython
Successful installation!
(Установка выполнена благополучно!)
Please read the sections 'Initial Configuration' and 'Quick Tips' in the
IPython manual (there are both HTML and PDF versions supplied with the

Базовые понятия 49
distribution) to make sure that your system environment is properly
configured to take advantage of IPython's features.
(Пожалуйста, прочитайте разделы 'Initial Configuration' и 'Quick Tips'
в руководстве к IPython (в состав дистрибутива входит как HTML, так и PDF
версия), чтобы суметь убедиться, что системное окружение настроено должным
образом для использования IPython)
Important note: the configuration system has changed! The old system is
still in place, but its setting may be partly overridden by the settings in
"~/.ipython/ipy_user_conf.py" config file. Please take a look at the file
if some of the new settings bother you.
(Важное примечание: файл с настройками системы был изменен! Прежняя система
осталась на месте, но ее настройки могут оказаться частично переопределенными
настройками в файле "~/.ipython/ipy_user_conf.py". Пожалуйста, загляните
в этот файл, если новые значения параметров представляют для вас интерес.)
Please press to start IPython.
(Чтобы запустить IPython, нажмите клавишу )
После нажатия на клавишу Return IPython выведет следующий текст:
jmjones@dinkgutsy:stable dev$ python ipython.py
Python 2.5.1 (r251:54863, Mar 7 2008, 03:39:23)
Type "copyright", "credits" or "license" for more information.
(Введите "copyright", "credits" или "license", чтобы получить
дополнительную информацию)
IPython 0.8.3.bzr.r96 An enhanced Interactive Python.
? > Introduction and overview of IPython's features.
(Введение и обзор возможностей IPython)
%quickref > Quick reference.
(Краткий справочник)
help > Python's own help system.
(Собственная справочная система Python)
object? > Details about 'object'. ?object also works, ?? prints more.
(Подробности об 'object'. ?object также допустимо,
?? выведет более подробную информацию)
In [1]:
Взаимодействие с IPython
Обычно, когда сталкиваешься с новой командной строкой, в первый
момент чувствуешь некоторую беспомощность. Совершенно непонят
но – что делать дальше. Помните, как вы в первый раз вошли в систе
му UNIX и столкнулись с командной оболочкой (ba|k|c|z)sh? Раз уж вы
читаете эту книгу, мы полагаем, что у вас уже имеется некоторый
опыт работы с командной оболочкой в операционной системе UNIX.
Если это действительно так, тогда обрести навыки работы в IPython
будет совсем просто.
Одна из причин, по которым непонятно, как действовать делать в обо
лочке IPython, состоит в том, что она практически не ограничивает

50 Глава 2. IPython
ваших действий. Поэтому здесь уместнее думать о том, что бы вы хоте
ли сделать. В командной оболочке IPython вам доступны все функцио
нальные возможности языка Python. Плюс несколько «магических»
функций IPython. Вы с легкостью можете запустить любую команду
UNIX в оболочке IPython и сохранить вывод в переменной Python. Сле
дующие примеры демонстрируют, что можно ожидать от IPython с на
стройками по умолчанию.
Ниже приводится пример выполнения некоторых простых операций
присваивания:
In [1]: a = 1
In [2]: b = 2
In [3]: c = 3
Пока не видно существенных отличий от стандартной командной обо
лочки интерпретатора Python, если ввести в ней те же инструкции.
Здесь мы просто присвоили значения 1, 2 и 3 переменным a, b и c, соот
ветственно. Самое большое отличие между IPython и стандартной обо
лочкой Python, которое можно наблюдать здесь, состоит в том, что
оболочка IPython выводит порядковый номер строки приглашения
к вводу.
Теперь, когда у нас имеется несколько переменных (a, b и c), которым
были присвоены значения (1, 2 и 3, соответственно), можно посмот
реть, какие именно значения они содержат:
In [4]: print a
1
In [5]: print b
2
In [6]: print c
3
Конечно, это надуманный пример, потому что мы только что ввели эти
значения, и можно было прокрутить назад окно, чтобы их увидеть.
Вывод значения каждой переменной потребовал ввода с клавиатуры
на шесть символов больше, чем это действительно необходимо. Ниже
приводится более краткий способ отображения значений переменных:
In [7]: a
Out[7]: 1
In [8]: b
Out[8]: 2
In [9]: c
Out[9]: 3
Несмотря на то, что результаты этих двух способов выглядят практи
чески одинаковыми, тем не менее, здесь имеются некоторые разли

Базовые понятия 51
чия. Инструкция print использует «неофициальное» строковое пред
ставление, тогда как при использовании одного только имени пере
менной выводится «официальное» строковое представление. Обычно
разница между этим двумя представлениями более заметна при работе
не со встроенными, а с собственными классами. Ниже приводится
пример различий между этими двумя строковыми представлениями:
In [10]: class DoubleRep(object):
....: def __str__(self):
....: return "Hi, I'm a __str__"
....: def __repr__(self):
....: return "Hi, I'm a __repr__"
....:
....:
In [11]: dr = DoubleRep()
In [12]: print dr
Hi, I'm a __str__
In [13]: dr
Out[13]: Hi, I'm a __repr__
Здесь с целью демонстрации различий между «официальным» и «не
официальным» строковыми представлениями объекта создается класс
DoubleRep, обладающий двумя методами – __str__ и __repr__. Специаль
ный метод __str__ объекта вызывается, когда требуется получить его
«неофициальное» строковое представление. Специальный метод
__repr__ объекта вызывается, когда требуется получить его «офици
альное» строковое представление. После создания экземпляра класса
DoubleRep и присваивания его в качестве значения переменной dr мы
выводим значение dr с помощью инструкции print. Вызывается метод
__str__. Затем в строке приглашения к вводу мы просто вводим имя пе
ременной, в результате чего вызывается метод __repr__. Таким обра
зом, когда мы просто вводим имя переменной, оболочка IPython выво
дит ее «официальное» строковое представление. Когда мы используем
инструкцию print, то получаем «неофициальное» строковое представ
ление. Вообще в языке Python метод __str__ вызывается, когда проис
ходит обращение к функции str(obj), которой передается желаемый
объект, или когда он используется в строке форматирования, такой
как эта: "%s" % obj. Когда происходит обращение к функции repr(obj)
или используется строка форматирования, такая как "%r" % obj, вызы
вается метод __repr__.
Как бы то ни было, такой характер поведения не является особенно
стью исключительно оболочки IPython. Это особенность поведения ин
терпретатора Python. Ниже приводится тот же самый пример использо
вания класса DoubleRep в стандартной интерактивной оболочке Python:
>>> class DoubleRep(object):
... def __str__(self):

52 Глава 2. IPython
... return "Hi, I'm a __str__"
... def __repr__(self):
... return "Hi, I'm a __repr__"
...
>>>
>>> dr = DoubleRep()
>>> print dr
Hi, I'm a __str__
>>> dr
Hi, I'm a __repr__
Вероятно, вы обратили внимание, что строки приглашения к вводу
в стандартной оболочке Python и в оболочке IPython отличаются друг
от друга. Строка приглашения в стандартной оболочке Python состоит
из трех символов «больше» (>>>), тогда как в IPython приглашение со
держит слово «In», за которым следует число в квадратных скобках
идвоеточие (In [1]:) . Оказывается, оболочка IPython запоминает ко
манды, которые вводились, и сохраняет их в списке с именем In. Так,
после присваивания значений 1, 2 и 3 переменным a, b и c в предыду
щем примере содержимое списка In выглядит следующим образом:
In [4]: print In
['\n', u'a = 1\n', u'b = 2\n', u'c = 3\n', u'print In\n']
Формат вывода результатов в IPython также отличается от вывода
в стандартной оболочке Python. Создается впечатление, что оболочка
IPython поразному выводит значения в инструкции print и вычислен
ные. В действительности же оболочка IPython не делает никаких раз
личий между этими двумя типами. Просто вызовы инструкций print
являются побочным эффектом вычислений, поэтому оболочка IPython
не видит их и не может их перехватить. Эти побочные проявления ин
струкции print отражаются только на стандартном потоке вывода std
out, куда передаются результаты запроса. Однако в ходе выполнения
программного кода пользователя IPython оболочка контролирует воз
вращаемые значения. Если возвращаемое значение не равно None, оно
выводится в строке с подсказкой Out [число]:.
В стандартной оболочке Python различия между этими двумя способа
ми вывода вообще не видны. Если инструкция, введенная в строке
приглашения IPython, вычисляет некоторое значение, отличное от
None, оболочка выведет его в строке, которая начинается со слова Out, за
которым следует число в квадратных скобках, символ двоеточия и зна
чение, вычисленное инструкцией (например, Out[1]: 1). В следующем
примере показано, как в оболочке IPython выполняется присваивание
целочисленного значения переменной, как отображается значение пе
ременной в результате ее оценки и как выводится значение этой же пе
ременной с помощью инструкции print. Сначала в оболочке IPython:
In [1]: a = 1
In [2]: a

Базовые понятия 53
Out[2]: 1
In [3]: print a
1
In [4]:
А теперь в стандартной оболочке Python:
>>> a = 1
>>> a
1
>>> print a
1
>>>
В действительности нет никаких различий между тем, как в оболоч
ках IPython и Python выполняется присваивание. Обе оболочки немед
ленно выводят приглашение к вводу. Но «официальное» строковое
представление переменной в оболочке IPython и в стандартной оболоч
ке Python отображается поразному. В оболочке IPython выводится
строкаподсказка Out, тогда как в оболочке Python просто выводится
значение переменной. В случае же использования инструкции print
никаких различий не наблюдается – в обеих оболочках выводится
только значение.
Наличие подсказок In [некоторое число]: и Out [некоторое число]: может
вызвать вопрос: имеются ли какиенибудь более глубокие различия
между IPython и стандартной оболочкой Python, или они носят исклю
чительно косметический характер. Определенно, различия намного
глубже. То есть видимые отличия от стандартной оболочки Python обу
словлены функциональными возможностями оболочки IPython.
В оболочке IPython имеются две встроенные переменные, о существо
вании вам необходимо знать. Это переменные с именами In и Out. Пер
вая из них представляет объект списка, в котором сохраняются вве
денные команды, а вторая – объект словаря. Вот что сообщает встроен
ная функция type о каждой из них:
In [1]: type(In)
Out[1]:
In [2]: type(Out)
Out[2]:
Когда вы начнете пользоваться переменными In и Out, эти различия
между ними приобретут особое значение.
Итак, что же хранится в этих переменных?
In [3]: print In
['\n', u'type(In)\n', u'type(Out)\n', u'print In\n']
In [4]: print Out
{1: , 2: }

54 Глава 2. IPython
Как и следовало ожидать, переменные In и Out содержат, соответствен
но, отличные от None ввод команд и выражений и результаты выполне
ния инструкций и выражений . Поскольку каждая строка непременно
содержит некоторый ввод, определенно имеет смысл сохранять вве
денные команды в виде такой структуры, как список. Но сохранение
вывода в виде списка может привести к появлению элементов, кото
рые содержат только значение None. Поэтому, т. к. не каждая введен
ная команда возвращает значение, отличное от None, есть смысл сохра
нять вывод в такой структуре данных, как словарь.
Дополнение
Другая невероятно полезная особенность IPython – функция дополне
ния, привязанная к клавише табуляции. Стандартная оболочка Py
thon также обладает функцией дополнения – при условии, что интер
претатор скомпилирован с поддержкой библиотеки readline, но для ее
активации необходимо выполнить следующие действия:
>>> import rlcompleter, readline
>>> readline.parse_and_bind('tab: complete')
Это позволит вам выполнять такие манипуляции, как показано ниже:
>>> import os
>>> os.lis
>>> os.listdir
>>> os.li
os.linesep os.link os.listdir
После импортирования модулей rlcompleter и readline и настройки па
раметра дополнения в модуле readline мы оказались в состоянии после
импортирования модуля os ввести os.lis, нажать клавишу Ta b один раз
и получить дополнение до os.listdir. Точно так же, после ввода os.li
мы получили список возможных вариантов дополнения, нажав клави
шу Ta b дважды.
Ту же самую функциональность можно получить в оболочке IPython,
причем для этого не требуется выполнять подготовительных опера
ций. То есть данная возможность в стандартной оболочке Python при
сутствует, а в IPython она активирована по умолчанию. Ниже приво
дится предыдущий пример, выполненный в оболочке IPython:
In [1]: import os
In [2]: os.lis
In [2]: os.listdir
In [2]: os.li
os.linesep os.link os.listdir
Обратите внимание: в последней части примера нам пришлось нажать
клавишу табуляции всего один раз.

Базовые понятия 55
Этот пример всего лишь демонстрирует возможность поиска и допол
нения атрибутов в оболочке IPython, но IPython, что может оказаться
более привлекательным, может дополнять и имена модулей в инструк
ции импортирования. Откройте новую оболочку IPython, чтобы мож
но было увидеть, как IPython помогает отыскивать импортируемый
модуль:
In [1]: import o
opcode operator optparse os os2emxpath ossaudiodev
In [1]: import xm
xml xmllib xmlrpclib
Обратите внимание, что все предлагаемые варианты дополнения явля
ются именами модулей, то есть это уже не случайное совпадение. Это
функциональная особенность.
В оболочке IPython есть два варианта дополнения: «дополнение» и «ме
ню с дополнениями». При использовании первого варианта текущее
«слово» дополняется по мере возможности и затем предлагается пере
чень альтернатив, при втором варианте слово дополняется полностью
до одной из альтернатив, а каждое последующее нажатие клавиши Ta b
предоставляет трансформацию слова до следующей альтернативы. По
умолчанию в оболочке IPython используется вариант «дополнение».
К вопросам настройки IPython мы подойдем очень скоро.
Специальная функция редактирования
Последняя тема, касающаяся вводавывода, которую мы затронем, –
это специальная функция edit. (Подробнее об специальных функциях
мы поговорим в следующем разделе.) Взаимодействие пользователя
с оболочкой, основанное на вводе строк, имеет огромное, но ограни
ченное значение. Так как это утверждение выглядит неоднозначным,
попробуем развернуть его. Возможность ввода команд по одной строке
за раз очень удобна. Вы вводите команду, а оболочка выполняет ее,
причем иногда приходится ждать некоторое время, пока команда бу
дет выполнена, а после этого вы вводите следующую команду.
В такой цикличности работы нет ничего плохого. В действительности
такой способ взаимодействия достаточно эффективен. Но иногда воз
никает необходимость ввести сразу целый блок строк. Было бы непло
хо иметь возможность использовать для этого предпочитаемый тексто
вый редактор, хотя аналогичную возможность предоставляет поддерж
ка readline в IPython. Мы уже знаем, как использовать текстовый ре
дактор для создания модулей на языке Python, но это не совсем то, что
мы имеем в виду. Мы подразумеваем некоторый компромисс между
строчноориентированным способом ввода и способом ввода в тексто
вом редакторе, который обеспечивает возможность передавать ко
мандной оболочке целые блоки строк с командами. Если можно ска
зать, что добавление поддержки возможности работы с блоками строк

56 Глава 2. IPython
совсем нелишне, это значит, что строчноориентированный интерфейс
имеет некоторые ограничения. Т. е. можно сказать, что строчноориен
тированный интерфейс исключительно удобен, и в то же время имеет
некоторые ограничения.
Специальная функция edit как раз и представляет собой упомянутый
выше компромисс между строчноориентированным способом ввода
в оболочке Python и способом ввода с привлечением текстового редак
тора. Преимущества такого компромисса состоят в том, что вы полу
чаете в свои руки мощные возможности обоих способов редактирова
ния. В вашем распоряжении имеются все преимущества вашего люби
мого текстового редактора. Вы легко можете редактировать блоки
программного кода и изменять строки в пределах циклов, методов или
функций. Плюс к этому вы не лишаетесь простоты и гибкости непо
средственного взаимодействия с оболочкой. Комбинирование этих
двух подходов еще больше усиливает их положительные стороны. Вы
получаете возможность управлять своим рабочим окружением непо
средственно из оболочки, вы можете приостанавливать работу, редак
тировать и выполнять программный код из текстового редактора. При
возобновлении работы в оболочке вам будут доступны все изменения,
выполненные в текстовом редакторе.
Настройка IPython
Последняя из «основ», о которой вам следует знать, – это порядок на
стройки IPython. Если вы не указывали иное местоположение при пер
вом запуске оболочки IPython, она создаст каталог .ipython в вашем до
машнем каталоге. Внутри каталога .ipython имеется файл с именем
ipy_user_conf.py. Это обычный конфигурационный файл пользовате
ля, в котором хранятся настройки, оформленные в виде синтаксиче
ских конструкций на языке Python. Конфигурационный файл хранит
большое разнообразие элементов, позволяющих настраивать внешний
вид и функциональные возможности IPython под себя. Например,
имеется возможность выбрать цветовую гамму для оболочки, опреде
лить компоненты строки приглашения и выбрать текстовый редактор,
который автоматически будет использоваться функцией %edit для
ввода текста. Мы не будем углубляться в пояснения. Просто знайте,
что такой конфигурационный файл существует и он стоит того, что
бы ознакомиться с его содержимым, – возможно, в нем вы найдете
некоторые параметры, которые потребуется изменить.
Справка по специальным функциям
Как мы уже говорили, оболочка IPython обладает весьма широкими
возможностями. Такая широта обусловлена наличием просто огром
ного числа встроенных специальных функций. Так что же такое спе
циальная функция? В документации к IPython говорится:

Справка по специальным функциям 57
Оболочка IPython рассматривает любую строку, начинающуюся
ссимвола %, как вызов «специальной» функции. Эти функции по
зволяют управлять поведением самой оболочки IPython и добавля
ют ряд особенностей для работы с системой. Все имена специальных
функций начинаются с символа %, при этом параметры передаются
без использования круглых скобок или кавычек.
Пример: выполнение команды '%cd mydir' (без кавычек) изменит ра
бочий каталог на «mydir», если таковой существует.
Просмотреть и разобраться в этом многообразии дополнительных воз
можностей вам помогут две «специальные» функции. Первая специ
альная справочная функция, которую мы рассмотрим, – это функция
lsmagic. Функция lsmagic выводит список всех «специальных» функ
ций. Ниже приводится результат работы функции lsmagic:
In [1]: lsmagic
Available magic functions:
%Exit %Pprint %Quit %alias %autocall %autoindent %automagic %bg
%bookmark %cd %clear %color_info %colors %cpaste %debug %dhist %dirs
%doctest_mode %ed %edit %env %exit %hist %history %logoff %logon
%logstart %logstate %logstop %lsmagic %macro %magic %p %page %pdb
%pdef %pdoc %pfile %pinfo %popd %profile %prun %psearch %psource
%pushd %pwd %pycat %quickref %quit %r %rehash %rehashx %rep %reset
%run %runlog %save %sc %store %sx %system_verbose %time %timeit
%unalias %upgrade %who %who_ls %whos %xmode
Automagic is ON, % prefix NOT needed for magic functions.
(Автоматически ВКЛЮЧЕННЫЕ специальные функции, префикс %
для них НЕ требуется)
Как видите, существует огромное число доступных для вас специаль
ных функций. Фактически, к моменту написания этих строк, сущест
вовало 69 специальных функций. Вы могли бы счесть более удобным
получить список специальных функций следующим способом:
In [2]: %
%Exit %debug %logstop %psearch %save
%Pprint %dhist %lsmagic %psource %sc
%Quit %dirs %macro %pushd %store
%alias %doctest_mode %magic %pwd %sx
%autocall %ed %p %pycat %system_verbose
%autoindent %edit %page %quickref %time
%automagic %env %pdb %quit %timeit
%bg %exit %pdef %r %unalias
%bookmark %hist %pdoc %rehash %upgrade
%cd %history %pfile %rehashx %who
%clear %logoff %pinfo %rep %who_ls
%color_info %logon %popd %reset %whos
%colors %logstart %profile %run %xmode
%cpaste %logstate %prun %runlog

58 Глава 2. IPython
Ввод последовательности % –TAB в результате дает отформатированный
список 69 специальных функций. Одним словом, функция lsmagic
икомбинация %
–TAB позволят вам быстро получить список всех имею
щихся специальных функций, когда вы ищете чтото определенное
или чтобы ознакомиться с тем, что вам доступно. Но список без описа
ния не в состоянии помочь вам понять, для чего предназначена каж
дая функция.
Здесь к вам на помощь придет другая специальная справочная функ
ция. Эта функция называется magic. Функция magic позволяет получить
справочное описание всех специальных функций, встроенных в обо
лочку IPython. В справочную информацию включаются имя функции,
порядок ее использования (область применения) и описание принципа
действия функции. Ниже приводится описание функции page:
%page:
Pretty print the object and display it through a pager.
(Форматирует объект и отображает его с помощью программы
постраничного просмотра)
%page [options] OBJECT
If no object is given, use _ (last output).
(Если объект не указан, используется _ (последний введенный))
Options:
(Параметры)
r: page str(object), don't pretty print it.
( r: page str(object), вывод информации в неформатированном виде)
В зависимости от используемой программы постраничного просмотра
вы можете выполнять поиск и прокручивать результаты работы функ
ции magic. Это может пригодиться, если вы знаете, что искать, чтобы
перейти сразу к нужной странице вместо того, чтобы прокручивать
описание к нужному месту. Описания функций упорядочены по алфа
виту, что поможет вам быстро отыскать нужную функцию.
Можно также использовать и другой метод получения справочной ин
формации, с которым мы познакомимся ниже в этой главе. Если вве
сти имя специальной функции и знак вопроса после нее (?), вы получи
те практически ту же самую информацию, что и с помощью функции
%magic. Ниже приводится результат выполнения команды %page ?:
In [1]: %page ?
Type: Magic function
Base Class:
String Form: >
Namespace: IPython internal
File: /home/jmjones/local/python/psa/lib/python2.5/
site packages/IPython/Magic.py
Definition: %page(self, parameter_s='')

Справка по специальным функциям 59
Docstring:
Pretty print the object and display it through a pager.
%page [options] OBJECT
If no object is given, use _ (last output).
Options:
r: page str(object), don't pretty print it.
И, наконец, еще одна справочная функция IPython, которая выводит
сводный отчет об использовании различных возможностей, а также
информацию о самих специальных функциях. Если в строке пригла
шения IPython ввести команду %quickref, вы получите справочник, ко
торый начинается со следующих строк:
IPython An enhanced Interactive Python Quick Reference Card
(IPython – Расширенная интерактивная оболочка Python – краткий справочник)
================================================================
obj?, obj?? : Get help, or more help for object (also works as
?obj, ??obj).
(Справка или подробная справка об объекте (допускается также
?obj, ??obj).)
?foo.*abc* : List names in 'foo' containing 'abc' in them.
(список имен в 'foo', содержащих 'abc')
%magic : Information about IPython's 'magic' % functions.
(Информация о специальных функциях % IPython)
Magic functions are prefixed by %, and typically take their arguments without
parentheses, quotes or even commas for convenience.
(Имена специальных функций начинаются с % и обычно принимают аргументы без
использования скобок, кавычек и даже запятых)
Example magic function calls:
(Примеры вызова специальных функций)
%alias d ls F : 'd' is now an alias for 'ls F'
(теперь 'd' – псевдоним для 'ls –F')
alias d ls F : Works if 'alias' not a python name
(Допустимо, если alias не является именем объекта Python)
alist = %alias : Get list of aliases to 'alist'
(Записывает список псевдонимов в переменную 'alist')
cd /usr/share : Obvious. cd to choose from visited dirs.
(Очевидно. cd для выбора из посещавшихся каталогов)
%cd?? : See help AND source for magic %cd
(См. справку И исходные тексты для специальной функции %cd)
System commands:
!cp a.txt b/ : System command escape, calls os.system()
(Экранирование системных команд, вызывается os.system())
cp a.txt b/ : after %rehashx, most system commands work without !
(после %rehashx, большинство системных команд работают без !)
cp ${f}.txt $bar : Variable expansion in magics and system commands

60 Глава 2. IPython
(Подстановка имен переменных в специальных функциях
и в системных командах)
files = !ls /usr : Capture sytem command output
(Захватывает вывод системной команды)
files.s, files.l, files.n: "a b c", ['a','b','c'], 'a\nb\nc'
и заканчивается следующими строками:
%time:
Time execution of a Python statement or expression.
(Время выполнения инструкции или вычисления выражения)
%timeit:
Time execution of a Python statement or expression
(Время выполнения инструкции или вычисления выражения)
%unalias:
Remove an alias
(Удаляет псевдоним)
%upgrade:
Upgrade your IPython installation
(Обновляет версию IPython)
%who:
Print all interactive variables, with some minimal formatting.
(Выводит все интерактивные переменные с минимальным форматированием)
%who_ls:
Return a sorted list of all interactive variables.
(Возвращает отсортированный список всех интерактивных переменных)
%whos:
Like %who, but gives some extra information about each variable.
(Подобна функции %who, но выводит дополнительные сведения
о каждой переменной)
%xmode:
Switch modes for the exception handlers.
(Переключает режим обработки исключений)
В самом начале вывода, получаемого от функции %quickref, приводит
ся справочная информация о различных функциональных возможно
стях оболочки IPython. Остальная часть справочника %quickref пред
ставляет собой краткое описание всех специальных функций. Это
краткое описание включает в себя первую строку из полной справки
по каждой специальной функции. Например, ниже приводится пол
ное описание функции %who:
In [1]: %who ?
Type: Magic function
Base Class:
String Form: >
Namespace: IPython internal
File: /home/jmjones/local/python/psa/lib/python2.5/
site packages/IPython/Magic.py
Definition: who(self, parameter_s='')
Docstring:

Командная оболочка UNIX 61
Print all interactive variables, with some minimal formatting.
(Выводит все интерактивные переменные с минимальным форматированием)
If any arguments are given, only variables whose type matches one of
these are printed. For example:
(Если указан какой либо параметр, будут выведены переменные только
соответствующего типа. Например:)
%who function str
will only list functions and strings, excluding all other types of
variables. To find the proper type names, simply use type(var) at
a command line to see how python prints type names. For example:
(выведет только имена функций и строковых переменных, исключая переменные
любых других типов. Чтобы отыскать требуемое имя типа, просто используйте
команду type(var), которая вернет имя типа в языке Python. Например:)
In [1]: type('hello')
Out[1]:
indicates that the type name for strings is 'str'.
(указывает, что строки принадлежат к типу с именем 'str')
%who always excludes executed names loaded through your configuration
file and things which are internal to IPython.
(%who всегда исключает выполняемые имена, загруженные
из конфигурационного файла, и наименования,
являющиеся внутренними сущностями IPython.)
This is deliberate, as typically you may load many modules and the
purpose of %who is to show you only what you've manually defined.
(Сделано это преднамеренно, так как может быть загружено множество
модулей, а назначение функции %who состоит в том, чтобы показывать
только имена, определенные вручную.)
Справочная информация о функции %who, присутствующая в выводе
функции %quickref, полностью идентична первой строке в разделе Doc
string в блоке информации, которая возвращается командой %who ?.
Командная оболочка UNIX
У работы в командной оболочке UNIX есть свои преимущества (из кото
рых можно назвать унифицированный подход к решению проблем, бо
гатый набор инструментов, достаточно краткий и простой синтаксис,
стандартные потоки вводавывода, конвейеры и перенаправление), но
для нас было бы просто замечательно добавить этому старому другу еще
и возможности Python. Оболочка IPython обладает рядом особенно
стей, которые повышают ценность соединения этих двух оболочек.
alias
Первая особенность объединения оболочки Python/UNIX, которую мы
рассмотрим, – это специальная функция alias. С помощью этой функ

62 Глава 2. IPython
ции можно создавать сокращенные псевдонимы системных команд.
Чтобы определить псевдоним, достаточно просто ввести имя функции
alias и далее указать системную команду (с любыми допустимыми па
раметрами). Например:
In [1]: alias nss netstat lptn
In [2]: nss
(Not all processes could be identified, non owned process info
will not be shown, you would have to be root to see it all.)
(Не все процессы могут быть опознаны, информация о процессах, которыми
вы не владеете, отображаться не будет, вы должны иметь привилегии
пользователя root, чтобы увидеть все процессы.)
Active Internet connections (only servers)
(Активные соединения с Интернетом (только серверы)))
Proto Recv Q Send Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
Существует несколько способов передать дополнительные данные на
вход псевдонима. Один из них – пассивный подход. Если все, что тре
буется передать псевдониму, допустимо смешивать в одну кучу, такой
подход может оказаться полезным. Например, если с помощью утили
ты grep из результатов команды netstat, показанных выше, необходи
мо отобрать только те, где номер порта равен 80, можно выполнить та
кую команду:
In [3]: nss | grep 80
(Not all processes could be identified, non owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
Такой подход не годится, когда требуется передать дополнительные
параметры, но для подобных случаев вполне подойдет.
Другой способ – активный подход. Он очень напоминает пассивный
подход за исключением того, что наряду с неявными параметрами вы
явно обрабатываете все последующие аргументы. Ниже приводится
пример, демонстрирующий обработку всех дополнительных парамет
ров как единой группы:
In [1]: alias achoo echo "|%l|"
In [2]: achoo
||
In [3]: achoo these are args
|these are args|
Здесь используется синтаксическая конструкция %l (знак процента, за
которым следует символ «l»), которая вставляет оставшуюся часть ко
мандной строки в псевдоним. В реальной жизни такой прием, скорее

Командная оболочка UNIX 63
всего, использовался бы для вставки остальной части строки кудани
будь в середину команды, для которой создается псевдоним.
И вот вам пример пассивного подхода, переделанный так, чтобы явно
обрабатывать все дополнительные аргументы:
In [1]: alias nss netstat lptn %l
In [2]: nss
(Not all processes could be identified, non owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv Q Send Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
In [3]: nss | grep 80
(Not all processes could be identified, non owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
В действительности, в этом примере не требовалось добавлять конст
рукцию %l. Если ее опустить, результат не изменится.
Для вставки параметров в середину командной строки можно было бы
использовать параметр подстановки строки %s. Следующий пример де
монстрирует, как выполняется обработка параметров:
In [1]: alias achoo echo first: "|%s|", second: "|%s|"
In [2]: achoo foo bar
first: |foo|, second: |bar|
Однако здесь может возникнуть проблема. Если псевдониму, который
ожидает получить два параметра, передать только один, можно ждать
появление ошибки:
In [3]: achoo foo
ERROR: Alias requires 2 arguments, 1 given.

AttributeError Traceback (most recent call last)
С другой стороны, вполне безопасно можно передавать большее число
параметров:
In [4]: achoo foo bar bam
first: |foo|, second: |bar| bam
Параметры foo и bar были помещены в соответствующие позиции,
а параметр bam просто был добавлен в конец, чего и следовало ожидать.
Сохранить псевдоним можно с помощью специальной функции %store,
и ниже в этой главе будет показано, как это делается. Продолжая пре
дыдущий пример, мы можем сохранить псевдоним achoo, чтобы при
следующем запуске оболочки IPython его можно было использовать:

64 Глава 2. IPython
In [5]: store achoo
Alias stored: achoo (2, 'echo first: "|%s|", second: "|%s|"')
In [6]:
Do you really want to exit ([y]/n)?
(psa)jmjones@dinkgutsy:code$ ipython nobanner
In [1]: achoo one two
first: |one|, second: |two|
Выполнение системных команд
Другой и, пожалуй, более простой способ выполнения системных ко
манд заключается в использовании восклицательного знака (!) перед
ними:
In [1]: !netstat lptn
(Not all processes could be identified, non owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv Q Send Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
Имеется возможность передавать системным командам значения пе
ременных, при этом имена переменных должны начинаться со знака
доллара ($). Например:
In [1]: user = 'jmjones'
In [2]: process = 'bash'
In [3]: !ps aux | grep $user | grep $process
jmjones 5967 0.0 0.4 21368 4344 pts/0 Ss+ Apr11 0:01 bash
jmjones 6008 0.0 0.4 21340 4304 pts/1 Ss Apr11 0:02 bash
jmjones 8298 0.0 0.4 21296 4280 pts/2 Ss+ Apr11 0:04 bash
jmjones 10184 0.0 0.5 22644 5608 pts/3 Ss+ Apr11 0:01 bash
jmjones 12035 0.0 0.4 21260 4168 pts/15 Ss Apr15 0:00 bash
jmjones 12943 0.0 0.4 21288 4268 pts/5 Ss Apr11 0:01 bash
jmjones 15720 0.0 0.4 21360 4268 pts/17 Ss 02:37 0:00 bash
jmjones 18589 0.1 0.4 21356 4260 pts/4 Ss+ 07:04 0:00 bash
jmjones 18661 0.0 0.0 320 16 pts/15 R+ 07:06 0:00 grep bash
jmjones 27705 0.0 0.4 21384 4312 pts/7 Ss+ Apr12 0:01 bash
jmjones 32010 0.0 0.4 21252 4172 pts/6 Ss+ Apr12 0:00 bash
Здесь перечислены все сеансы работы с командной оболочкой bash,
принадлежащие пользователю jmjones.
Ниже приводится пример сохранения результатов команды !:
In [4]: l = !ps aux | grep $user | grep $process
In [5]: l
Out[5]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: jmjones 5967 0.0 0.4 21368 4344 pts/0 Ss+ Apr11 0:01 bash

Командная оболочка UNIX 65
1: jmjones 6008 0.0 0.4 21340 4304 pts/1 Ss Apr11 0:02 bash
2: jmjones 8298 0.0 0.4 21296 4280 pts/2 Ss+ Apr11 0:04 bash
3: jmjones 10184 0.0 0.5 22644 5608 pts/3 Ss+ Apr11 0:01 bash
4: jmjones 12035 0.0 0.4 21260 4168 pts/15 Ss Apr15 0:00 bash
5: jmjones 12943 0.0 0.4 21288 4268 pts/5 Ss Apr11 0:01 bash
6: jmjones 15720 0.0 0.4 21360 4268 pts/17 Ss 02:37 0:00 bash
7: jmjones 18589 0.1 0.4 21356 4260 pts/4 Ss+ 07:04 0:00 bash
8: jmjones 27705 0.0 0.4 21384 4312 pts/7 Ss+ Apr12 0:01 bash
9: jmjones 32010 0.0 0.4 21252 4172 pts/6 Ss+ Apr12 0:00 bash
Обратите внимание, что результат работы команды, сохраненный в пе
ременной l, отличается от результата, полученного в предыдущем
примере. Это потому, что переменная l содержит объект списка, тогда
как в предыдущем примере демонстрировался обычный вывод коман
ды. Подробнее объекты списков мы будем рассматривать в разделе
«Обработка строк».
Альтернативой использованию символа ! является использование ком
бинации !!. Эта комбинация обеспечивает возможность выполнять те
же самые действия, что и !, за исключением того, что не позволяет со
хранять результат в переменной. Но при ее использовании открывает
ся возможность использовать конструкции _ и _[0
–9]*, которые будут
обсуждаться ниже, в разделе «История результатов».
Использование ! или !! перед системными командами определенно со
ставляет меньше труда, чем создание псевдонимов, однако в одних слу
чаях более предпочтительными могут оказаться псевдонимы, а в дру
гих – использование ! или !!. Например, если предполагается, что не
которая команда будет использоваться постоянно, лучше создать для
нее псевдоним. Для однократного или очень редкого использования
лучше применять ! или !!.
rehash
Существует еще один способ создания псевдонимов и/или выполнения
системных команд из оболочки IPython, основанный на применении
специальной функции rehash (выполняющей рехеширование). С тех
нической точки зрения, она создает псевдонимы системных команд,
но не совсем так, как это делали бы вы сами. Специальная функция re
hash дополняет «таблицу псевдонимов» всем, что будет обнаружено
в пути поиска файлов, определяемым переменной окружения PATH. Вы
можете спросить: «Что это за таблица псевдонимов?». Когда создают
ся псевдонимы, оболочка IPython отображает имена псевдонимов на
системные команды, с которыми они должны быть ассоциированы.
Таблица псевдонимов как раз и описывает эти отображения.
Для рехеширования таблицы псевдонимов предпочтительнее
использовать специальную функцию rehashx, а не rehash. Мы
представим обе функции и покажем, как они работают, а затем
опишем имеющиеся между ними отличия.

66 Глава 2. IPython
Во время работы оболочка IPython предоставляет в ваше распоряже
ние ряд переменных, таких как In и Out, с которыми мы уже встреча
лись ранее. Одна из таких переменных называется __IP. Она представ
ляет собой объект интерактивной оболочки. Атрибут этого объекта,
с именем alias_table, ссылается на эту таблицу. Это именно то место,
где хранятся отображения имен псевдонимов на системные команды.
Мы можем просматривать эту таблицу точно так же, как любую дру
гую переменную:
In [1]: __IP.alias_table
Out[1]:
{'cat': (0, 'cat'),
'clear': (0, 'clear'),
'cp': (0, 'cp i'),
'lc': (0, 'ls F o color'),
'ldir': (0, 'ls F o color %l | grep /$'),
'less': (0, 'less'),
'lf': (0, 'ls F o color %l | grep ^ '),
'lk': (0, 'ls F o color %l | grep ^l'),
'll': (0, 'ls lF'),
'lrt': (0, 'ls lart'),
'ls': (0, 'ls F'),
'lx': (0, 'ls F o color %l | grep ^ ..x'),
'mkdir': (0, 'mkdir'),
'mv': (0, 'mv i'),
'rm': (0, 'rm i'),
'rmdir': (0, 'rmdir')}
Эта таблица выглядит как словарь:
In [2]: type(__IP.alias_table)
Out[2]:
Вид может оказаться обманчивым, но это не тот случай.
В настоящий момент в этом словаре имеется 16 элементов:
In [3]: len(__IP.alias_table)
Out[3]: 16
После вызова функции rehash объем словаря значительно увеличился:
In [4]: rehash
In [5]: len(__IP.alias_table)
Out[5]: 2314
Попробуем отыскать в нем то, чего не было прежде, но что должно бы
ло появиться, – теперь в словаре должна появиться утилита transcode:
In [6]: __IP.alias_table['transcode']
Out[6]: (0, 'transcode')

Командная оболочка UNIX 67
Когда вам встречается имя переменной или атрибута, начи
нающееся с двух символов подчеркивания (__), как правило,
это означает, что программист не предполагал, что вы будете
изменять содержимое этой переменной. Мы обратились к пе
ременной __IP, но только для того, чтобы продемонстрировать
вам внутреннее устройство. В случае необходимости мы могли
бы воспользоваться официальным прикладным интерфейсом
(API) IPython и обратиться к объекту _ip, доступному из ко
мандной строки оболочки IPython.
rehashx
Специальная функция rehashx по своему действию напоминает специ
альную функцию rehash, за исключением того, что при просмотре ка
талогов, перечисленных в переменной окружения PATH, она добавляет
в таблицу псевдонимов только имена исполняемых файлов. Поэтому
резонно предположить, что сразу после запуска оболочки IPython в ре
зультате работы функции rehashx таблица псевдонимов будет иметь
тот же или меньший объем, как после запуска функции rehash:
In [1]: rehashx
In [2]: len(__IP.alias_table)
Out[2]: 2307
Интересно: после запуска функции rehashx размер таблицы псевдони
мов оказался на семь элементов меньше, чем после вызова функции
rehash. Ниже приводятся эти семь отличий:
In [3]: from sets import Set
In [4]: rehashx_set = Set(__IP.alias_table.keys())
In [5]: rehash
In [6]: rehash_set = Set(__IP.alias_table.keys())
In [7]: rehash_set rehashx_set
Out[7]: Set(['fusermount', 'rmmod.modutils', 'modprobe.modutils', 'kallsyms',/
'ksyms', 'lsmod.modutils', 'X11'])
И если поинтересоваться, почему, например, файл rmmod.modutils был
отобран функцией rehash, но не был отобран функцией rehashx, можно
обнаружить следующее:
jmjones@dinkgutsy:Music$ slocate rmmod.modutils
/sbin/rmmod.modutils
jmjones@dinkgutsy:Music$ ls l /sbin/rmmod.modutils
lrwxrwxrwx 1 root root 15 2007 12 07 10:34 /sbin/rmmod.modutils >
insmod.modutils
jmjones@dinkgutsy:Music$ ls l /sbin/insmod.modutils
ls: /sbin/insmod.modutils: No such file or directory

68 Глава 2. IPython
Здесь видно, что rmmod.utils – это ссылка на insmod.modutils, но ins
mod.modutils отсутствует на диске.
cd
Если вам приходилось работать в стандартной оболочке Python, то,
возможно, вы заметили, что в ней достаточно сложно определить имя
каталога, в котором вы находитесь. Можно использовать функцию
os.chdir(), чтобы перейти в нужный каталог, но это не очень удобно.
Имя текущего каталога можно узнать с помощью функции os.get
cwd(), но это тоже крайне неудобно. Поскольку в стандартной оболочке
Python выполняются команды языка Python, это может выглядеть не
такой большой проблемой, но при работе в оболочке IPython и нали
чии более простого доступа к системным командам достаточно важно
иметь возможность более простого перемещения по каталогам.
Попробуйте воспользоваться специальной функцией cd. Вам кажется,
что мы придаем этому большее значение, чем оно заслуживает: здесь
нет ничего революционного и вполне можно обойтись без этой функ
ции. Но только представьте, что ее нет. Жизнь без нее оказалась бы на
много сложнее.
В оболочке IPython функция cd работает практически так же, как од
ноименная команда Bash. Типичный пример ее использования: cd
directory_name. Этого вполне можно было ожидать из опыта работы
в Bash. При вызове без аргументов функция cd выполняет переход
в домашний каталог пользователя. Если после имени функции доба
вить пробел и дефис, cd
–, она выполнит переход в предыдущий ката
лог. Функция cd может принимать три дополнительных аргумента,
которые отсутствуют у одноименной команды в Bash.
Первый аргумент:
–q, или quiet. Если этот аргумент не указать, IPy
thon будет выводить имя каталога, куда был выполнен переход. В сле
дующем примере демонстрируются способы изменения текущего ка
талога как с применением аргумента – q, так и без него:
In [1]: cd /tmp
/tmp
In [2]: pwd
Out[2]: '/tmp'
In [3]: cd
/home/jmjones
In [4]: cd q /tmp
In [5]: pwd
Out[5]: '/tmp'
Указание аргумента q запрещает IPython вывод имени каталога /tmp,
в который был выполнен переход.

Командная оболочка UNIX 69
Еще одной особенностью функции cd в IPython является возможность
перехода по определенным ранее закладкам. (Вскоре мы объясним,
как они создаются.) В следующем примере показано, как выполнить
переход в каталог по созданной ранее закладке:
In [1]: cd b t
(bookmark:t) > /tmp
/tmp
В этом примере предполагается, что ранее для каталога /tmp была соз
дана закладка с именем t. Формально синтаксис перехода в каталог по
закладке имеет следующий вид: cd
–b bookmark_name, но если закладка
bookmark_name определена и в текущем каталоге отсутствует подкаталог
bookmark_name, то ключ
–b можно опустить – в этом случае оболочка
IPython предполагает, что вы собираетесь выполнить переход по за
кладке.
Последняя дополнительная особенность, которую предлагает функ
ция cd в оболочке IPython, заключается в возможности перейти в опре
деленный каталог, присутствующий в списке ранее посещавшихся ка
талогов. Ниже приводится пример использования этого списка:
0: /home/jmjones
1: /home/jmjones/local/Videos
2: /home/jmjones/local/Music
3: /home/jmjones/local/downloads
4: /home/jmjones/local/Pictures
5: /home/jmjones/local/Projects
6: /home/jmjones/local/tmp
7: /tmp
8: /home/jmjones
In [2]: cd 6
/home/jmjones/local/tmp
В первой части примера приводится список ранее посещавшихся ката
логов. Как его получить, мы совсем скоро расскажем. Затем следует
вызов функции cd, которой передается числовой аргумент
–6. Он сооб
щает оболочке IPython, что нам требуется перейти в каталог, который
находится в списке под номером «6», то есть в каталог /home/jmjones/
local/tmp. И в последней строке оболочка сообщает, что теперь вы на
ходитесь в каталоге /home/jmjones/local/tmp.
bookmark
Мы только что продемонстрировали, как использовать закладки
в функции cd для перехода в требуемый каталог. А теперь мы пока
жем, как создавать эти закладки и как ими управлять. Следует упомя
нуть, что закладки сохраняются между сеансами работы с оболочкой
IPython. Если завершить работу с оболочкой, а затем вновь запустить

70 Глава 2. IPython
ее, закладки будут восстановлены. Создать закладку можно двумя
способами. Ниже демонстрируется первый из них:
In [1]: cd /tmp
/tmp
In [2]: bookmark t
Выполнив команду bookmark t после перехода в каталог /tmp, мы созда
ли закладку с именем t, указывающую на каталог /tmp. Второй способ
создания закладки требует ввести более чем одно слово:
In [3]: bookmark muzak /home/jmjones/local/Music
Здесь была создана закладка с именем muzak, которая указывает на ло
кальный каталог с музыкой. Первый аргумент – это имя закладки,
а второй – имя каталога, на который ссылается закладка.
Получить список закладок, которых у нас к настоящему моменту все
го две, можно с помощью параметра
–l. Посмотрим, как выглядит спи
сок всех наших закладок:
In [4]: bookmark l
Current bookmarks:
muzak > /home/jmjones/local/Music
t > /tmp
Удалять закладки можно двумя способами: удалить сразу все заклад
ки или только выбранную. В следующем примере создается новая за
кладка, затем она удаляется, а после этого удаляются все остальные
закладки:
In [5]: bookmark ulb /usr/local/bin
In [6]: bookmark l
Current bookmarks:
muzak > /home/jmjones/local/Music
t > /tmp
ulb > /usr/local/bin
In [7]: bookmark d ulb
In [8]: bookmark l
Current bookmarks:
muzak > /home/jmjones/local/Music
t > /tmp
Вместо команды bookmark –l можно использовать команду cd –b:
In [9]: cd b
muzak t
Нажав несколько раз клавишу Backspace, продолжим с того места, где
остановились:
In [9]: bookmark r

Командная оболочка UNIX 71
In [10]: bookmark l
Current bookmarks:
В этом примере сначала была создана закладка с именем ulb, указы
вающая на каталог /usr/local/bin. Затем она была удалена с помощью
аргумента
–d bookmark_name команды bookmark. В конце были удалены
все закладки с помощью аргумента
–r.
dhist
В примере использования функции cd был продемонстрирован список
посещавшихся ранее каталогов. Этот список сохраняется не только
в течение сеанса, но и между сеансами работы с оболочкой IPython.
Ниже демонстрируется пример вызова функции dhist без аргументов:
In [1]: dhist
Directory history (kept in _dh)
0: /home/jmjones
1: /home/jmjones/local/Videos
2: /home/jmjones/local/Music
3: /home/jmjones/local/downloads
4: /home/jmjones/local/Pictures
5: /home/jmjones/local/Projects
6: /home/jmjones/local/tmp
7: /tmp
8: /home/jmjones
9: /home/jmjones/local/tmp
10: /tmp
Быстро получить доступ к этому списку можно с помощью команды
cd
, как показано ниже:
In [1]: cd
00 [/home/jmjones] 06 [/home/jmjones/local/tmp]
01 [/home/jmjones/local/Videos] 07 [/tmp]
02 [/home/jmjones/local/Music] 08 [/home/jmjones]
03 [/home/jmjones/local/downloads] 09 [/home/jmjones/local/tmp]
04 [/home/jmjones/local/Pictures] 10 [/tmp]
05 [/home/jmjones/local/Projects]
Две дополнительных возможности функции dhist делают ее более гиб
кой, чем команда cd
. В первом случае имеется возможность ука
зать, сколько каталогов должно быть отображено. Например, чтобы
указать, что требуется отобразить только пять последних посещав
шихся каталогов, можно воспользоваться такой командой:
In [2]: dhist 5
Directory history (kept in _dh)
6: /home/jmjones/local/tmp
7: /tmp
8: /home/jmjones
9: /home/jmjones/local/tmp
10: /tmp

72 Глава 2. IPython
Во втором – определить диапазон элементов списка посещавшихся
ранее каталогов. Например, чтобы просмотреть каталоги в списке
с третьего по шестой, можно выполнить следующую команду:
In [3]: dhist 3 7
Directory history (kept in _dh)
3: /home/jmjones/local/downloads
4: /home/jmjones/local/Pictures
5: /home/jmjones/local/Projects
6: /home/jmjones/local/tmp
Обратите внимание: элемент списка с номером, соответствующим вто
рому аргументу, не включается в вывод, поэтому второе число должно
соответствовать порядковому номеру элемента списка, следующему
непосредственно за последним каталогом, который требуется вывести.
pwd
Это простая функция, но она часто бывает необходима при навигации
в дереве каталогов. Функция pwd выводит имя текущего каталога. На
пример:
In [1]: cd /tmp
/tmp
In [2]: pwd
Out[2]: '/tmp'
Подстановка переменных
Предыдущие особенности оболочки IPython определенно удобны и не
обходимы, но следующие три особенности доставят массу удоволь
ствия искушенным пользователям. Первая из них – подстановка имен
переменных. До настоящего момента мы использовали в командной
оболочке только то, что имеет отношение к командной оболочке,
а в Python – только то, что принадлежит языку Python. Но теперь мы
попробуем соединить это вместе. То есть мы попробуем взять значе
ние, которое было получено интерпретатором Python, и передать его
командной оболочке:
In [1]: for i in range(10):
...: !date > ${i}.txt
...:
...:
In [2]: ls
0.txt 1.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt 9.txt
In [3]: !cat 0.txt
Sat Mar 8 07:40:05 EST 2008

Командная оболочка UNIX 73
Это достаточно надуманный пример. Едва ли вам потребуется создать
10 текстовых файлов, каждый из которых содержит дату. Но этот при
мер наглядно демонстрирует, как смешивать программный код на
языке Python с программным кодом на языке командной оболочки.
В этом примере выполняются итерации по списку, созданному функ
цией range(), каждый элемент которого поочередно сохраняется в пе
ременной i. В каждой итерации с использованием нотации ! вызыва
ется системная утилита date. Обратите внимание, что синтаксис вы
зова date идентичен способу, который использовался бы, если бы мы
определили переменную окружения i. При таком подходе производит
ся вызов утилиты date, а ее вывод перенаправляется в файл с именем
{текущий элемент списка}.txt. После этого в примере выводится спи
сок созданных файлов и даже содержимое одного из них, чтобы убе
диться, что он содержит нечто, напоминающее дату.
В системную оболочку можно передавать любые значения, получен
ные в Python. Независимо от того, получены эти значения из базы дан
ных, созданы в ходе вычислений, получены в результате обращения
к службе XMLRPC или извлечены из текстового файла, вы можете по
лучить их средствами языка Python и передать системной команде,
применяя прием с использованием !.
Обработка строк
Другой невероятно мощной особенностью, которую предлагает обо
лочка IPython, является возможность обрабатывать строки, получен
ные от системных команд. Предположим, что нам необходимо полу
чить идентификаторы всех процессов (PID), принадлежащих пользо
вателю jmjones. Для этого можно было бы использовать следующую ко
манду:
ps aux | awk '{if ($1 == "jmjones") print $2}'
Эта команда выглядит достаточно компактной и понятной. Но попро
буем решить ту же самую задачу средствами IPython. Для начала по
лучим вывод от команды ps aux:
In [1]: ps = !ps aux
In [2]:
Результат работы команды ps aux сохраняется в переменной ps в виде
списка, элементами которого являются строки, полученные от систем
ной команды. Под словами «в виде списка» в данном случае подразу
мевается, что переменная принадлежит к встроенному типу list, по
этому она поддерживает все методы, принадлежащие этому типу. Бла
годаря этому, если у вас имеется функция, которая ожидает получить
список, вы можете передать ей полученный объект с результатами.
Кроме того, помимо стандартных методов списка она поддерживает

74 Глава 2. IPython
еще пару весьма интересных методов и один атрибут, которые могут
вам пригодиться. Только ради того, чтобы продемонстрировать эти
«интересные методы», мы пока отложим задачу получения всех про
цессов, принадлежащих пользователю jmjones. Первый «интересный
метод» – это метод grep(). Фактически он представляет собой обычный
фильтр, который определяет, какие строки оставить, а какие отбро
сить. Чтобы узнать, имеются ли какиенибудь строки, содержащие
слово lighthttp, мы могли бы воспользоваться следующей командой:
In [2]: ps.grep('lighttpd')
Out[2]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: www data 4905 0.0 0.1........0:00 /usr/sbin/lighttpd f /etc/lighttpd/l
Здесь мы вызвали метод grep() и передали ему регулярное выражение
'lighthttp'. Запомните: регулярные выражения, которые передаются
методу grep(), не чувствительны к регистру символов. В результате это
го вызова метода grep() была получена строка, где было найдено соот
ветствие регулярному выражению 'lighthttp'. Чтобы получить все за
писи, за исключением тех, что соответствуют указанному регулярному
выражению, мы могли бы использовать примерно такую команду:
In [3]: ps.grep('Mar07', prune=True)
Out[3]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
1: jmjones 19301 0.0 0.4 21364 4272 pts/2 Ss+ 03:58 0:00 bash
2: jmjones 21340 0.0 0.9 202484 10184 pts/3 Sl+ 07:00 0:06 vim ipytho
3: jmjones 23024 0.0 1.1 81480 11600 pts/4 S+ 08:58 0:00 /home/jmjo
4: jmjones 23025 0.0 0.0 0 0 pts/4 Z+ 08:59 0:00 [sh] 5: jmjones 23373 5.4 1.0 81160 11196 pts/0 R+ 09:20 0:00 /home/jmjo
6: jmjones 23374 0.0 0.0 3908 532 pts/0 R+ 09:20 0:00 /bin/sh c
7: jmjones 23375 0.0 0.1 15024 1056 pts/0 R+ 09:20 0:00 ps aux
Мы передали методу grep() регулярное выражение 'Mar07' и обнару
жили, что большинство процессов было запущено 7 марта, поэтому мы
решили получить все процессы, которые были запущены не 7 марта.
Чтобы исключить все записи, соответствующие регулярному выраже
нию 'Mar07', мы добавили еще один аргумент: prune=True. Этот имено
ванный аргумент сообщает оболочке IPython, что «любые записи, со
ответствующие указанному регулярному выражению, должны быть
отброшены». И, как видите, в полученном списке нет ни одной запи
си, соответствующей регулярному выражению 'Mar07'.
С методом grep() можно также использовать функции обратного вызо
ва. Это означает, что метод grep() может принимать в виде аргумента
функцию и вызывать ее. Он передает функции текущий элемент спи
ска. Если функция возвращает True для этого элемента, он включается
в итоговый набор. Например, мы могли бы получить содержимое ката
лога и оставить в нем только файлы или только каталоги:

Командная оболочка UNIX 75
In [1]: import os
In [2]: file_list = !ls
In [3]: file_list
Out[3]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: ch01.xml
1: code
2: ipython.pdf
3: ipython.xml
Этот каталог содержит «файлы». Мы не можем сказать, какие из них
действительно являются файлами, а какие каталогами, но если вос
пользоваться фильтром os.path.isfile(), мы сможем отобрать только
те, которые являются файлами:
In [4]: file_list.grep(os.path.isfile)
Out[4]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: ch01.xml
1: ipython.pdf
2: ipython.xml
В этом списке отсутствует «файл» code, следовательно, он вообще не
является файлом. Попробуем отобрать каталоги:
In [5]: file_list.grep(os.path.isdir)
Out[5]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: code
Теперь видно, что code действительно является каталогом. Другой ин
тересный метод – это метод fields(). После (или даже до) того, как бу
дет выполнена фильтрация набора результатов в соответствии с опре
деленными требованиями, вы можете отобразить те поля, которые же
лательно было бы вывести. Вернемся к нашему предыдущему примеру,
где выводились записи о процессах, запущенных не 7 марта, и выведем
только информацию в полях USER, PID и START:
In [4]: ps.grep('Mar07', prune=True).fields(0, 1, 8)
Out[4]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: USER PID START
1: jmjones 19301 03:58
2: jmjones 21340 07:00
3: jmjones 23024 08:58
4: jmjones 23025 08:59
5: jmjones 23373 09:20
6: jmjones 23374 09:20
7: jmjones 23375 09:20
Вопервых, обратите внимание, что метод fields() применяется к ре
зультатам, возвращаемым методом grep(). Это возможно потому, что
метод grep() возвращает объект того же типа, что и объект ps. Метод

76 Глава 2. IPython
fields() так же возвращает объект того же типа, что и метод grep().
Благодаря этому мы смогли объединить в цепочку методы grep()
иfields(). Теперь подробнее о том, что здесь происходит. Метод fields()
принимает неопределенное число аргументов, которые, как предпола
гается, обозначают номера «колонок» в выводе, при этом предполага
ется, что колонки отделяются пробелами. Это очень похоже на то, как
awk разбивает строки текста. В данном случае методу fields() предпи
сывается вывести колонки с порядковыми номерами 0, 1 и 8. Эти но
мера соответствуют колонкам USER, PID и START.
Теперь вернемся к задаче отображения идентификаторов всех процес
сов (PID), принадлежащих пользователю jmjones:
In [5]: ps.fields(0, 1).grep('jmjones').fields(1)
Out[5]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: 5385
1: 5388
2: 5423
3: 5425
4: 5429
5: 5431
6: 5437
7: 5440
8: 5444
<продолжение списка...>
В этом примере сначала отбираются только первые два столбца, 0 и 1,
которые соответствуют колонкам USER и PID, соответственно. Затем из
полученного списка, с помощью метода grep(), отбираются только те
записи, которые соответствуют регулярному выражению 'jmjones'.
И в заключение, из полученного набора выводится только второе поле
с помощью вызова метода fields(1). (Не забывайте, что нумерация по
лей начинается с нуля.)
Последний элемент, имеющий отношение к обработке строк, из тех,
которые нам хотелось бы продемонстрировать, – это атрибут s объек
та, который позволяет получить непосредственный доступ к списку.
Возможно, что результаты, которые дает сам объект, – не совсем то,
что вам хотелось бы получить. Поэтому для передачи ваших данных
системной командной оболочке используйте атрибут s списка:
In [6]: ps.fields(0, 1).grep('jmjones').fields(1).s
Out[6]: '5385 5388 5423 5425 5429 5431 5437 5440 5444 5452 5454 5457
5458 5468 5470 5478 5480 5483 5489 5562 5568 5593 5595 5597 5598 5618
5621 5623 5628 5632 5640 5740 5742 5808 5838 12707 12913 14391 14785
19301 21340 23024 23025 23373 23374 23375'
При обращении к атрибуту s возвращается обычная строка, содержа
щая идентификаторы процессов, разделенные пробелами, с которой
можно работать средствами командной оболочки. При желании этот

Командная оболочка UNIX 77
список в виде строки можно было сохранить в переменной с именем
pids и затем в оболочке IPython выполнить, например, такую команду:
kill $pids. Но такая команда послала бы сигнал SIGTERM всем процес
сам, принадлежащим пользователю jmjones, что привело бы к завер
шению работы текстового редактора и сеанса IPython.
Ранее уже демонстрировалось, что та же самая задача может быть ре
шена с помощью однострочного сценария на языке awk:
ps aux | awk '{if ($1 == "jmjones") print $2}'
Мы будем готовы добиться тех же результатов после того, как рассмот
рим еще одну концепцию. Метод grep() принимает еще один необяза
тельный параметр с именем field. Если параметр field определен, во
время принятия решения о включении очередного элемента в резуль
тат критерий поиска будет применяться к указанному полю:
In [1]: ps = !ps aux
In [2]: ps.grep('jmjones', field=0)
Out[2]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: jmjones 5361 0.0 0.1 46412 1828 ? SL Apr11
0:00 /usr/bin/gnome keyring daemon d
1: jmjones 5364 0.0 1.4 214948 14552 ? Ssl Apr11
0:03 x session manager
....
53: jmjones 32425 0.0 0.0 3908 584 ? S Apr15
0:00 /bin/sh /usr/lib/firefox/run mozilla.
54: jmjones 32429 0.1 8.6 603780 88656 ? Sl Apr15
2:38 /usr/lib/firefox/firefox bin
В этом случае были отобраны требуемые строки, но они были получе
ны целиком. Чтобы отобрать только идентификаторы процессов, мож
но предусмотреть следующее действие:
In [3]: ps.grep('jmjones', field=0).fields(1)
Out[3]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: 5361
1: 5364
....
53: 32425
54: 32429
Теперь мы имеем средства достичь той же цели, что и фильтр на языке
awk.
Профиль sh
Одно из понятий IPython, которое еще не было описано, – это про
филь. Профиль – это набор конфигурационных данных, которые за
гружаются при запуске оболочки IPython. Имеется возможность соз

78 Глава 2. IPython
давать произвольное число профилей для настройки IPython в зависи
мости от потребностей. Для вызова определенной конфигурации сле
дует использовать ключ командной строки
–p и указать имя желаемого
профиля.
Профиль sh (или shell) – это один из встроенных профилей IPython.
Профиль sh определяет значения некоторых конфигурационных пара
метров, в результате чего оболочка IPython становится более дружест
венной по отношению к системной командной оболочке. Приведем два
примера параметров конфигурации, имеющих значения, отличные от
значений в стандартном профиле IPython: параметр, задающий ото
бражение текущего каталога в строке приглашения к вводу, и пара
метр, задающий рехеширование каталогов, перечисленных в перемен
ной окружения PATH, что обеспечивает моментальный доступ ко всем
исполняемым файлам, к которым он имеется, например, в оболочке
Bash.
Помимо установки некоторых конфигурационных значений профиль
sh активирует некоторые полезные расширения. Например, он акти
вирует расширение envpersist. Расширение envpersist позволяет изме
нять различные переменные окружения и запоминать их значения
впрофиле sh, благодаря чему ликвидируется необходимость обнов
лять содержимое файла .bash_profile или .bashrc.
Ниже показано, как выглядит значение переменной PATH:
jmjones@dinkgutsy:tmp$ ipython p sh
IPython 0.8.3.bzr.r96 [on Py 2.5.1]
[~/tmp]|2> import os
[~/tmp]|3> os.environ['PATH']
<3> '/home/jmjones/local/python/psa/bin:
/home/jmjones/apps/lb/bin:/home/jmjones/bin:
/usr/local/sbin:/usr/local/bin:/usr/sbin:
/usr/bin:/sbin:/bin:/usr/games'
Теперь добавим :/appended в конец значения переменной PATH:
[~/tmp]|4> env PATH+=:/appended
PATH after append = /home/jmjones/local/python/psa/bin:
/home/jmjones/apps/lb/bin:/home/jmjones/bin:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:
/sbin:/bin:/usr/games:/appended
а /prepended: в начало:
[~/tmp]|5> env PATH =/prepended:
PATH after prepend = /prepended:/home/jmjones/local/python/psa/bin:
/home/jmjones/apps/lb/bin:/home/jmjones/bin:/usr/local/sbin:
/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/appended
Теперь посмотрим содержимое переменной PATH с помощью os.environ:
[~/tmp]|6> os.environ['PATH']

Командная оболочка UNIX 79
<6> '/prepended:/home/jmjones/local/python/psa/bin:
/home/jmjones/apps/lb/bin:/home/jmjones/bin:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:
/bin:/usr/games:/appended'
Закроем оболочку IPython:
[~/tmp]|7>
Do you really want to exit ([y]/n)?
jmjones@dinkgutsy:tmp$
Теперь откроем снова оболочку IPython, чтобы взглянуть на содержи
мое переменной PATH:
jmjones@dinkgutsy:tmp$ ipython p sh
IPython 0.8.3.bzr.r96 [on Py 2.5.1]
[~/tmp]|2> import os
[~/tmp]|3> os.environ['PATH']
<3> '/prepended:/home/jmjones/local/python/psa/bin:
/home/jmjones/apps/lb/bin:/home/jmjones/bin:/usr/local/sbin:
/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/appended'
Как видите, добавленные значения остались на своих местах, и при
этом нам не потребовалось изменять какиелибо конфигурационные
сценарии. Значение переменной PATH было сохранено без нашего вме
шательства. Теперь посмотрим, какие наши изменения переменных
окружения сохраняются:
[~/tmp]|4> env p
<4> {'add': [('PATH', ':/appended')], 'pre': [('PATH', '/
prepended:')],
'set': {}}
Мы можем удалить сохраняемые изменения значения переменной PATH:
[~/tmp]|5> env d PATH
Forgot 'PATH' (for next session)
и проверить получившееся значение переменной PATH:
[~/tmp]|6> os.environ['PATH']
<6>'/prepended:/home/jmjones/local/python/psa/bin:/home/jmjones/apps/
lb/bin:/home/jmjones/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/
usr/
bin:/sbin:/bin:/usr/games:/appended'
Хотя была дана команда удалить сохраняемые изменения для пере
менной PATH, они попрежнему остаются на месте. Это означает лишь
то, что оболочка IPython удалила указание на необходимость сохране
ния этих изменений. Обратите внимание, что процесс, запущенный
с определенными значениями в переменной окружения, будет сохра
нять их, если не изменить их некоторым способом. При следующем за
пуске оболочки IPython окружение изменится:

80 Глава 2. IPython
[~/tmp]|7>
Do you really want to exit ([y]/n)?
jmjones@dinkgutsy:tmp$ ipython p sh
IPython 0.8.3.bzr.r96 [on Py 2.5.1]
[~/tmp]|2> import os
[~/tmp]|3> os.environ['PATH']
<3> '/home/jmjones/local/python/psa/bin:/home/jmjones/apps/lb/bin:
/home/jmjones/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:
/sbin:/bin:/usr/games'
Как и следовало ожидать, переменная PATH вернулась к значению, ко
торое предшествовало тому моменту, как мы внесли в нее изменения.
Еще одна полезная особенность в профиле sh – это специальная функ
ция mglob. Функция mglob имеет простой синтаксис для наиболее рас
пространенных вариантов использования. Например, чтобы отыскать
все файлы с расширением .py в проекте Django, можно было бы вос
пользоваться следующей командой:
[django/trunk]|3> mglob rec:*py
<3> SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: ./setup.py
1: ./examples/urls.py
2: ./examples/manage.py
3: ./examples/settings.py
4: ./examples/views.py
...
1103: ./django/conf/project_template/urls.py
1104: ./django/conf/project_template/manage.py
1105: ./django/conf/project_template/settings.py
1106: ./django/conf/project_template/__init__.py
1107: ./docs/conf.py
[django/trunk]|4>
Директива rec предписывает выполнить рекурсивный поиск по задан
ному вслед за ней шаблону. В данном случае шаблоном служит *py.
Чтобы отобразить список всех каталогов в корневом каталоге проекта
Django, можно было бы воспользоваться следующей командой:
[django/trunk]|3> mglob dir:*
<3> SList (.p, .n, .l, .s, .grep(), .fields() available).
Value:
0: examples
1: tests
2: extras
3: build
4: django
5: docs
6: scripts


Сбор информации 81
Функция mglob возвращает объект списка, поэтому все, что в языке Py
thon можно сделать со списком, можно сделать и с полученным спи
ском каталогов.
Это была демонстрация лишь некоторых особенностей поведения про
филя sh. Существуют другие особенности и параметры этого профиля,
которые мы не рассматривали.
Сбор информации
IPython – это немножко больше, чем просто оболочка, в которой мож
но активно работать. Это еще и инструмент сбора разного рода инфор
мации о программном коде и объектах, с которыми приходится рабо
тать. Она обеспечивает такие возможности в добывании информации,
что с легкостью может рассматриваться как инструмент исследовате
ля. В этом разделе описывается ряд особенностей, которые помогут
вам в сборе информации.
page
Если представление объекта, с которым приходится работать, не уме
щается на экране, можно попробовать воспользоваться специальной
функцией page. Вы можете использовать функцию page для вывода
объекта с помощью программы постраничного просмотра. Во многих
системах в качестве такой программы по умолчанию используется
утилита less, но вы можете использовать какуюнибудь другую про
грамму. Ниже демонстрируется стандартный способ использования:
In [1]: p = !ps aux
==
['USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND',
'root 1 0.0 0.1 5116 1964 ? Ss Mar07 0:00 /sbin/init',
< ... дальнейшие результаты обрезаны ... >
In [2]: page p
['USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND',
'root 1 0.0 0.1 5116 1964 ? Ss Mar07 0:00 /sbin/init',
< ... дальнейшие результаты обрезаны ... >
Здесь в переменной p сохраняется результат выполнения системной
команды ps aux. Затем вызывается функция page, которой передается
объект с результатами. После этого функция page запускает програм
му less.
Функция page имеет один дополнительный параметр:
–r. Этот пара
метр предписывает функции page передать программе постраничного
просмотра неформатированное строковое представление объекта (ре
зультат вызова функции str()). Для нашего объекта со списком про
цессов это могло бы выглядеть так:

82 Глава 2. IPython
In [3]: page r p
ilus cd burner/mapping d', 'jmjones 5568 0.0 1.0 232004 10608 ? S
Mar07 0:00 /usr/lib/gnome applets/trashapplet ', 'jmjones 5593 0.0 0.9
188996 10076 ? S Mar07 0:00 /usr/lib/gnome applets/battstat apple',
'jmjones 5595 0.0 2.8 402148 29412 ? S Mar07 0:01 p
<... дальнейшие результаты обрезаны ...>
Такой неформатированный вывод выглядит почти нечитабельно. Мы
рекомендуем начинать с форматированного вывода и работать уже
сним.
pdef
Специальная функция pdef выводит заголовки определений (сигнату
ры функций) любых вызываемых объектов. В следующем примере мы
создаем свою собственную функцию со строкой документирования
иинструкцией return:
In [1]: def myfunc(a, b, c, d):
...: '''return something by using a, b, c, d to do something'''
...: return a, b, c, d
...:
In [2]: pdef myfunc
myfunc(a, b, c, d)
Функция pdef проигнорировала строку документирования и инструк
цию return, но вывела сигнатуру функции. Функцию pdef можно ис
пользовать с любой вызываемой функцией. Она может работать, даже
когда исходный программный код недоступен, но при условии, что
имеется доступ к файлу .pyc или к пакету.
pdoc
Функция pdoc выводит строку документирования переданной ей функ
ции. Ниже мы передали pdoc функцию myfunc, которую передавали
функции pdef в примере выше:
In [3]: pdoc myfunc
Class Docstring:
return something by using a, b, c, d to do something
Calling Docstring:
x.__call__(...) <==> x(...)
Результат достаточно очевиден.
pfile
Функция pfile передает файл, содержащий указанный объект, про
грамме постраничного просмотра, если этот файл будет найден:
In [1]: import os
In [2]: pfile os

Сбор информации 83
r"""OS routines for Mac, NT, or Posix depending on what system we're on.
This exports:
all functions from posix, nt, os2, mac, or ce, e.g. unlink, stat, etc.
<... дальнейшие результаты обрезаны ...>
В этом примере открывается файл модуля os и передается программе
less. Это может оказаться удобным, если вы пытаетесь понять, почему
тот или иной фрагмент программного кода ведет себя тем или иным
способом. Эта функция не работает, если доступным файлом является
пакет или файл с байткодом .pyc.
Ту же информацию, что выводят специальные функции %pdef,
%pdoc и %pfile, можно получить с помощью оператора ??. Причем
использование оператора ?? предпочтительнее.
pinfo
Функция pinfo и родственные ей утилиты настолько удобны, что
сложно представить, как можно обходиться без них. Функция pinfo
предоставляет такую информацию, как тип, базовый класс, простран
ство имен и строка документирования. Если представить, что у нас
имеется модуль, содержащий следующее:
#!/usr/bin/env python
class Foo:
"""мой класс Foo"""
def __init__(self):
pass
class Bar:
"""мой класс Bar"""
def __init__(self):
pass
class Bam:
"""мой класс Bam"""
def __init__(self):
pass
то можно было бы запросить информацию непосредственно о модуле:
In [1]: import some_module
In [2]: pinfo some_module
Type: module
Base Class:
String Form:
Namespace: Interactive
File: /home/jmjones/code/some_module.py
Docstring:


84 Глава 2. IPython
Об определенном классе в этом модуле:
In [3]: pinfo some_module.Foo
Type: classobj
String Form: some_module.Foo
Namespace: Interactive
File: /home/jmjones/code/some_module.py
Docstring:
мой класс Foo
Constructor information:
Definition: some_module.Foo(self)
Об экземпляре одного из классов:
In [4]: f = some_module.Foo()
In [5]: pinfo f
Type: instance
Base Class: some_module.Foo
String Form:
Namespace: Interactive
Docstring:
мой класс Foo
Оператор ?, стоящий перед или после имени объекта, позволит полу
чить ту же информацию, которую выводит функция pinfo:
In [6]: ? f
Type: instance
Base Class: some_module.Foo
String Form:
Namespace: Interactive
Docstring:
мой класс Foo
In [7]: f ?
Type: instance
Base Class: some_module.Foo
String Form:
Namespace: Interactive
Docstring:
мой класс Foo
А два знака вопроса (??), помещенные перед или после имени объекта,
позволят получить еще больше информации:
In [8]: some_module.Foo ??
Type: classobj
String Form: some_module.Foo
Namespace: Interactive
File: /home/jmjones/code/some_module.py
Source:
class Foo:
"""мой класс Foo"""

Сбор информации 85
def __init__(self):
pass
Constructor information:
Definition: some_module.Foo(self)
Оператор ?? выводит ту же информацию, которую дает функция pinfo,
плюс исходный программный код реализации запрошенного объекта.
Поскольку в этом примере мы запросили информацию об определен
ном классе, оператор ?? вывел исходный программный код только для
этого объекта, а не весь файл целиком. Эта особенность оболочки IPy
thon используется значительно чаще, чем любая другая ее особенность.
psource
Функция psource выводит исходный программный код указанного
объекта, будь то модуль или элемент модуля, такой как класс или
функция. Для отображения исходного программного кода использует
ся программа постраничного просмотра. Ниже приводится пример ис
пользования psource для просмотра содержимого модуля:
In [1]: import some_other_module
In [2]: psource some_other_module
#!/usr/bin/env python
class Foo:
"""мой класс Foo"""
def __init__(self):
pass
class Bar:
"""мой класс Bar"""
def __init__(self):
pass
class Bam:
"""мой класс Bam"""
def __init__(self):
pass
def baz():
"""моя функция baz"""
return None
Ниже приводится пример использования функции psource для про
смотра исходного кода класса в модуле:
In [3]: psource some_other_module.Foo
class Foo:
"""мой класс Foo"""
def __init__(self):
pass

86 Глава 2. IPython
и в следующем примере функция psource используется для вывода ис
ходного кода функции:
In [4]: psource some_other_module.baz
def baz():
"""моя функция baz"""
return None
psearch
Специальная функция psearch позволяет отыскать объект на языке Py
thon по имени, с возможностью использования шаблонных символов.
Здесь мы лишь коротко опишем функцию psearch. Если вам потребует
ся дополнительная информация о ней, вы можете обратиться к доку
ментации по специальным функциям, введя команду magic в строке
приглашения IPython и отыскав описание функции psearch в списке,
отсортированном по алфавиту.
Для начала объявим следующие объекты:
In [1]: a = 1
In [2]: aa = "one"
In [3]: b = 2
In [4]: bb = "two"
In [5]: c = 3
In [6]: cc = "three"
Мы можем отыскать все объекты, имена которых начинаются с симво
ла a, b или c, следующим способом:
In [7]: psearch a*
a
aa
abs
all
any
apply
In [8]: psearch b*
b
basestring
bb
bool
buffer
In [9]: psearch c*
c
callable
cc
chr

Сбор информации 87
classmethod
cmp
coerce
compile
complex
copyright
credits
Обратите внимание, что помимо наших объектов a, aa, b, bb, c и cc были
найдены еще и встроенные объекты.
Оператор ? может рассматриваться как приблизительный эквивалент
функции psearch. Например:
In [2]: import os
In [3]: psearch os.li*
os.linesep
os.link
os.listdir
In [4]: os.li*?
os.linesep
os.link
os.listdir
То есть вместо psearch мы вполне можем использовать *?.
Функция psearch имеет дополнительные параметры:
–s позволяет
включить, а
–e – исключить из поиска указанное пространство из отно
сящихся к этой функции пространств имен. В пространства имен вхо
дят builtin, user, user_global, internal и alias. По умолчанию функция
psearch производит поиск в пространствах имен builtin и user. Чтобы
произвести поиск только в пространстве имен user, можно было бы пе
редать функции psearch параметр
–e builtin, который исключит из по
иска пространство имен builtin. Использование этих параметров не
сколько неочевидно, но имеет некоторый смысл. По умолчанию функ
ция psearch производит поиск в пространствах имен builtin и user, по
этому, если использовать параметр
–s user, поиск попрежнему будет
производиться в пространствах имен builtin и user. В следующем при
мере мы еще раз выполнили поиск, но на этот раз исключили про
странство встроенных имен builtin:
In [10]: psearch e builtin a*
a
aa
In [11]: psearch e builtin b*
b
bb
In [12]: psearch e builtin c*
c
cc

88 Глава 2. IPython
Кроме того, функция psearch позволяет отыскивать объекты указанных
типов. Ниже мы выполнили поиск целочисленных объектов в про
странстве имен user:
In [13]: psearch e builtin * int
a
b
c
В следующем примере произведен поиск строк:
In [14]: psearch e builtin * string
__
___
__name__
aa
bb
cc
Объекты __ и ___, которые были найдены здесь, являются сокращения
ми IPython. Объект __name__ – это специальная переменная, которая
хранит имя модуля. Если переменная __name__ содержит строку
'__main__', это означает, что модуль выполняется как самостоятель
ный сценарий, а не импортируется другим модулем.
who
Оболочка IPython предоставляет множество способов получения спи
сков интерактивных объектов. Первый из них – функция who. Ниже
приводится продолжение предыдущего примера с переменными a, aa,
b, bb, c и cc и использованием функции who:
In [15]: who
a aa b bb c cc
Эта функция не содержит никаких подвохов, она просто выводит пере
чень всех объектов, которые были определены в интерактивном режи
ме. Функцию who можно использовать для выборки переменных опре
деленных типов, например:
In [16]: who int
a b c
In [17]: who str
aa bb cc
who_ls
Функция who_ls похожа на функцию who, за исключением того, что она
не выводит перечень имен соответствующих переменных, а возвраща
ет список. Ниже приводится пример использования функции who_ls
без аргументов:

Сбор информации 89
In [18]: who_ls
Out[18]: ['a', 'aa', 'b', 'bb', 'c', 'cc']
А в следующем примере производится выборка объектов определенно
го типа:
In [19]: who_ls int
Out[19]: ['a', 'b', 'c']
In [20]: who_ls str
Out[20]: ['aa', 'bb', 'cc']
Функция who_ls возвращает список имен, поэтому вы можете полу
чить доступ к нему с помощью переменной _, которая содержит «по
следний выведенный результат». Ниже демонстрируется способ обхо
да последнего полученного списка с соответствующими именами пере
менных:
In [21]: for n in _:
....: print n
....:
....:
aa
bb
cc
whos
Функция whos похожа на функцию who, но в отличие от последней
функция whos выводит информацию в табличном виде. Ниже приво
дится пример использования функции whos без аргументов:
In [22]: whos
Variable Type Data/Info

a int 1
aa str one
b int 2
bb str two
c int 3
cc str three
n str cc
Так же, как и функция who, она способна отбирать переменные в соот
ветствии с указанным типом:
In [23]: whos int
Variable Type Data/Info

a int 1
b int 2
c int 3

90 Глава 2. IPython
In [24]: whos str
Variable Type Data/Info

aa str one
bb str two
cc str three
n str cc
История
В оболочке IPython существует два способа получения доступа к исто
рии вводившихся команд. Первый основан на использовании под
держки библиотеки readline, а второй – на использовании специаль
ной функции hist.
Поддержка readline
В оболочке IPython обеспечивается доступ ко всем функциональным
возможностям, которые может предоставить приложение, обладаю
щее поддержкой библиотеки readline. Если вы привыкли пользовать
ся управляющими комбинациями для выполнения поиска по истории
команд в оболочке Bash, значит у вас не будет проблем с использовани
ем той же самой функциональности в IPython. В примере ниже опре
деляется несколько переменных, а затем производится поиск по исто
рии команд:
In [1]: foo = 1
In [2]: bar = 2
In [3]: bam = 3
In [4]: d = dict(foo=foo, bar=bar, bam=bam)
In [5]: dict2 = dict(d=d, foo=foo)
In [6]:
(reverse i search)`fo': dict2 = dict(d=d, foo=foo)

(reverse i search)`fo': d = dict(foo=foo, bar=bar, bam=bam)
Здесь мы нажали комбинацию клавиш Ctrlr, затем ввели строку fo, ко
торая выступает в качестве критерия поиска. В результате была полу
чена строка, которую мы ввели в приглашении IPython In [5]. Пользу
ясь поддержкой поиска в библиотеке readline, мы вновь нажали ком
бинацию Ctrlr и получили строку, которую мы ввели в приглашении
IPython In [4].
Есть еще некоторые комбинации клавиш, которые можно использо
вать при наличии поддержки readline, но мы коснемся их очень крат
ко. Комбинация Ctrla переносит курсор в начало строки, комбинация

Сбор информации 91
Ctrle – в конец строки. Комбинация Ctrlf вызывает перемещение кур
сора на один символ вперед (forward), а комбинация Ctrlb – на один
символ назад (backward). Комбинация Ctrld удаляет (delete) один сим
вол под курсором, а комбинация Ctrlh – удаляет один символ левее
курсора (аналогично действию клавиши Backspace (забой)). Комбина
ция Ctrlp вызывает перемещение на одну команду назад, к началу ис
тории, а комбинация Ctrln – на одну команду вперед, к концу истории.
Дополнительную информацию о возможностях readline можно полу
чить в справочном руководстве *nixсистем с помощью команды man
readline.
Функция hist
В дополнение к возможности доступа к истории команд посредством
библиотеки readline оболочка IPython также предоставляет для этого
свою собственную специальную функцию с именем history и сокра
щенный вариант имени hist. При вызове без параметров функция hist
выводит последовательный список команд, вводившихся пользовате
лем. По умолчанию строки в этом списке пронумерованы. В следую
щем примере мы определили несколько переменных, перешли в дру
гой каталог и затем вызвали функцию hist:
In [1]: foo = 1
In [2]: bar = 2
In [3]: bam = 3
In [4]: cd /tmp
/tmp
In [5]: hist
1: foo = 1
2: bar = 2
3: bam = 3
4: _ip.magic("cd /tmp")
5: _ip.magic("hist ")
Четвертый и пятый элементы в списке выше – это вызовы специаль
ных функций. Обратите внимание на то, как они были изменены обо
лочкой IPython, благодаря чему можно видеть, что в действительно
сти команды выполняются через вызов оболочкой функции magic().
Чтобы подавить вывод номеров строк, можно использовать параметр
–n. Ниже приводится пример использования функции hist с парамет
ром
–n:
In [6]: hist n
foo = 1
bar = 2
bam = 3
_ip.magic("cd /tmp")

92 Глава 2. IPython
_ip.magic("hist ")
_ip.magic("hist n")
Это очень удобно, когда при работе в IPython возникает необходимость
скопировать блок программного кода из оболочки IPython и вставить
его в текстовый редактор.
Параметр
–t возвращает «преобразованное» (translated) представление
истории, где показано, как в действительности выглядят команды,
введенные в оболочке IPython. Этот параметр установлен по умолча
нию. Ниже приводится история команд, которая образовалась к на
стоящему моменту, полученная с параметром
–t:
In [7]: hist t
1: foo = 1
2: bar = 2
3: bam = 3
4: _ip.magic("cd /tmp")
5: _ip.magic("hist ")
6: _ip.magic("hist n")
7: _ip.magic("hist t")
При использовании параметра –r выводится история команд в «непо
средственном (сыром) виде» (raw) и отображаются команды в том ви
де, в каком они вводились. Ниже приводится результат применения
параметра
–r:
In [8]: hist r
1: foo = 1
2: bar = 2
3: bam = 3
4: cd /tmp
5: hist
6: hist n
7: hist t
8: hist r
Параметр –g функции обеспечивает возможность поиска в истории по
заданному шаблону. Ниже приводится пример использования пара
метра
–g, чтобы отыскать в истории все вхождения слова hist:
In [9]: hist g hist
0187: hist
0188: hist n
0189: hist g import
0190: hist h
0191: hist t
0192: hist r
0193: hist d
0213: hist g foo
0219: hist g hist
===
^shadow history ends, fetch by %rep (must start with 0)

Сбор информации 93
=== start of normal history ===
5 : _ip.magic("hist ")
6 : _ip.magic("hist n")
7 : _ip.magic("hist t")
8 : _ip.magic("hist r")
9 : _ip.magic("hist g hist")
Обратите внимание на слова «shadow history» (теневая история), поя
вившиеся в предыдущем примере. «Теневая история» – это история
всех команд, которые вводились когдалибо. Строки с элементами этой
истории отображаются в самом начале списка и начинаются с нуля.
Строки с элементами истории текущего сеанса отображаются в конце
списка и не начинаются с нуля.
История результатов
И в стандартной оболочке Python, и в оболочке IPython имеется воз
можность доступа не только к истории вводившихся команд, но к ис
тории результатов. Первый способ доступа заключается в использова
нии специальной переменной с именем _, которая содержит «послед
ний выведенный результат». Ниже приводится пример использования
переменной _ в IPython:
In [1]: foo = "foo_string"
In [2]: _
Out[2]: ''
In [3]: foo
Out[3]: 'foo_string'
In [4]: _
Out[4]: 'foo_string'
In [5]: a = _
In [6]: a
Out[6]: 'foo_string'
Когда в приглашении In [1] мы определили переменную foo, перемен
ная _ в In [2] вернула пустую строку. Когда мы вывели значение пере
менной foo в In [3], переменная _ в In [4] вернула полученный выше ре
зультат. А операция в In [5] показала, что имеется возможность сохра
нять результат в переменной.
Ниже приводится тот же самый пример, выполненный в стандартной
оболочке Python:
>>> foo = "foo_string"
>>> _
Traceback (most recent call last):
File "", line 1, in

94 Глава 2. IPython
NameError: name '_' is not defined
>>> foo
'foo_string'
>>> _
'foo_string'
>>> a = _
>>> a
'foo_string'
Здесь, в стандартной оболочке Python, наблюдаем практически ту же
самую картину, что и в оболочке IPython, за исключением того, что
при попытке обратиться к имени _ до того, как чтонибудь будет выве
дено, возбуждается исключение NameError.
Оболочка IPython поднимает концепцию «последнего выведенного ре
зультата» на новый уровень. В разделе «Выполнение системных ко
манд» было дано описание операторов ! и !! и говорилось, что резуль
тат работы оператора !! нельзя сохранить в переменной, но позднее его
можно использовать. Проще говоря, у вас имеется доступ к любым ре
зультатам с помощью символа подчеркивания (_), вслед за которым
следует число в соответствии с синтаксисом _[0
–9]*. Число должно со
ответствовать результату в строке Out [0
–9]*.
Чтобы продемонстрировать, как действует этот прием, мы сначала вы
ведем списки файлов, но при этом ничего не будем делать с получен
ными результатами:
In [1]: !!ls apa*py
Out[1]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: apache_conf_docroot_replace.py
1: apache_log_parser_regex.py
2: apache_log_parser_split.py
In [2]: !!ls e*py
Out[2]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: elementtree_system_profile.py
1: elementtree_tomcat_users.py
In [3]: !!ls t*py
Out[3]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: test_apache_log_parser_regex.py
1: test_apache_log_parser_split.py
Теперь у нас должна иметься возможность доступа к Out [1 –3] с помо
щью _1, _2 и _3. Чтобы было более понятно, мы присвоим эти значения
переменным с говорящими именами:
In [4]: apache_list = _1
In [5]: element_tree_list = _2
In [6]: tests = _3

Автоматизация и сокращения 95
Теперь apache_list, tree_list и tests содержат те же элементы, которые
были выведены в строках Out [1], Out [2] и Out [3], соответственно:
In [7]: apache_list
Out[7]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: apache_conf_docroot_replace.py
1: apache_log_parser_regex.py
2: apache_log_parser_split.py
In [8]: element_tree_list
Out[8]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: elementtree_system_profile.py
1: elementtree_tomcat_users.py
In [9]: tests
Out[9]: SList (.p, .n, .l, .s, .grep(), .fields() available). Value:
0: test_apache_log_parser_regex.py
1: test_apache_log_parser_split.py
Обобщим сказанное: в оболочке IPython имеется возможность обра
щаться к выведенным ранее результатам либо через специальную пе
ременную _, либо через _ с явно указанным номером полученного ра
нее результата.
Автоматизация и сокращения
Оболочка IPython делает достаточно много, чтобы повысить произво
дительность труда, и кроме этого она предоставляет ряд функций
и особенностей, помогающих автоматизировать решение задач в IPy
thon.
alias
Для начала упомянем специальную функцию alias. Мы уже рассмат
ривали ее выше в этой главе, поэтому не будем повторно описывать
принципы ее использования. Но нам хотелось бы напомнить, что
функция alias способна не только помочь использовать системные ко
манды системы *nix в оболочке IPython, но также может оказать по
мощь в автоматизации решения задач.
macro
Функция macro позволяет определять блоки программного кода, кото
рые могут выполняться позднее, в составе любого программного кода,
с которым вам придется работать. Макроопределения, создаваемые
с помощью специальной функции macro, выполняются в текущем кон
тексте вашего программного кода. Если у вас имеется некоторая по
следовательность инструкций, которую вы часто используете для об
работки своих файлов, вы можете создать макроопределение, которое

96 Глава 2. IPython
будет выполнять эту работу. Чтобы получить представление о том, как
с помощью макроопределения можно выполнять обработку списка
файлов, рассмотрим следующий пример:
In [1]: dirlist = []
In [2]: for f in dirlist:
...: print "working on", f
...: print "done with", f
...: print "moving %s to %s.done" % (f, f)
...: print "*" * 40
...:
...:
In [3]: macro procdir 2
Macro `procdir` created. To execute, type its name (without quotes).
Macro contents:
for f in dirlist:
print "working on", f
print "done with", f
print "moving %s to %s.done" % (f, f)
print "*" * 40
К моменту создания цикла в In [2] в dirlist не было ни одного элемен
та, чтобы их можно было обойти в цикле, но так как мы предполагаем,
что позднее в dirlist появятся элементы, мы создали макрокоманду
с именем procdir, которая выполняет обход списка в цикле. Макроко
манда создается в соответствии с синтаксисом: macro macro_name range_
of_lines, где под range_of_lines подразумевается список строк истории
команд, которые должны быть добавлены в макроопределение. Строки
в этом списке должны определяться номерами или диапазонами номе
ров (например, 1
–4) строк и отделяться друг от друга пробелами.
В следующем примере мы создали список имен файлов и сохранили
его в переменной dirlist, а затем выполнили макрокоманду procdir.
Макрокоманда выполнит обход списка файлов в dirlist:
In [4]: dirlist = ['a.txt', 'b.txt', 'c.txt']
In [5]: procdir
> procdir()
working on a.txt
done with a.txt
moving a.txt to a.txt.done
****************************************
working on b.txt
done with b.txt
moving b.txt to b.txt.done
****************************************
working on c.txt
done with c.txt
moving c.txt to c.txt.done
****************************************

Автоматизация и сокращения 97
После того как макрокоманда будет определена, ее можно будет отре
дактировать с помощью функции edit. В результате этого будет от
крыт текстовый редактор. Очень удобно иметь возможность выпол
нить отладку макрокоманды, добиваясь правильной ее работы, преж
де чем сохранить ее.
store
Вы можете сохранить свои макрокоманды и простые переменные с по
мощью специальной функции store. Она имеет следующий стандарт
ный формат: store variable. Кроме того, функция store может прини
мать дополнительные параметры, которые могут оказаться для вас по
лезными: вызов store
–d variable удалит указанную переменную из
списка сохраняемых; параметр
–z удалит все сохраняемые перемен
ные; а параметр
–r выполнит повторную загрузку всех сохраняемых
переменных.
reset
Функция reset удаляет все переменные из интерактивного простран
ства имен. В следующем примере определяются три переменные, за
тем вызывается функция whos, чтобы убедиться в их присутствии, за
тем выполняется очистка пространства имен с помощью функции re
set и повторно вызывается функция whos, чтобы убедиться в том, что
переменные исчезли:
In [1]: a = 1
In [2]: b = 2
In [3]: c = 3
In [4]: whos
Variable Type Data/Info

a int 1
b int 2
c int 3
In [5]: reset
Once deleted, variables cannot be recovered. Proceed (y/[n])? y
(После удаления переменные нельзя будет восстановить. Продолжить (y/[n])?)
In [6]: whos
Interactive namespace is empty.
(Интерактивное пространство имен очищено.)
run
Функция run выполняет указанный файл в оболочке IPython. Это, кро
ме всего, позволяет работать с модулями на языке Python во внешнем
текстовом редакторе и интерактивно тестировать внесенные изменения

98 Глава 2. IPython
в IPython. После выполнения указанной программы управление воз
вращается в оболочку IPython. Функция run имеет следующий формат
записи: run options specified_file args.
При использовании параметра
–n переменная __name__ модуля получа
ет в качестве значения название модуля, а не строку '__main__'. Это
приводит к тому, что модуль выполняется так, как если бы он был
просто импортирован.
При использовании параметра
–i модуль выполняется в текущем про
странстве имен оболочки IPython, благодаря чему модуль получает
доступ ко всем переменным, которые были определены.
При использовании параметра
–e оболочка IPython будет игнориро
вать вызов функции sys.exit() и исключение SystemExit. Даже если
они будут иметь место, оболочка IPython продолжит свою работу.
При использовании параметра
–t оболочка IPython выведет информа
цию о времени выполнения модуля.
При использовании параметра
–d указанный модуль будет запущен
под управлением отладчика Python (pdb).
При использовании параметра
–p указанный модуль будет запущен
под управлением профилировщика.
save
Функция save сохраняет указанные строки ввода в указанный файл.
Порядок использования функции save: save options filename lines.
Строки могут указываться в том же формате, что и в функции macro.
Единственный дополнительный параметр
–r определяет, что в файл
следует сохранить строки в непреобразованном виде, то есть том виде,
в каком они вводились. По умолчанию строки сохраняются в преобра
зованном, стандартном для языка Python, виде.
rep
Последняя функция, используемая для автоматизации решения за
дач, – это функция rep. Функция rep может принимать ряд парамет
ров, которые вы найдете полезными. Вызов функции rep без парамет
ров возвращает последний вычисленный результат и помещает стро
ковое его представление в следующей строке ввода. Например:
In [1]: def format_str(s):
...: return "str(%s)" % s
...:
In [2]: format_str(1)
Out[2]: 'str(1)'
In [3]: rep
In [4]: str(1)

Автоматизация и сокращения 99
Вызов функции rep в строке In [3] привел к вставке текста в строке In
[4]. Такая ее особенность позволяет программно генерировать ввод
в оболочке IPython. Это особенно удобно при использовании комбина
ций макрокоманд и генераторов.
Обычно функция rep без параметров используется при редактирова
нии без использования мыши. Если у вас имеется переменная, содер
жащая некоторое значение, с помощью этой функции его можно бу
дет редактировать непосредственно. В качестве примера представим,
что у нас имеется функция, которая возвращает каталог bin, куда был
установлен некоторый пакет. Мы сохраняем каталог bin в перемен
ной с именем a:
In [2]: a = some_blackbox_function('squiggly')
In [3]: a
Out[3]: '/opt/local/squiggly/bin'
Если вызвать функцию rep прямо сейчас, мы получим строку /opt/lo
cal/squiggly/bin в новой строке ввода, с мигающим курсором в конце
строки, приглашающим нас к ее редактированию:
In [4]: rep
In [5]: /opt/local/squiggly/bin<мигающий курсор>
Если нам требуется сохранить не каталог bin, а корневой каталог паке
та, мы можем просто удалить bin в конце строки, окружить строку ка
вычками и добавить в начало строки ввода имя новой переменной
и оператор присваивания:
In [5]: new_a = '/opt/local/squiggly'
Теперь у нас имеется новая переменная, содержащая строку с именем
корневого каталога данного пакета.
Несомненно, мы могли бы просто скопировать и вставить эту строку,
но для этого пришлось бы выполнить больший объем работы. Зачем
нам отвлекаться от столь удобной клавиатуры, чтобы дотянуться до
мыши? Теперь вы можете использовать переменную new_a в качестве
корневого каталога для выполнения любых необходимых действий
спакетом.
Когда функции rep в качестве параметра передается число, она выби
рает значение соответствующей строки ввода из истории команд,
вставляет ее в следующую строку ввода и помещает курсор в конец
этой строки. Это бывает удобно для запуска, редактирования и повтор
ного запуска отдельных строк или даже небольших блоков програм
мы. Например:
In [1]: map = (('a', '1'), ('b', '2'), ('c', '3'))
In [2]: for alph, num in map:

100 Глава 2. IPython
...: print alph, num
...:
...:
a 1
b 2
c 3
Теперь нам нужно отредактировать строку ввода In [2] так, чтобы вы
водились значения, умноженные на 2. Для этого можно снова ввести
цикл for или воспользоваться функцией rep:
In [3]: rep 2
In [4]: for alph, num in map:
print alph, int(num) * 2
...:
...:
a 2
b 4
c 6
Кроме того, функция rep способна принимать диапазоны строк. Син
таксис диапазона аналогичен тому, что используется в функции macro,
которая уже рассматривалась выше в этой главе. Когда функции rep
передается диапазон строк, они выполняются немедленно, например:
In [1]: i = 1
In [2]: i += 1
In [3]: print i
2
In [4]: rep 2 3
lines [u'i += 1\nprint i\n']
3
In [7]: rep 2 3
lines [u'i += 1\nprint i\n']
4
Здесь в строках с In [1] по In [3] мы определили переменную, увеличи
ли ее на 1, и вывели текущее значение. В строках In [4] и In [7] мы
предложили функции rep повторить строки 2 и 3. Обратите внимание
на отсутствие двух строк (5 и 6) – эти строки были выполнены после
строки In [4].
Последний параметр функции rep, который мы рассмотрим, – строка.
Этот случай можно выразить словами: «передача слова функции rep»
или даже: «передача функции rep искомой строки без кавычек». На
пример:
In [1]: a = 1
In [2]: b = 2

В заключение 101
In [3]: c = 3
In [4]: rep a
In [5]: a = 1
Здесь мы определили несколько переменных и потребовали от функ
ции rep повторить строку, в которой присутствует слово «a». В резуль
тате мы получили строку, введенную в приглашении In [1], и получи
ли возможность отредактировать ее и запустить повторно.
В заключение
Оболочка IPython является самым ценным инструментом в нашем ар
сенале. Мастерство владения ее возможностями напоминает мастерст
во владения текстовым редактором: чем большим опытом вы обладае
те, тем быстрее будете решать утомительные задачи. Даже несколько
лет тому назад, когда мы только начали использовать оболочку IPy
thon, она уже была весьма мощным инструментом. С тех пор ее мощь
увеличилась еще больше. Функция grep и обработка строк – это пер
вые две особенности, которые сразу же приходят на ум, когда мы заду
мываемся о понастоящему полезных и мощных возможностях, пере
чень которых непрерывно продолжает пополняться усилиями сообще
ства IPython. Мы настоятельно рекомендуем поближе познакомиться
с оболочкой IPython. Освоение ее – это надежные инвестиции в буду
щее, о которых вам не придется сожалеть.

3
Текст
Практически каждому системному администратору приходится иметь
дело с текстом в той или иной форме, например, с файлами журналов,
данными приложений, с XML, HTML и конфигурационными файла
ми или с выводом некоторых команд. Обычно для работы вполне хва
тает таких утилит, как grep и awk, но иногда для решения сложных за
дач необходим более элегантный и выразительный инструмент. Когда
возникает потребность создать файл с данными, извлеченными из дру
гих файлов, часто бывает достаточно перенаправить вывод процесса
обработки (здесь опять приходят на ум grep и awk) в файл. Но иногда
складываются ситуации, когда для выполнения задания требуется ин
струмент с более широкими возможностями.
Как мы уже говорили во «Введении», наш опыт показывает, что язык
Python можно рассматривать как более элегантный, выразительный
и расширяемый, чем Perl, Bash или другие языки программирова
ния, которые мы использовали в своей практике. Подробное описание
причин, почему мы оцениваем Python более высоко, чем Perl или Bash
(то же самое относится к sed и awk), приводится в главе 1. Стандартная
библиотека языка Python, особенности языка и встроенные типы
представляют собой мощные средства чтения текстовых файлов, ма
нипулирования текстом и извлечения информации из текстовых фай
лов. Язык Python и стандартная библиотека обладают богатыми и гиб
кими функциональными возможностями обработки текста с помощью
строкового типа, файлового типа и модуля регулярных выражений.
Недавнее пополнение стандартной библиотеки – модуль ElementTree –
чрезвычайно удобен при работе с данными в формате XML. В этой гла
ве мы покажем, как эффективно использовать стандартную библиоте
ку и встроенные компоненты при работе с текстовой информацией.

Встроенные компоненты Python и модули 103
Встроенные компоненты Python и модули
str
Строка – это просто последовательность символов. При любой работе
с текстовой информацией вы почти наверняка вынуждены будете ра
ботать с ней как со строковым объектом или с последовательностью
строковых объектов. Строковый тип, str, – это мощное, гибкое средст
во манипулирования строковыми данными. В этом разделе показыва
ется, как создавать строки и какие операции можно выполнять над
ними после их создания.
Создание строк
Обычно строка создается путем заключения некоторого текста в ка
вычки:
In [1]: string1 = 'This is a string'
In [2]: string2 = "This is another string"
In [3]: string3 = '''This is still another string'''
In [4]: string4 = """And one more string"""
In [5]: type(string1), type(string2), type(string3), type(string4)
Out[5]: (, , , )
Апострофы и кавычки, обычные и тройные, обозначают одно и то же:
все они создают объект типа str. Апострофы и кавычки идентичны по
своему действию и являются взаимозаменяемыми. Этим язык Python
отличается от командных оболочек UNIX, где апострофы и кавычки
не являются взаимозаменяемыми. Например:
jmjones@dink:~$ FOO=sometext
jmjones@dink:~$ echo "Here is $FOO"
Here is sometext
jmjones@dink:~$ echo 'Here is $FOO'
Here is $FOO
В языке Perl апострофы и кавычки также не могут замещать друг дру
га при создании строк. Ниже приводится похожий пример на языке
Perl.
#!/usr/bin/perl
$FOO = "some_text";
print " $FOO \n";
print ' $FOO \n';
И вот какие результаты дает этот небольшой сценарий на языке Perl:
jmjones@dinkgutsy:code$ ./quotes.pl
some_text
$FOO \njmjones@dinkgutsy:code$

104 Глава 3. Текст
Это различие отсутствует в языке Python. Право определять различия
Python оставляет за программистом. Например, вы можете использо
вать апострофы, когда внутри строки должны находиться кавычки
и вам не хотелось бы экранировать их (символом обратного слеша).
Точно так же вы можете использовать кавычки, когда внутри строки
должны присутствовать апострофы и вам не хотелось бы экранировать
их, как показано в примере 3.1.
Пример 3.1. Кавычки и апострофы в языке Python
In [1]: s = "This is a string with 'quotes' in it"
In [2]: s
Out[2]: "This is a string with 'quotes' in it"
In [3]: s = 'This is a string with \'quotes\' in it'
In [4]: s
Out[4]: "This is a string with 'quotes' in it"
In [5]: s = 'This is a string with "quotes" in it'
In [6]: s
Out[6]: 'This is a string with "quotes" in it'
In [7]: s = "This is a string with \"quotes\" in it"
In [8]: s
Out[8]: 'This is a string with "quotes" in it'
Обратите внимание, что во 2й и 4й строках вывода (Out [4] и Out [8])
включение в строку экранированных кавычек того же типа, что и окру
жающие строку, привело при выводе к изменению типа наружных ка
вычек. (В действительности это приводит отображение строки
к «правильному» применению кавычек разных типов.)
Иногда бывает необходимо, чтобы в одной строке объединялось не
сколько строк. Иногда эту проблему можно решить, вставляя символ
\n там, где необходимо создать разрыв строки, но это довольно неудоб
ный способ. Другая, более ясная альтернатива заключается в исполь
зовании тройных кавычек, которые позволяют определять много
строчный текст. В примере 3.2 демонстрируется неудачная попытка
использовать апострофы для определения многострочного текста и ус
пешная попытка использовать тройные апострофы.
Пример 3.2. Тройные кавычки
In [6]: s = 'this is

File "", line 1
s = 'this is
^
SyntaxError: EOL while scanning single quoted string
(SyntaxError: обнаружен конец строки при интерпретации строки в апострофах)

Встроенные компоненты Python и модули 105
In [7]: s = '''this is a
...: multiline string'''
In [8]: s
Out[8]: 'this is a\nmultiline string'
Помимо этого существует еще один способ обозначения строк, которые
в языке Python называются «сырыми» строками. Сырые строки созда
ются добавлением символа r непосредственно перед открывающей ка
вычкой. По сути дела, сырые строки отличаются от обычных строк
тем, что в сырых строках Python не интерпретирует экранированные
последовательности символов, тогда как в обычных строках они интер
претируются. При интерпретации экранированных последовательно
стей в языке Python соблюдается практически тот же набор правил, ко
торый описывается стандартом языка C. Например, в обычных стро
ках последовательность \t интерпретируется как символ табуляции,
\n– как символ новой строки и \r – как перевод строки. В табл. 3.1
приводится список экранированных последовательностей в языке Py
thon.
Таблица 3.1. Экранированные последовательности в языке Python
Последовательность Интерпретируется как
\newlineИгнорируется
\\Символ обратного слеша
\'Апостроф
\"Кавычка
\aASCIIсимвол звукового сигнала
\bASCIIсимвол забоя
\fASCIIсимвол перевода формата (страницы)
\nASCIIсимвол новой строки
\N{имя}Именованный символ Юникода (только для строк
в кодировке Юникод)
\rASCIIсимвол возврата каретки
\tASCIIсимвол горизонтальной табуляции
\uxxxxШестнадцатеричный код 16битового символа
(только для строк в кодировке Юникод)
\UxxxxxxxxШестнадцатеричный код 32битового символа
(только для строк в кодировке Юникод)
\vASCIIсимвол вертикальной табуляции
\oooВосьмеричный код символа
\xhhШестнадцатеричный код символа

106 Глава 3. Текст
Об экранированных последовательностях и сырых строках стоит пом
нить, особенно, когда приходится иметь дело с регулярными выраже
ниями, к которым мы подойдем далее в этой главе. В примере 3.3 де
монстрируется использование экранированных последовательностей
и неформатированных строк.
Пример 3.3. Экранированные последовательности и сырые строки
In [1]: s = '\t'
In [2]: s
Out[2]: '\t'
In [3]: print s
In [4]: s = r'\t'
In [5]: s
Out[5]: '\\t'
In [6]: print s
\t
In [7]: s = '''\t'''
In [8]: s
Out[8]: '\t'
In [9]: print s
In [10]: s = r'''\t'''
In [11]: s
Out[11]: '\\t'
In [12]: print s
\t
In [13]: s = r'\''
In [14]: s
Out[14]: "\\'"
In [15]: print s
\'
Когда выполняется интерпретация экранированных последовательно
стей, \t превращается в символ табуляции. Когда интерпретация не
выполняется, экранированная последовательность \t воспринимает
ся, как строка из двух символов, \ и t. Строки, окруженные кавычка
ми или апострофами, обычными или тройными, подразумевают, что
последовательность \t будет интерпретироваться как символ табуля
ции. Если те же самые строки предваряются символом r, последова
тельность \t интерпретируется как два символа, \ и t.
Еще один фокус этого примера – различия между __repr__ и __str__. Ко
гда имя переменной вводится в строке приглашения оболочки IPython

Встроенные компоненты Python и модули 107
и нажимается клавиша Enter, значение переменной отображается вы
зовом метода __repr__. Когда вводится инструкция print, которой пере
дается имя переменной, и нажимается клавиша Enter, переменная
отображается вызовом метода __str__. Инструкция print интерпрети
рует экранированные последовательности в строке и отображает их со
ответствующим образом. Подробнее о __repr__ и __str__ рассказывает
ся в главе 2, в разделе «Базовые понятия».
Встроенные методы извлечения строковых данных
Строки в языке Python – это объекты, поэтому они имеют методы, ко
торые могут вызываться для выполнения определенных операций. Од
нако под «методами» мы подразумеваем не только методы, которыми
обладает тип str, но и любые другие способы, позволяющие извлекать
данные из объектов типа str. Сюда входят все методы типа str, а также
операторы in и not in, приведенные в примере, следующем ниже.
С технической точки зрения, операторы проверки условия in и not in
вызывают метод __contains__() объекта str. За дополнительной инфор
мацией о том, как работают эти операторы, обращайтесь к приложе
нию. Операторы in и not in могут использоваться для проверки, яв
ляется ли некоторая строка частью другой строки, как показано в при
мере 3.4.
Пример 3.4. Операторы in и not in
In [1]: import subprocess
In [2]: res = subprocess.Popen(['uname', ' sv'], stdout=subprocess.PIPE)
In [3]: uname = res.stdout.read().strip()
In [4]: uname
Out[4]: 'Linux #1 SMP Tue Feb 12 02:46:46 UTC 2008'
In [5]: 'Linux' in uname
Out[5]: True
In [6]: 'Darwin' in uname
Out[6]: False
In [7]: 'Linux' not in uname
Out[7]: False
In [8]: 'Darwin' not in uname
Out[8]: True
Если строка string2 содержит строку string1, то выражение string1 in
string2 вернет значение True, в противном случае – значение False. По
этому проверка вхождения строки "Linux" в строку uname в нашем слу
чае дает в результате значение True, а проверка вхождения строки
"Darwin" в строку uname дает значение False. Применение оператора not
in мы привели «для комплекта».

108 Глава 3. Текст
Иногда бывает достаточно узнать, что некоторая строка является под
строкой другой строки. А иногда требуется узнать, в какой позиции
находится искомая подстрока. Выяснить это можно с помощью мето
дов find() и index(), как показано в примере 3.5.
Пример 3.5. Методы find() и index()
In [9]: uname.index('Linux')
Out[9]: 0
In [10]: uname.find('Linux')
Out[10]: 0
In [11]: uname.index('Darwin')

Traceback (most recent call last)
/home/jmjones/code/ in ()
: substring not found
In [12]: uname.find('Darwin')
Out[12]: 1
Если строка string1 присутствует в строке string2 (как в данном приме
ре), метод string2.find(string1) вернет индекс первого символа string1
в строке string2, в противном случае он вернет
–1. (Не беспокойтесь,
к индексам мы перейдем через мгновение). Точно так же, если строка
string1 присутствует в строке string2, метод string2.index(string1) вер
нет индекс первого символа string1 в строке string2, в противном слу
чае он возбудит исключение ValueError. В данном примере метод find()
обнаружил подстроку "Linux" в начале строки, поэтому он вернул зна
чение 0. Однако метод find() не смог обнаружить подстроку "Darwin"
в этой строке, поэтому он вернул значение
–1. Когда в операционной
системе Linux была выполнена попытка отыскать подстроку "Linux"
спомощью метода index(), был получен тот же результат, что и в слу
чае применения метода find(). Но при попытке отыскать подстроку
"Darwin" метод index() возбудил исключение ValueError, показывая, что
не смог отыскать эту подстроку.
Итак, что можно делать с этими числовыми «индексами»? Зачем они
нам нужны? Строки интерпретируются как списки символов. «Ин
декс», который возвращается методами find() и index(), просто пока
зывает, начиная с какого символа в большей строке было обнаружено
совпадение, как показано в примере 3.6.
Пример 3.6. Срез строки
In [13]: smp_index = uname.index('SMP')
In [14]: smp_index
Out[14]: 9
In [15]: uname[smp_index:]

Встроенные компоненты Python и модули 109
Out[15]: 'SMP Tue Feb 12 02:46:46 UTC 2008'
In [16]: uname[:smp_index]
Out[16]: 'Linux #1 '
In [17]: uname
Out[17]: 'Linux #1 SMP Tue Feb 12 02:46:46 UTC 2008'
Мы оказались в состоянии увидеть все символы, начиная с символа,
индекс которого был получен в результате поиска подстроки "SMP",
и до конца строки, воспользовавшись синтаксической конструкцией
извлечения среза string[index:]. Мы также смогли увидеть все симво
лы от начала строки uname до индекса, который был получен в резуль
тате поиска подстроки "SMP", применив синтаксическую конструкцию
извлечения среза string[:index]. Все различия между этими двумя
конструкциями заключаются в местоположении символа двоеточия
(:) относительно индекса.
Цель примеров на извлечение среза строки и применения операторов
in и not in состоит в том, чтобы показать вам, что строки являются по
следовательностями и поэтому обладают теми же особенностями, что
и другие последовательности, такие как списки. Более полно последо
вательности обсуждаются в разделе «Sequence Operations» в главе 4
книги «Python in a Nutshell» (издательство O’Reilly) Алекса Мартелли
(Alex Martelli) (этот раздел доступен в Интернете на сайте издательст
ва: http://safari.oreilly.com/0596100469/pythonian>CHP>4>SECT>6).
Еще два строковых метода, startswith() и endswith(), как следует из их
названий, помогут определить, «начинается» ли или «заканчивается»
ли строка определенной подстрокой, как показано в примере 3.7.
Пример 3.7. Методы startswith() и endswith()
In [1]: some_string = "Raymond Luxury Yacht"
In [2]: some_string.startswith("Raymond")
Out[2]: True
In [3]: some_string.startswith("Throatwarbler")
Out[3]: False
In [4]: some_string.endswith("Luxury Yacht")
Out[4]: True
In [5]: some_string.endswith("Mangrove")
Out[5]: False
Как видите, интерпретатор Python возвращает информацию, которая
говорит о том, что строка «Raymond LuxuryYacht» начинается с под
строки «Raymond» и заканчивается подстрокой «LuxuryYacht». Она
не начинается с подстроки «Throatwarbler» и не заканчивается подстро
кой «Mangrove». Достаточно просто те же результаты можно получить

110 Глава 3. Текст
с помощью операции извлечения среза, но такой подход к решению
выглядит менее наглядно и может показаться несколько утомитель
ным в реализации, как показано в примере 3.8:
Пример 3.8. Имитация методов startswith() и endswith()
In [6]: some_string[:len("Raymond")] == "Raymond"
Out[6]: True
In [7]: some_string[:len("Throatwarbler")] == "Throatwarbler"
Out[7]: False
In [8]: some_string[ len("Luxury Yacht"):] == "Luxury Yacht"
Out[8]: True
In [9]: some_string[ len("Mangrove"):] == "Mangrove"
Out[9]: False
Операция извлечения среза создает и возвращает новый строко
вый объект, а не изменяет саму строку. Если операции извлече
ния среза часто используются в сценарии, они могут оказывать
существенное влияние на потребление памяти и на производи
тельность. Даже если заметного влияния на производитель
ность не ощущается, тем не менее, лучше воздержаться от ис
пользования операций извлечения среза в случаях, когда доста
точно применения методов startswith() и endswith().
Мы сумели убедиться, что первые символы в строке some_string, число
которых равно длине строки «Raymond», соответствуют строке «Ray
mond». Другими словами, мы сумели убедиться, что строка some_string
начинается с подстроки «Raymond», без использования метода
startswith(). Точно так же мы смогли убедиться, что строка заканчи
вается подстрокой «LuxuryYacht».
Методы lstrip(), rstrip() и strip() без аргументов удаляют ведущие,
заключительные, и ведущие и заключительные пробельные символы,
соответственно. В качестве таких пробельных символов можно на
звать символы табуляции, пробелы, символы возврата каретки и но
вой строки. Метод lstrip() без аргументов удаляет любые пробельные
символы, которые находятся в начале строки, и возвращает новую
строку. Метод rstrip() без аргументов удаляет любые пробельные сим
волы, которые находятся в конце строки, и возвращает новую строку.
Метод strip() без аргументов удаляет любые пробельные символы, ко
торые находятся в начале и в конце строки, и возвращает новую стро
ку, как показано в примере 3.9.
Все три метода из семейства strip() не изменяют саму строку,
а создают и возвращают новый строковый объект. Возможно,
вы никогда не будете испытывать проблем с таким поведением
методов, но вы должны знать о нем.

Встроенные компоненты Python и модули 111
Пример 3.9. Методы lstrip(), rstrip() и strip()
In [1]: spacious_string = "\n\t Some Non Spacious Text\n \t\r"
In [2]: spacious_string
Out[2]: '\n\t Some Non Spacious Text\n \t\r'
In [3]: print spacious_string
Some Non Spacious Text
In [4]: spacious_string.lstrip()
Out[4]: 'Some Non Spacious Text\n \t\r'
In [5]: print spacious_string.lstrip()
Some Non Spacious Text
In [6]: spacious_string.rstrip()
Out[6]: '\n\t Some Non Spacious Text'
In [7]: print spacious_string.rstrip()
Some Non Spacious Text
In [8]: spacious_string.strip()
Out[8]: 'Some Non Spacious Text'
In [9]: print spacious_string.strip()
Some Non Spacious Text
Все три метода, lstrip(), rstrip() и strip(), могут принимать единст
венный необязательный аргумент: строку символов, которые следует
удалить из соответствующего места строки. Это означает, что методы
семейства strip() не просто удаляют пробельные символы – они могут
удалять любые символы, какие вы укажете:
In [1]: xml_tag = ""
In [2]: xml_tag.lstrip("<")
Out[2]: 'some_tag>'
In [3]: xml_tag.lstrip(">")
Out[3]: ''
In [4]: xml_tag.rstrip(">")
Out[4]: ' In [5]: xml_tag.rstrip("<")
Out[5]: ''
Здесь мы удалили из тега XML левую и правую угловые скобки, по од
ной за раз. А как быть, если нам потребуется удалить обе скобки одно
временно? Сделать это можно следующим способом:
In [6]: xml_tag.strip("<").strip(">")
Out[6]: 'some_tag'

112 Глава 3. Текст
Методы семейства strip() возвращают строку, поэтому мы можем вы
зывать другие строковые операции прямо вслед за вызовом метода
strip(). В этом примере мы объединили вызовы методов strip() в це
почку. Первый вызов метода strip() удаляет начальный символ (ле
вую угловую скобку) и возвращает строку, а второй метод strip() уда
ляет завершающий символ (правую угловую скобку) и возвращает
строку "some_tag". Однако существует более простой способ:
In [7]: xml_tag.strip("<>")
Out[7]: 'some_tag'
Возможно, вы подумали, что методы семейства strip() удаляют точное
вхождение указанной подстроки, но в действительности удаляются
любые последовательные вхождения указанных символов с соответст
вующей стороны строки. В этом последнем примере методу strip() бы
ло предписано удалить "<>". Это не означает точное соответствие под
строке "<>" и не означает, что должны быть удалены вхождения этих
двух символов, следующих друг за другом именно в таком порядке, –
это означает, что должны быть удалены символы "<" или ">", находя
щиеся в начале или в конце строки.
Ниже приводится, возможно, более понятный пример:
In [8]: gt_lt_str = "<><><>gt lt str<><><>"
In [9]: gt_lt_str.strip("<>")
Out[9]: 'gt lt str'
In [10]: gt_lt_str.strip("><")
Out[10]: 'gt lt str'
Здесь мы удалили все вхождения символов "<" или ">" с обоих концов
строки. Таким способом мы можем ликвидировать простые символы
и пробелы.
Следует заметить, что такой прием может работать несколько не так,
как вы ожидаете, например:
In [11]: foo_str = "blah"
In [12]: foo_str.strip("")
Out[12]: 'blah'
У вас могло бы сложиться мнение, что метод strip() в этом примере
удалит символы справа, но не слева. Однако он обнаружит и удалит
любые последовательные вхождения символов "<", "f", "o" и ">". Это
не ошибка, мы не пропустили второй символ "o". Вот еще один, заклю
чительный пример использования метода strip(), который прояснит
это утверждение:

Встроенные компоненты Python и модули 113
In [13]: foo_str.strip("> Out[13]: 'blah'
Здесь удаляются символы ">", "<", "f", "o", хотя они следуют не в этом
порядке.
Методы upper() и lower() удобно использовать, когда необходимо вы
полнить сравнение двух строк без учета регистра символов. Метод up
per() возвращает строку со всеми символами из оригинальной строки
в верхнем регистре. Метод lower() возвращает строку со всеми симво
лами из оригинальной строки в нижнем регистре, как показано в при
мере 3.10.
Пример 3.10. Методы upper() и lower()
In [1]: mixed_case_string = "VOrpal BUnny"
In [2]: mixed_case_string == "vorpal bunny"
Out[2]: False
In [3]: mixed_case_string.lower() == "vorpal bunny"
Out[3]: True
In [4]: mixed_case_string == "VORPAL BUNNY"
Out[4]: False
In [5]: mixed_case_string.upper() == "VORPAL BUNNY"
Out[5]: True
In [6]: mixed_case_string.upper()
Out[6]: 'VORPAL BUNNY'
In [7]: mixed_case_string.lower()
Out[7]: 'vorpal bunny'
Если вам необходимо извлечь часть строки, ограниченной какимили
бо символамиразделителями, метод split() предоставит вам эту воз
можность, как показано в примере 3.11.
Пример 3.11. Метод split()
In [1]: comma_delim_string = "pos1,pos2,pos3"
In [2]: pipe_delim_string = "pipepos1|pipepos2|pipepos3"
In [3]: comma_delim_string.split(',')
Out[3]: ['pos1', 'pos2', 'pos3']
In [4]: pipe_delim_string.split('|')
Out[4]: ['pipepos1', 'pipepos2', 'pipepos3']
Методу split() передается строкаразделитель, по которому необходи
мо разбить строку на подстроки. Часто это единственный символ, та
кой как запятая или вертикальная черта, но это может быть строка,
содержащая более одного символа. В данном примере мы разбили

114 Глава 3. Текст
строку comma_delim_string по запятым, а строку pipe_delim_string – по
символу вертикальной черты (|), передавая символ запятой или верти
кальной черты методу split(). Возвращаемым значением метода явля
ется список строк, каждая из которых представляет собой группу по
следовательных символов, находящихся между разделителями. Когда
в качестве разделителя необходимо использовать не единственный
символ, а некоторую строку, метод split() справится и с этим. К мо
менту написания этих строк в языке Python отсутствовал символьный
тип, поэтому хотя в примерах метод split() получал единственный
символ, он рассматривался методом как строка. Поэтому, когда мето
ду split() передается несколько символов, он обработает и их, как по
казано в примере 3.12.
Пример 3.12. Строка>разделитель в методе split()
In [1]: multi_delim_string = "pos1XXXpos2XXXpos3"
In [2]: multi_delim_string.split("XXX")
Out[2]: ['pos1', 'pos2', 'pos3']
In [3]: multi_delim_string.split("XX")
Out[3]: ['pos1', 'Xpos2', 'Xpos3']
In [4]: multi_delim_string.split("X")
Out[4]: ['pos1', '', '', 'pos2', '', '', 'pos3']
Обратите внимание, что сначала мы использовали в качестве раздели
теля строку "XXX" для разделения строки multi_delim_string. Как
и ожидалось, в результате был получен список ['pos1', 'pos2', 'pos3'].
Затем, мы использовали в качестве разделителя строку "XX" и метод
split() вернул ['pos1', 'Xpos2', 'Xpos3']. Здесь метод split() выбрал все
символы, находящиеся между соседними разделителями "XX". Под
строка "pos1" начинается с начала строки и простирается до первого
разделителя "XX"; подстрока "Xpos2" располагается сразу за первым
вхождением разделителя "XX" и простирается до второго его вхожде
ния; и подстрока "Xpos3" располагается сразу за вторым вхождением "
XX" и простирается до конца строки. Последний вызов метода split()
получает в качестве разделителя единственный символ "X". Обратите
внимание, что позициям между соседними символами "X" соответству
ют пустые строки ("") в результирующем списке. Это означает, что ме
жду соседними символами "X" ничего нет.
Но как быть, если необходимо разбить строку только по первым n вхо
ждениям указанного разделителя? Для этого методу split() следует
передать второй аргумент, с именем max_split. Когда во втором аргу
менте max_split методу split() передается целочисленное значение, он
выполнит только указанное число разбиений исходной строки:
In [1]: two_field_string = "8675309,This is a freeform, plain text, string"
In [2]: two_field_string.split(',', 1)
Out[2]: ['8675309', 'This is a freeform, plain text, string']

Встроенные компоненты Python и модули 115
Здесь мы разбиваем строку по запятым и предписываем методу split()
выполнить только одно разбиение. Несмотря на то, что в строке при
сутствует несколько запятых, строка была разбита только один раз.
Если необходимо разбить строку по пробелам, например, чтобы из
влечь из текста отдельные слова, эту задачу легко можно решить вызо
вом метода split() без аргументов:
In [1]: prosaic_string = "Insert your clever little piece of text here."
In [2]: prosaic_string.split()
Out[2]: ['Insert', 'your', 'clever', 'little', 'piece', 'of', 'text', 'here.']
Когда метод split() не получает никаких аргументов, по умолчанию
он выполняет разбиение строки по пробельным символам.
Чаще всего вы будете получать именно те результаты, которые ожида
ли получить. Однако в случае многострочного текста результат может
получиться неожиданным для вас. Часто при работе с многострочным
текстом бывает необходимо выполнять его обработку по одной строке
за раз. Но вы можете с удивлением обнаружить, что программа разби
вает такой текст на отдельные слова:
In [1]: multiline_string = """This
...: is
...: a multiline
...: piece of
...: text"""
In [2]: multiline_string.split()
Out[2]: ['This', 'is', 'a', 'multiline', 'piece', 'of', 'text']
Для таких случаев лучше подходит метод splitlines():
In [3]: lines = multiline_string.splitlines()
In [4]: lines
Out[4]: ['This', 'is', 'a multiline', 'piece of', 'text']
Метод splitlines() возвращает список всех строк из многострочного
текста и сохраняет группы «слов». После этого можно выполнить ите
рации по отдельным строкам текста и извлечь отдельные слова:
In [5]: for line in lines:
...: print "START LINE::"
...: print line.split()
...: print "::END LINE"
...:
START LINE::
['This']
::END LINE
START LINE::
['is']
::END LINE

116 Глава 3. Текст
START LINE::
['a', 'multiline']
::END LINE
START LINE::
['piece', 'of']
::END LINE
START LINE::
['text']
::END LINE
Иногда бывает необходимо не анализировать строку или извлекать из
нее информацию, а объединить в строку уже имеющиеся данные.
В этом случае вам на помощь придет метод join():
In [1]: some_list = ['one', 'two', 'three', 'four']
In [2]: ','.join(some_list)
Out[2]: 'one,two,three,four'
In [3]: ', '.join(some_list)
Out[3]: 'one, two, three, four'
In [4]: '\t'.join(some_list)
Out[4]: 'one\ttwo\tthree\tfour'
In [5]: ''.join(some_list)
Out[5]: 'onetwothreefour'
Учитывая, что исходные данные хранятся в виде списка, мы можем
объединить строки 'one', 'two', 'three' и 'four' несколькими способа
ми. Мы объединяем элементы списка some_list с помощью запятой, за
пятой и пробела, символа табуляции и пустой строки. Метод join() –
это строковый метод, поэтому вызов его в качестве метода литерала,
такого как ',', является корректным. Метод join() принимает в каче
стве аргумента последовательность строк и объединяет их в одну стро
ку так, чтобы элементы последовательности располагались в исходном
порядке и отделялись строкой, для которой вызывается метод join().
Мы должны предупредить вас об особенностях поведения метода
join() и об аргументе, который он ожидает получить. Обратите внима
ние: метод join() ожидает получить последовательность строк. А что
произойдет, если ему передать последовательность целых чисел?
Взгляните!
In [1]: some_list = range(10)
In [2]: some_list
Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [3]: ",".join(some_list)

exceptions.TypeError Traceback (most recent call last)
/Users/jmjones/

Встроенные компоненты Python и модули 117
TypeError: sequence item 0: expected string, int found
(TypeError: элемент 0 последовательности: ожидается строка, обнаружено целое)
Диагностическая информация, приложенная к исключению, возбуж
денному методом join(), достаточно ясно объясняет происшедшее, но
так как это довольно распространенная ошибка, в этом стоит разо
браться. Вы легко сможете избежать этой ловушки с помощью просто
го генератора списков. Ниже мы прибегли к помощи генератора спи
сков, чтобы преобразовать все элементы списка some_list, которые со
держат целые числа, в строки:
In [4]: ",".join([str(i) for i in some_list])
Out[4]: '0,1,2,3,4,5,6,7,8,9'
Или можно использовать выражениегенератор:
In [5]: ",".join(str(i) for i in some_list)
Out[5]: '0,1,2,3,4,5,6,7,8,9'
За дополнительной информацией об использовании генераторов спи
сков обращайтесь к разделу «Control Flow Statements» в главе 4 кни
ги «Python in a Nutshell» (этот раздел доступен в Интернете, на сайте
издательства: http://safari.oreilly.com/0596100469/pythonian>CHP>4>
SECT>10).
Последний метод, используемый для создания и изменения текстовых
строк, – это метод replace(). Метод replace() принимает два аргумента:
строку, которую требуется заменить, и строку замены, соответствен
но. Ниже приводится простой пример использования метода replace():
In [1]: replacable_string = "trancendental hibernational nation"
In [2]: replacable_string.replace("nation", "natty")
Out[2]: 'trancendental hibernattyal natty'
Обратите внимание, что метод replace() никак не проверяет, является
замещаемая строка частью слова или отдельным словом. Поэтому ме
тод replace() может использоваться только в случаях, когда просто
требуется заменить определенную последовательность символов дру
гой определенной последовательностью символов.
Иногда требуется более тонкое управление операцией замены, когда
вариант простой замены одной последовательности символов на дру
гую не подходит. В таких случаях обычно бывает необходимо иметь
возможность определить шаблон последовательности символов, кото
рую требуется найти и заменить. Применение шаблонов также может
помочь с поиском требуемого текста для последующего извлечения из
него данных. В тех случаях, когда предпочтительнее использовать
шаблоны, вам помогут регулярные выражения. Регулярные выраже
ния мы рассмотрим далее.

118 Глава 3. Текст
Так же, как операция извлечения среза и метод strip(), метод
replace() не изменяет существующую строку, а создает новую.
Строки Юникода
До сих пор во всех примерах работы со строками, которые мы видели,
использовались исключительно строковые объекты встроенного типа
str, но в языке Python существует еще один строковый тип, с которым
вам предстоит познакомиться: строки Юникода. Любые символы, ко
торые выводятся на экран дисплея, внутри компьютера представлены
числами. До появления кодировки Юникод существовало множество
разнообразных наборов отображения числовых кодов в символы в за
висимости от языка и платформы. Юникод – это стандарт, обеспечи
вающий единое отображение числовых кодов в символы, независимое
от языка, платформы или даже программы, выполняющей обработку
текста. В этом разделе мы рассмотрим понятие Юникода и способы ра
боты с этой кодировкой, имеющиеся в языке Python. Подробное опи
сание Юникода вы найдете в превосходном учебнике Э. М. Качлинга
(A. M. Kuchling) по адресу: http://www.amk.ca/python/howto/unicode.
Создание строк Юникода выглядит ничуть не сложнее, чем создание
обычных строк:
In [1]: unicode_string = u'this is a unicode string'
In [2]: unicode_string
Out[2]: u'this is a unicode string'
In [3]: print unicode_string
this is a unicode string
Или можно воспользоваться встроенной функцией unicode():
In [4]: unicode('this is a unicode string')
Out[4]: u'this is a unicode string'
На первый взгляд, в этом нет ничего примечательного, особенно если
учесть, что здесь мы имеем дело с символами одного языка. Но как
быть, когда приходится работать с символами из нескольких языков?
Здесь вам на помощь придет Юникод. Чтобы внутри строки Юникода
создать символ с определенным числовым кодом, можно воспользо
ваться нотацией \uXXXX или \uXXXXXXXX. Например, ниже приводится
строка Юникода, содержащая символы латиницы, греческого алфави
та и кириллицы:
In [1]: unicode_string = u'abc_\u03a0\u03a3\u03a9_\u0414\u0424\u042F'
In [2]: unicode_string
Out[2]: u'abc_\u03a0\u03a3\u03a9_\u0414\u0424\u042f'

Встроенные компоненты Python и модули 119
Интерпретатор генерирует строку (str) в зависимости от используемой
кодировки. В версии Python, которая поставляется вместе с компью
терами Mac, если попытаться вывести строку из предыдущего приме
ра с помощью инструкции print, будет получено сообщение об ошибке:
In [3]: print unicode_string

UnicodeEncodeError Traceback (most recent call last)
/Users/jmjones/ in ()
UnicodeEncodeError: 'ascii' codec can't encode characters in position 4 6:
ordinal not in range(128)
(UnicodeEncodeError: кодек 'ascii' не в состоянии кодировать символы
в позиции 4 6: числовые значения находятся вне диапазона range(128))
Мы должны определить другой кодек, который знает, как обрабаты
вать все символы в строке:
In [4]: print unicode_string.encode('utf 8')
Здесь мы выполнили кодирование строки, содержащей символы лати
ницы, греческого алфавита и кириллицы, в кодировку UTF8, которая
наиболее часто используется для кодирования данных Юникода.
Строки Юникода обладают теми же возможностями, такими как воз
можность выполнения проверки с помощью оператора in, и методами,
что и обычные строки, о которых мы уже говорили:
In [5]: u'abc' in unicode_string
Out[5]: True
In [6]: u'foo' in unicode_string
Out[6]: False
In [7]: unicode_string.split()
Out[7]: [u'abc_\u03a0\u03a3\u03a9_\u0414\u0424\u042f']
In [8]: unicode_string.
unicode_string.__add__ unicode_string.expandtabs
unicode_string.__class__ unicode_string.find
unicode_string.__contains__ unicode_string.index
unicode_string.__delattr__ unicode_string.isalnum
unicode_string.__doc__ unicode_string.isalpha
unicode_string.__eq__ unicode_string.isdecimal
unicode_string.__ge__ unicode_string.isdigit
unicode_string.__getattribute__ unicode_string.islower
unicode_string.__getitem__ unicode_string.isnumeric
unicode_string.__getnewargs__ unicode_string.isspace
unicode_string.__getslice__ unicode_string.istitle
unicode_string.__gt__ unicode_string.isupper
unicode_string.__hash__ unicode_string.join
unicode_string.__init__ unicode_string.ljust
unicode_string.__le__ unicode_string.lower
abc_  _ДФЯ

120 Глава 3. Текст
unicode_string.__len__ unicode_string.lstrip
unicode_string.__lt__ unicode_string.partition
unicode_string.__mod__ unicode_string.replace
unicode_string.__mul__ unicode_string.rfind
unicode_string.__ne__ unicode_string.rindex
unicode_string.__new__ unicode_string.rjust
unicode_string.__reduce__ unicode_string.rpartition
unicode_string.__reduce_ex__ unicode_string.rsplit
unicode_string.__repr__ unicode_string.rstrip
unicode_string.__rmod__ unicode_string.split
unicode_string.__rmul__ unicode_string.splitlines
unicode_string.__setattr__ unicode_string.startswith
unicode_string.__str__ unicode_string.strip
unicode_string.capitalize unicode_string.swapcase
unicode_string.center unicode_string.title
unicode_string.count unicode_string.translate
unicode_string.decode unicode_string.upper
unicode_string.encode unicode_string.zfill
unicode_string.endswith
Возможно, строки Юникода не потребуются вам немедленно. Но важ
но знать об их существовании, если вы собираетесь продолжать про
граммировать на языке Python.
re
Раз поставка языка Python комплектуется в соответствии с принци
пом «батарейки включены», можно было бы ожидать, что в состав
стандартной библиотеки будут включены модули для работы с регу
лярными выражениями. Так оно и есть. Акцент в этом разделе сделан
на использовании в языке Python регулярных выражений, а не на под
робностях их синтаксиса. Поэтому, если вы не знакомы с регулярны
ми выражениями, рекомендуем вам приобрести книгу «Mastering Re
gular Expressions» (O’Reilly) Джеффри Е. Ф. Фридла (Jeffrey E. F.
Friedl) (доступна также в Интернете на сайте издательства по адресу:
http://safari.oreilly.com/0596528124).
1 Далее мы предполагаем, что вы
достаточно уверенно оперируете регулярными выражениями, в про
тивном случае рекомендуем держать книгу Фридла под рукой.
Если вы знакомы с языком Perl, то, возможно, вы уже использовали
регулярные выражения с оператором =~. В языке Python поддержка
регулярных выражений реализована на уровне библиотеки, а не на
уровне синтаксических особенностей языка. Поэтому для работы с ре
гулярными выражениями необходимо импортировать модуль re. Ни
1 Джеффри Фридл «Регулярные выражения», 3е издание. – Пер. с англ. –
СПб.: Символплюс, 2008. В Интернете можно ознакомиться с 5й главой
этой книги по адресу http://www.books.ru/chapter?id= 592346&num=1. –
Прим. перев.

Встроенные компоненты Python и модули 121
же приводится простой пример создания и использования регулярно
го выражения, как показано в примере 3.13.
Пример 3.13. Простой пример использования регулярного выражения
In [1]: import re
In [2]: re_string = "{{(.*?)}}"
In [3]: some_string = "this is a string with {{words}} embedded in\
...: {{curly brackets}} to show an {{example}} of {{regular expressions}}"
In [4]: for match in re.findall(re_string, some_string):
...: print "MATCH >", match
...:
MATCH > words
MATCH > curly brackets
MATCH > example
MATCH > regular expressions
Первое, что мы сделали в этом примере, – импортировали модуль re.
Как вы уже наверняка поняли, имя re происходит от «regular expres
sions» (регулярные выражения). Затем мы создали строку re_string, ко
торая будет играть роль шаблона для поиска. Этому шаблону будут со
ответствовать две открывающие фигурные скобки ({{), вслед за которы
ми может следовать текст, завершающийся двумя закрывающими фи
гурными скобками (}}). Затем мы создали строку some_string, которая
содержит группы слов, окруженные фигурными скобками. И в конце
мы выполнили обход результатов поиска в строке some_string по шаб
лону re_string, полученных от функции findall() из модуля re. Как ви
дите, пример вывел строки words, curly brackets, example и regular expres
sions, которые представляют все группы слов, заключенные в двойные
фигурные скобки.
В языке Python существует два способа работы с регулярными выра
жениями. Первый заключается в непосредственном использовании
функций из модуля re, как в предыдущем примере. Второй способ со
стоит в том, чтобы создать объект скомпилированного регулярного вы
ражения и затем использовать методы этого объекта.
Итак, что же такое скомпилированное регулярное выражение? Это
просто объект, созданный вызовом функции re.compile(), которой пе
редается шаблон. Этот объект, созданный за счет передачи шаблона
функции re.compile(), содержит множество методов для работы с регу
лярным выражением. Между скомпилированным и нескомпилирован
ным регулярными выражениями имеются два основных отличия. Во
первых, вместо ссылки на шаблон регулярного выражения "{{.*?}}"
создается объект скомпилированного выражения на основе шаблона.
Вовторых, вместо функции findall() из модуля re следует вызывать
метод findall() объекта скомпилированного выражения.

122 Глава 3. Текст
За дополнительной информацией о всех функциях, имеющихся в мо
дуле re, обращайтесь к разделу «Module Contents» в справочнике «Py
thon Library Reference», http://docs.python.org/lib/node46.html. За до
полнительной информацией об объектах скомпилированных регуляр
ных выражений обращайтесь к разделу «Regular Expression Objects»
в справочнике «Python Library Reference», http://docs.python.org/lib/
re>objects.html.
В примере 3.14 представлена реализация предыдущего примера с двой
ными фигурными скобками, выполненная на основе использования
объекта скомпилированного регулярного выражения.
Пример 3.14. Простое регулярное выражение, скомпилированный шаблон
In [1]: import re
In [2]: re_obj = re.compile("{{(.*?)}}")
In [3]: some_string = "this is a string with {{words}} embedded in\
...: {{curly brackets}} to show an {{example}} of {{regular expressions}}"
In [4]: for match in re_obj.findall(some_string):
...: print "MATCH >", match
...:
MATCH > words
MATCH > curly brackets
MATCH > example
MATCH > regular expressions
Выбор метода работы с регулярными выражениями в языке Python за
висит отчасти от личных предпочтений и от самого регулярного выра
жения. Следует заметить, что метод, основанный на использовании
функций модуля re, уступает в производительности методу, основанно
му на использовании объектов скомпилированных регулярных выра
жений. Проблема производительности особенно остро может вставать,
например, когда регулярное выражение применяется в цикле к каждой
строке текстового файла, содержащего сотни и тысячи строк. В приме
рах ниже представлены реализации простых сценариев, использующих
скомпилированые и нескомпилированные регулярные выражения,
которые применяются к файлу, содержащему 500 000 строк текста.
Если воспользоваться специальной функцией timeit, можно увидеть
разницу в производительности между этими двумя сценариями. Смот
рите пример 3.15.
Пример 3.15. Тест производительности нескомпилированного
регулярного выражения
#!/usr/bin/env python
import re
def run_re():
pattern = 'pDq'

Встроенные компоненты Python и модули 123
infile = open('large_re_file.txt', 'r')
match_count = 0
lines = 0
for line in infile:
match = re.search(pattern, line)
if match:
match_count += 1
lines += 1
return (lines, match_count)
if __name__ == "__main__":
lines, match_count = run_re()
print 'LINES::', lines
print 'MATCHES::', match_count
Функция timeit выполняет программный код несколько раз и возвра
щает время самого лучшего варианта. Ниже показаны результаты за
пуска утилиты timeit для этого сценария в оболочке IPython:
In [1]: import re_loop_nocompile
In [2]: timeit n 5 re_loop_nocompile.run_re()
5 loops, best of 3: 1.93 s per loop
В этом примере функция run_re() была вызвана 5 раз, и было вычисле
но среднее из 3 самых лучших показателей, которое составило 1,93 се
кунды. Специальная функция timeit выполняется с исследуемым про
граммным кодом несколько раз, чтобы уменьшить погрешность, вы
званную влиянием других процессов, исполняющихся в системе.
Ниже приводятся результаты измерения времени выполнения того же
самого программного кода с помощью утилиты time операционной сис
темы UNIX:
jmjones@dink:~/code$ time python re_loop_nocompile.py
LINES:: 500000 MATCHES:: 242
real 0m2.113s
user 0m1.888s
sys 0m0.163s
Пример 3.16 – это тот же пример с регулярным выражением за исклю
чением того, что мы используем re.compile() для создания объекта
скомпилированного шаблона.
Пример 3.16. Тест производительности скомпилированного
регулярного выражения
#!/usr/bin/env python
import re
def run_re():
pattern = 'pDq'
re_obj = re.compile(pattern)

124 Глава 3. Текст
infile = open('large_re_file.txt', 'r')
match_count = 0
lines = 0
for line in infile:
match = re_obj.search(line)
if match:
match_count += 1
lines += 1
return (lines, match_count)
if __name__ == "__main__":
lines, match_count = run_re()
print 'LINES::', lines
print 'MATCHES::', match_count
Испытания с помощью специальной функции timeit в оболочке IPy
thon дали следующие результаты:
In [3]: import re_loop_compile
In [4]: timeit n 5 re_loop_compile.run_re()
5 loops, best of 3: 860 ms per loop
А испытания того же самого сценария с помощью утилиты time опера
ционной системы UNIX дали следующие результаты:
jmjones@dink:~/code$ time python
re_loop_compile.py LINES:: 500000 MATCHES:: 242
real 0m0.996s
user 0m0.836s
sys 0m0.154s
Версия со скомпилированным регулярным выражением одержала
чистую победу. Время работы этой версии оказалось в два раза меньше
как по данным утилиты UNIX time, так и по данным функции timeit
оболочки IPython. Поэтому мы настоятельно рекомендуем взять в при
вычку использовать объекты скомпилированных регулярных выра
жений.
Как уже говорилось ранее в этой главе, для определения строк, в кото
рых не интерпретируются экранированные последовательности, мож
но использовать сырые (неформатированные) строки. В примере 3.17
показано применение неформатированных строк для использования
в регулярных выражениях.
Пример 3.17. Неформатированные строки и регулярные выражения
In [1]: import re
In [2]: raw_pattern = r'\b[a z]+\b'
In [3]: non_raw_pattern = '\b[a z]+\b'
In [4]: some_string = 'a few little words'

Встроенные компоненты Python и модули 125
In [5]: re.findall(raw_pattern, some_string)
Out[5]: ['a', 'few', 'little', 'words']
In [6]: re.findall(non_raw_pattern, some_string)
Out[6]: []
Шаблонный символ \b в регулярных выражениях соответствует гра
нице слова. То есть, как в случае применения сырой строки, так
и в случае применения обычной строки, мы предполагаем отыскать
отдельные слова, состоящие из символов нижнего регистра. Обратите
внимание, что при использовании raw_pattern были обнаружены от
дельные слова в some_string, а при использовании non_raw_pattern вооб
ще ничего не было найдено. В строке raw_pattern комбинация \b интер
претируется как два отдельных символа, в то время как в строке
non_raw_pattern она интерпретируется как символ забоя (backspace).
В результате функция findall() сумела отыскать отдельные слова с по
мощью неформатированной строки шаблона. Однако при использова
нии шаблона в виде обычной строки функция findall() не отыскала ни
одного символа забоя (backspace).
Чтобы с помощью шаблона non_raw_pattern можно было отыскать соот
ветствие в строке, необходимо окружить требуемое слово символами
\b, как показано ниже:
In [7]: some_other_string = 'a few \blittle\b words'
In [8]: re.findall(non_raw_pattern, some_other_string)
Out[8]: ['\x08little\x08']
Обратите внимание на шестнадцатеричную форму записи символа
"\x08" в соответствии, найденном функцией findall(). Эта шестнадца
теричная форма записи соответствует символам забоя (backspace), ко
торые были добавлены с помощью экранированной последовательно
сти \b.
Как видите, неформатированные строки могут пригодиться, когда
предполагается использовать специальные последовательности, такие
как "\b", обозначающую границу слова, "\d", обозначающую цифру,
или "\w", обозначающую алфавитноцифровой символ. Полный пере
чень специальных последовательностей, начинающихся с символа об
ратного слеша, вы найдете в разделе «Regular Expression Syntax»
в справочнике «Python Library Reference», http://docs.python.org/lib/
re>syntax.html.
Примеры с 3.14 по 3.17 были очень простыми. В них во всех использова
лись регулярные выражения и различные методы, применяемые к ним.
Иногда такого ограниченного использования регулярных выражений
вполне достаточно. Иногда бывает необходимо нечто более мощное,
чем имеется в библиотеке регулярных выражений.

126 Глава 3. Текст
К основным методам (или функциям) регулярных выражений, которые
используются наиболее часто, относятся findall(), finditer(), match()
иsearch(). Вам также могут потребоваться методы split() и sub(), но,
вероятно, не так часто, как другие методы.
Метод findall() отыскивает все вхождения указанного шаблона в стро
ке. Если метод findall() найдет соответствия шаблону, тип возвращае
мой структуры данных будет зависеть от наличия групп в шаблоне.
Краткое напоминание: группировка в регулярных выражениях
позволяет указывать текст внутри регулярного выражения, ко
торый следует извлечь из результата. За дополнительной ин
формацией обращайтесь к разделу «Common Metacharacters and
Fields» в книге Фридла (Friedl) «Mastering Regular Expres
sions»
1 или в Интернете по адресу: http://safari.oreilly.com/
0596528124/regex3>CHP>3>SECT>5?imagepage=137.
Если в регулярном выражении отсутствуют группы, а совпадение най
дено, тогда findall() вернет список строк. Например:
In [1]: import re
In [2]: re_obj = re.compile(r'\bt.*?e\b')
In [3]: re_obj.findall("time tame tune tint tire")
Out[3]: ['time', 'tame', 'tune', 'tint tire']
В этом шаблоне отсутствуют группы, поэтому findall() возвращает
список строк. Здесь можно наблюдать интересный побочный эффект –
последний элемент списка содержит два слова, tint и tire. Используе
мое здесь регулярное выражение соответствует словам, начинающимся
с символа «t» и заканчивающимся символом «e». Но часть выражения
.*? соответствует любым символам, включая пробелы. Метод findall()
отыскал все, что предполагалось. Он отыскал слово, начинающееся
с символа «t» (tint), и продолжил просмотр строки, пока не обнаружил
слово, завершающееся символом «e» (tire). Поэтому соответствие «tint
tire» вполне согласуется с шаблоном. Чтобы исключить пробел, можно
было бы использовать регулярное выражение r'\bt\w*e\b':
In [4]: re_obj = re.compile(r'\bt\w*e\b')
In [5]: re_obj.findall("time tame tune tint tire")
Out[5]: ['time', 'tame', 'tune', 'tire']
Второй тип структуры данных, который может быть получен, – это
список кортежей. Если группы присутствуют в выражении и было
найдено совпадение, то findall() вернет список кортежей. Подобный
шаблон и строка показаны в примере 3.18.
1 Джеффри Фридл «Регулярные выражения», 3е издание. – Пер. с англ. –
СПб.: Символплюс, 2008. Глава 3. Раздел «Стандартные метасимволы
и возможности». – Прим. перев.

Встроенные компоненты Python и модули 127
Пример 3.18. Простая группировка и метод findall()
In [1]: import re
In [2]: re_obj = re.compile(r"""(A\W+\b(big|small)\b\W+\b
...: (brown|purple)\b\W+\b(cow|dog)\b\W+\b(ran|jumped)\b\W+\b
...: (to|down)\b\W+\b(the)\b\W+\b(street|moon).*?\.)""",
...: re.VERBOSE)
In [3]: re_obj.findall('A big brown dog ran down the street. \
...: A small purple cow jumped to the moon.')
Out[3]:
[('A big brown dog ran down the street.',
'big',
'brown',
'dog',
'ran',
'down',
'the',
'street'),
('A small purple cow jumped to the moon.',
'small',
'purple',
'cow',
'jumped',
'to',
'the',
'moon')]
Несмотря на свою простоту, этот пример демонстрирует ряд важных
моментов. Вопервых, обратите внимание, что этот простой шаблон не
лепо длинен и содержит массу неалфавитноцифровых символов, от
которых начинает рябить в глазах, если смотреть слишком долго. Это
обычная вещь для многих регулярных выражений. Затем, обратите
внимание, что шаблон содержит явные вложенные группы. Объемлю
щая группа будет соответствовать любому тексту, начинающемуся
с символа «A» и заканчивающемуся точкой. Символы между началь
ным символом «A» и завершающей точкой образуют вложенные груп
пы, которые должны соответствовать словам «big» или «small»,
«brown» или «purple» и так далее. Далее, возвращаемое значение мето
да findall() представляет собой список кортежей. Элементами этих
кортежей являются группы, которые были определены в регулярном
выражении. Первый элемент кортежа – все предложение, потому что
оно соответствует наибольшей, объемлющей группе. Последующие
элементы кортежа соответствуют каждой из подгрупп. Наконец, обра
тите внимание на последний аргумент в вызове метода re.compile() –
re.VERBOSE. Это позволило нам записать регулярное выражение в много
строчном режиме, то есть мы смогли расположить регулярное выраже
ние в нескольких строках, не оказывая влияния на поиск соответст
вий. Пробел, оказавшийся за пределами группировки, был проигнори

128 Глава 3. Текст
рован. Хотя мы и не продемонстрировали здесь такую возможность,
тем не менее, многострочный режим позволяет вставлять коммента
рии в конец каждой строки регулярного выражения, чтобы описать,
что делает та или иная его часть. Одна из основных сложностей, свя
занных с регулярными выражениями, состоит в том, что описание
шаблона часто бывает очень длинным и трудным для чтения. Цель
re.VERBOSE состоит в том, чтобы упростить написание регулярных выра
жений, следовательно, это ценный инструмент, облегчающий сопрово
ждение программного кода, содержащего регулярные выражения.
Метод finditer() является разновидностью метода findall(). Вместо
того чтобы возвращать список кортежей, как это делает метод find
all(), finditer() возвращает итератор, как это следует из имени мето
да. Каждый элемент итератора – это объект найденного совпадения,
который мы обсудим далее в этой главе. Пример 3.19 реализует тот же
простой пример, только в нем вместо метода findall() используется
метод finditer().
Пример 3.19. Пример использования метода finditer()
In [4]: re_iter = re_obj.finditer('A big brown dog ran down the street. \
...: A small purple cow jumped to the moon.')
In [5]: re_iter
Out[5]:
In [6]: for item in re_iter:
...: print item
...: print item.groups()
...:
<_sre.SRE_Match object at 0x9ff858>
('A big brown dog ran down the street.', 'big', 'brown', 'dog', 'ran',
'down', 'the', 'street')
<_sre.SRE_Match object at 0x9ff940>
('A small purple cow jumped to the moon.', 'small', 'purple', 'cow',
'jumped', 'to', 'the', 'moon')
Если прежде вы никогда не сталкивались с итераторами, вы можете
представлять их себе как списки, которые создаются в тот момент, ко
гда они необходимы. Один из недостатков такого определения состоит
в том, что вы не можете обратиться к определенному элементу итера
тора по его индексу, как, например, к элементу списка some_list[3].
Вследствие этого ограничения вы не можете получить срез итератора,
как, например, в случае списка some_list[2:6]. Тем не менее, независи
мо от этих ограничений итераторы представляют собой легкое и мощ
ное средство, особенно когда необходимо выполнить итерации через
некоторую последовательность, потому что при этом последователь
ность не загружается целиком в память, а элементы ее возвращаются
по требованию. Это позволяет итераторам занимать меньший объем

Встроенные компоненты Python и модули 129
памяти, чем соответствующие им списки. Кроме того, доступ к эле
ментам последовательности производится быстрее.
Еще одно преимущество метода finditer() перед findall() состоит в том,
что каждый элемент, возвращаемый методом finditer(), – это объект
match, а не простой список строк или список кортежей, соответствую
щих найденному тексту.
Методы match() и search() обеспечивают похожие функциональные
возможности. Оба метода применяют регулярное выражение к строке;
оба указывают, с какой позиции начать и в какой закончить поиск по
шаблону; оба возвращают объект match для первого найденного соот
ветствия заданному шаблону. Разница между этими двумя методами
состоит в том, что метод match() пытается отыскать совпадение только
от начала строки или от указанного места в строке, не переходя в дру
гие позиции в строке, а метод search() будет пытаться отыскать соот
ветствие шаблону в любом месте строки или между начальной и конеч
ной позицией, которые вы укажете, как показано в примере 3.20.
Пример 3.20. Сравнение методов match() и search()
In [1]: import re
In [2]: re_obj = re.compile('FOO')
In [3]: search_string = ' FOO'
In [4]: re_obj.search(search_string)
Out[4]: <_sre.SRE_Match object at 0xa22f38>
In [5]: re_obj.match(search_string)
In [6]:
Даже при том, что в строке search_string имеется соответствие шабло
ну, поиск по которому производит метод match(), тем не менее, поиск
завершается неудачей, потому что подстрока в search_string, соответ
ствующая шаблону, находится не в начале строки. Метод search(), на
против, нашел соответствие и вернул объект match.
Методы search() и match() принимают параметры, определяющие на
чальную и конечную позицию поиска в строке, как показано в приме
ре 3.21.
Пример 3.21. Параметры начала и конца поиска в методах search()
и match()
In [6]: re_obj.search(search_string, pos=1)
Out[6]: <_sre.SRE_Match object at 0xabe030>
In [7]: re_obj.match(search_string, pos=1)
Out[7]: <_sre.SRE_Match object at 0xabe098>

130 Глава 3. Текст
In [8]: re_obj.search(search_string, pos=1, endpos=3)
In [9]: re_obj.match(search_string, pos=1, endpos=3)
In [10]:
Параметр pos – это индекс, определяющий место в строке, откуда дол
жен начинаться поиск по шаблону. В данном примере передача пара
метра pos методу search() не повлияла на результат, но передача пара
метра pos методу match()привела к тому, что он нашел соответствие
шаблону, хотя без параметра pos соответствие обнаружить не удалось.
Установка параметра endpos в значение 3 привела к тому, что оба
метода – и match(), и search() не нашли соответствие, потому что соот
ветствие шаблону включает символ в третьей позиции.
Методы findall() и finditer() отвечают на вопрос: «чему соответствует
мой шаблон?», а главный вопрос, на который отвечают методы search()
и match(): «имеется ли соответствие моему шаблону?». Методы search()
и match() отвечают также на вопрос: «каково первое соответствие мо
ему шаблону?», но часто единственное, что требуется узнать, это:
«имеется ли соответствие моему шаблону?». Например, предположим,
что необходимо написать сценарий, который должен читать строки из
файла журнала и обертывать каждую строку в теги HTML, чтобы обес
печить удобочитаемое отображение. При этом хотелось бы, чтобы все
строки, содержащие текст «ERROR», отображались красным цветом,
для чего можно было бы выполнить цикл по всем строкам в файле, про
верить их с помощью регулярного выражения и, если метод search()
обнаруживает текст «ERROR», можно было бы определить такой фор
мат строки, чтобы она отображалась красным цветом.
Методы search() и match() удобны не только тем, что они определяют
наличие соответствия, но и тем, что они возвращают объект match. Объ
екты match содержат различные методы извлечения данных, которые
могут пригодиться при обходе полученных результатов. Особый инте
рес представляют такие методы объекта match, как start(), end(),
span(), groups() и groupdict().
Методы start(), end() и span() определяют позиции в строке поиска,
где совпадение с шаблоном начинается и где заканчивается. Метод
start() возвращает целое число, определяющее позицию в строке на
чала найденного соответствия. Метод end() возвращает целое число,
определяющее позицию в строке конца найденного соответствия.
Аметод span() возвращает кортеж, содержащий позицию начала и кон
ца совпадения.
Метод groups() возвращает кортеж совпадения, каждый элемент кото
рого соответствует группе, имеющейся в шаблоне. Этот кортеж напо
минает кортежи в списке, возвращаемом методом findall()
. Метод
groupdict() возвращает словарь именованных групп, ключи которого
соответствуют именам групп, присутствующих непосредственно в ре
гулярном выражении, например: (?Ppattern).

Встроенные компоненты Python и модули 131
Подводя итоги, можно сказать – чтобы эффективно использовать регу
лярные выражения, следует взять в привычку использовать объекты
скомпилированных регулярных выражений. Используйте методы
findall() и finditer(), когда необходимо получить части текста, соот
ветствующие шаблону. Запомните, что метод finditer() обладает более
высокой гибкостью, чем findall(), потому что возвращает итератор по
объектам match. Более подробный обзор библиотеки регулярных выра
жений вы найдете в главе 9 книги «Python in a Nutshell» Алекса Мар
телли (Alex Martelli) (O’Reilly). Чтобы познакомиться с регулярными
выражениями в действии, обращайтесь к книге «Data Crunching» Гре
га Уилсона (Greg Wilson) (The Pragmatic Bookshelf).
Работа с конфигурационным файлом Apache
Теперь, когда вы получили представление о работе с регулярными вы
ражениями в языке Python, попробуем поработать с конфигурацион
ным файлом вебсервера Apache:
NameVirtualHost 127.0.0.1:80

DocumentRoot /var/www/

Options FollowSymLinks
AllowOverride None

ErrorLog /var/log/apache2/error.log
LogLevel warn
CustomLog /var/log/apache2/access.log combined
ServerSignature On


DocumentRoot /var/www2/

Options FollowSymLinks
AllowOverride None

ErrorLog /var/log/apache2/error2.log
LogLevel warn
CustomLog /var/log/apache2/access2.log combined
ServerSignature On

Это слегка измененный конфигурационный файл Apache в Ubuntu.
Мы создали именованные виртуальные хосты для некоторых своих
нужд. Мы также добавили в файл /etc/hosts следующую строку:
127.0.0.1 local2
Она позволяет указать броузеру, что серверу с именем local2 соответст
вует IPадрес 127.0.01, то есть локальный компьютер. И в чем же здесь
смысл? Если в броузере ввести адрес http://local2, он передаст серверу

132 Глава 3. Текст
указанное имя в заголовке HTTP. Ниже приводится HTTPзапрос, на
правленный серверу local2:
GET / HTTP/1.1
Host: local2
User Agent: Mozilla/5.0 (X11; U; Linux x86_64; en US; rv:1.8.1.13)
Gecko/20080325 Ubuntu/7.10 (gutsy) Firefox/2.0.0.13
Accept: text/xml,application/xml,application/xhtml+xml,text/html
Accept Language: en us,en;q=0.5
Accept Encoding: gzip,deflate
Accept Charset: ISO 8859 1,utf 8;q=0.7,*;q=0.7
Keep Alive: 300
Connection: keep alive
If Modified Since: Tue, 15 Apr 2008 17:25:24 GMT
If None Match: "ac5ea 53 44aecaf804900"
Cache Control: max age=0
Обратите внимание, что запрос начинается с заголовка Host:. Когда
вебсервер Apache получит такой запрос, он направит его виртуально
му хосту с именем local2.
Теперь все, что нам предстоит сделать, – это написать сценарий, кото
рый анализирует конфигурационный файл вебсервера Apache, такой,
как показано выше, отыскивает раздел VirtualHost и замещает значе
ние параметра DocumentRoot в этом разделе. Сам сценарий приводится
ниже:
#!/usr/bin/env python
from cStringIO import StringIO
import re
vhost_start = re.compile(r'')
vhost_end = re.compile(r' docroot_re = re.compile(r'(DocumentRoot\s+)(\S+)')
def replace_docroot(conf_string, vhost, new_docroot):
'''отыскивает в файле httpd.conf строки DocumentRoot, соответствующие
указанному vhost, и замещает их новыми строками new_docroot
'''
conf_file = StringIO(conf_string)
in_vhost = False
curr_vhost = None
for line in conf_file:
vhost_start_match = vhost_start.search(line)
if vhost_start_match:
curr_vhost = vhost_start_match.groups()[0]
in_vhost = True
if in_vhost and (curr_vhost == vhost):
docroot_match = docroot_re.search(line)
if docroot_match:
sub_line = docroot_re.sub(r'\1%s' % new_docroot, line)
line = sub_line

Встроенные компоненты Python и модули 133
vhost_end_match = vhost_end.search(line)
if vhost_end_match:
in_vhost = False
yield line
if __name__ == '__main__':
import sys
conf_file = sys.argv[1]
vhost = sys.argv[2]
docroot = sys.argv[3]
conf_string = open(conf_file).read()
for line in replace_docroot(conf_string, vhost, docroot):
print line,
Этот сценарий сначала создает три объекта скомпилированных регу
лярных выражений: один соответствует открывающему тегу Virtual
Host, один – закрывающему тегу VirtualHost и один – строке с парамет
ром DocumentRoot. Мы также создали функцию, которая выполняет эту
утомительную работу. Функция называется replace_docroot и принима
ет в качестве аргументов тело конфигурационного файла в виде строки,
имя раздела VirtualHost, который требуется отыскать, и значение пара
метра DocumentRoot, которое требуется назначить для данного виртуаль
ного хоста. Функция устанавливает признак состояния, который ука
зывает, находится ли текущая анализируемая строка в разделе Virtu
alHost. Кроме того, сохраняется имя текущего виртуального хоста. При
анализе строк в разделе VirtualHost эта функция пытается отыскать
строку с параметром DocumentRoot и изменяет его значение. Поскольку
функция replace_docroot() выполняет итерации по каждой строке в кон
фигурационном файле, она возвращает либо неизмененную исходную
строку, либо измененную строку с параметром DocumentRoot.
Мы создали простой интерфейс командной строки к этой функции.
В нем не предусматривается использование ничего особенного, такого
как функция optparse, и не выполняется проверка на количество вход
ных аргументов, но он работает. Теперь попробуем применить этот
сценарий к конфигурационному файлу вебсервера Apache, представ
ленному выше, и изменим настройки VirtualHost local2:80 так, чтобы
он использовал каталог /tmp в качестве корневого каталога докумен
тов. Предусмотренный нами интерфейс командной строки просто вы
водит строки, возвращаемые функцией replace_docroot(), а не изменя
ет сам файл:
jmjones@dinkgutsy:code$ python apache_conf_docroot_replace.py
/etc/apache2/sites available/psa
local2:80 /tmp
NameVirtualHost 127.0.0.1:80

DocumentRoot /var/www/

Options FollowSymLinks

134 Глава 3. Текст
AllowOverride None

ErrorLog /var/log/apache2/error.log
LogLevel warn
CustomLog /var/log/apache2/access.log combined
ServerSignature On


DocumentRoot /tmp

Options FollowSymLinks
AllowOverride None

ErrorLog /var/log/apache2/error2.log
LogLevel warn
CustomLog /var/log/apache2/access2.log combined
ServerSignature On

Единственная строка, которая изменилась, – это строка с параметром
DocumentRoot в разделе VirtualHost local2:80. Ниже приводятся разли
чия, полученные после того, как вывод сценария был перенаправлен
вфайл:
jmjones@dinkgutsy:code$ diff apache_conf.diff /etc/apache2/sites available/psa
20c20
< DocumentRoot /tmp

> DocumentRoot /var/www2/
Изменение значения параметра DocumentRoot в конфигурационном фай
ле вебсервера Apache – это достаточно простая задача, но когда это
приходится делать достаточно часто или когда имеется множество вир
туальных хостов, которые приходится изменять, тогда есть смысл на
писать сценарий, подобный тому, что был показан выше. Не менее про
сто можно было бы изменить сценарий так, чтобы он комментировал
требуемый раздел VirtualHost, изменял значение параметра LogLevel
или изменял имя файла журнала для указанного виртуального хоста.
Работа с файлами
Овладение приемами работы с файлами является ключом к обработке
текстовых данных. Зачастую текст, который требуется обработать, на
ходится в текстовом файле, например, в файле журнала, в конфигура
ционном файле или в файле с данными приложения. Нередко резуль
таты анализа данных требуется сохранить в виде файла отчета или
просто записать их в текстовый файл для последующего изучения.
К счастью, в языке Python имеется простой в использовании тип объ
ектов с именем file, который в состоянии помочь выполнить все необ
ходимые действия с файлами.

Встроенные компоненты Python и модули 135
Создание файлов
Это может показаться странным, но чтобы прочитать содержимое су
ществующего файла, необходимо создать новый объект типа file. Од
нако не надо путать операцию создания нового объекта с созданием но
вого файла. Чтобы выполнить операцию записи в файл, необходимо
создать новый объект file и, возможно, создать новый файл на диске,
поэтому в такой ситуации создание объекта file интуитивно более по
нятно, чем создание объекта file для чтения. Создавать объект file
обязательно, потому что он необходим для организации взаимодейст
вий с файлом на диске.
Для создания объекта file используется встроенная функция open().
Ниже приводится фрагмент программного кода, который открывает
файл для чтения:
In [1]: infile = open("foo.txt", "r")
In [2]: print infile.read()
Some Random
Lines
Of
Text.
Функция open() – это встроенная функция, поэтому нет никакой необ
ходимости импортировать какойлибо модуль. Функция open() прини
мает три аргумента: имя файла, режим открытия и размер буфера.
Обязательным является только первый аргумент – имя файла. Наибо
лее часто используются режимы: «r» (режим чтения, используется по
умолчанию), «w» (режим записи) и «a» (режим записи в конец файла).
Вместе с этими тремя спецификаторами режимов может использо
ваться дополнительный спецификатор «b», определяющий двоичный
режим доступа. Третий аргумент, размер буфера, определяет способ
буферизации операций над файлом.
В предыдущем примере было предписано открыть файл foo.txt в режи
ме для чтения и сохранить ссылку на созданный объект файла в пере
менной infile. После получения ссылки на объект в переменной infile
появилась возможность обратиться к методу read() этого объекта, ко
торый читает содержимое файла целиком.
Создание объекта типа file для записи в файл выполняется почти так
же, как создание объекта для чтения из файла. Просто вместо специ
фикатора режима "r" следует использовать спецификатор "w":
In [1]: outputfile = open("foo_out.txt", "w")
In [2]: outputfile.write("This is\nSome\nRandom\nOutput Text\n")
In [3]: outputfile.close()
В этом примере предписывается открыть файл foo_out.txt в режиме
для записи и сохранить ссылку на вновь созданный объект типа file

136 Глава 3. Текст
в переменной outputfile. После получения ссылки на объект мы смог
ли обратиться к методу write(), чтобы записать в файл некоторый
текст и закрыть его вызовом метода close().
Несмотря на всю простоту создания файлов, у вас может появиться
желание создавать файлы способом, более устойчивым к появлению
ошибок. Считается хорошей практикой обертывать вызов функции
open() конструкцией try/finally, особенно, когда вслед за этим вызы
вается метод write(). Ниже приводится пример реализации записи
в файл с использованием инструкции try/finally:
In [1]: try:
...: f = open('writeable.txt', 'w')
...: f.write('quick line here\n')
...: finally:
...: f.close()
При такой реализации записи файлов метод close() вызывается, когда
гденибудь в блоке try возникает исключение. В действительности
этот подход позволяет методу close() закрыть файл, даже когда в бло
ке try не возникает исключение. Блок finally выполняется после за
вершения работы блока try всегда, независимо от того, возникло ис
ключение или нет.
В версии Python 2.5 появилась новая идиома – инструкция with, ко
торая позволяет использовать менеджер контекста. Менеджер
контекста – это просто объект с методами __enter__() и __exit__(). Ко
гда объект создается с помощью инструкции with, вызывается метод
__enter__() менеджера контекста. Когда выполнение блока with завер
шается, вызывается метод __exit__() менеджера контекста, даже если
возникло исключение. Объекты типа file имеют методы __enter__() и
__exit__(). В методе __exit__() объекта типа file вызывается метод
close(). Ниже приводится пример использования инструкции with:
In [1]: from __future__ import with_statement
In [2]: with open('writeable.txt', 'w') as f:
...: f.write('this is a writeable file\n')
...:
...:
Хотя в этом фрагменте отсутствует вызов метода close() объекта f, ме
неджер контекста закроет файл после выхода из блока with:
In [3]: f
Out[3]:
In [4]: f.write("this won't work")

ValueError Traceback (most recent call last)
/Users/jmjones/ in ()

Встроенные компоненты Python и модули 137
ValueError: I/O operation on closed file
(ValueError: операция ввода вывода с закрытым файлом)
Как и следовало ожидать, файл был закрыт. Хотя это хорошая прак
тика – обрабатывать все возможные исключения и гарантировать за
крытие файла, когда это необходимо, но ради простоты и ясности мы
не будем предусматривать такую обработку во всех примерах.
Полный перечень методов объектов типа file вы найдете в разделе «File
Objects» в справочнике «Python Library Reference» по адресу: http://
docs.python.org/lib/bltin>file>objects.html.
Чтение из файлов
Как только появляется объект файла, открытого для чтения с флагом r,
вы получаете возможность использовать три обычных метода объекта
file, удобных для получения данных, содержащихся в файле: read(),
readline() и readlines(). Метод read() читает, что не удивительно, дан
ные из объекта открытого файла и возвращает эти данные в виде стро
ки. Метод read() принимает необязательный аргумент, который ука
зывает число байтов, которые требуется прочитать из файла. Если этот
аргумент отсутствует, метод read() попытается прочитать содержимое
файла целиком. Если размер файла меньше, чем величина аргумента,
метод read() будет читать данные, пока не встретит конец файла и вер
нет то, что удалось прочитать.
Допустим, что имеется следующий файл:
jmjones@dink:~/some_random_directory$ cat foo.txt Some Random
Lines
Of
Text.
Тогда метод read() будет работать с этим файлом, как показано ниже:
In [1]: f = open("foo.txt", "r")
In [2]: f.read()
Out[2]: 'Some Random\n Lines\nOf \n Text.\n'
Обратите внимание, что символы новой строки отображаются как по
следовательности \n – это стандартный способ обозначения символа
новой строки.
Если бы требовалось прочитать только первые 5 байтов, сделать это
можно было бы следующим способом:
In [1]: f = open("foo.txt", "r")
In [2]: f.read(5)
Out[2]: 'Some '
Следующий метод, позволяющий получать текст из файла, – метод
readline(). Метод readline() читает текст из файла по одной строке за

138 Глава 3. Текст
раз. Этот метод принимает один необязательный аргумент: size. Он
определяет максимальное число байтов, которые метод readline() бу
дет читать из файла, прежде чем вернуть строку, независимо от того,
был достигнут конец строки или нет. Поэтому в следующем примере
программа читает первую строку из текста из файла foo.txt, затем чи
тает первые 7 байтов текста из второй строки, а после этого считывает
остаток второй строки:
In [1]: f = open("foo.txt", "r")
In [2]: f.readline()
Out[2]: 'Some Random\n'
In [3]: f.readline(7)
Out[3]: ' Lin'
In [4]: f.readline()
Out[4]: 'es\n'
Последний метод получения текста из объектов типа file, который мы
рассмотрим, – это метод readlines(). Имя readlines() – это не опечатка
и не ошибка, закравшаяся при копировании имени метода из преды
дущего примера. Метод readlines() читает сразу все строки из файла.
Впрочем, это почти правда. Метод readlines() имеет аргумент sizehint,
определяющий максимальное число байтов, которые требуется прочи
тать. В следующем примере мы создали файл biglines.txt, содержащий
10 000 строк, в каждой из которых по 80 символов. После этого мы от
крыли файл, указали, что нам требуется прочитать из файла N первых
строк, общий объем которых составляет примерно 1024 байта, опреде
лили число прочитанных строк и байтов и затем прочитали оставшую
ся часть файла:
In [1]: f = open("biglines.txt", "r")
In [2]: lines = f.readlines(1024)
In [3]: len(lines)
Out[3]: 102
In [4]: len("".join(lines))
Out[4]: 8262
In [5]: lines = f.readlines()
In [6]: len(lines)
Out[6]: 9898
In [7]: len("".join(lines))
Out[7]: 801738
Команда в строке [3] показывает, что было прочитано 102 строки, а ко
манда в строке [4] показала, что общее число прочитанных байтов со
ставило 8262. Как так вышло, что мы указали «примерное» число бай
тов, которые требуется прочитать, равное 1024, а получили 8262? Оно

Встроенные компоненты Python и модули 139
было округлено до размера внутреннего буфера, который равен при
мерно 8 килобайтам. Суть в том, что аргумент sizehint не всегда оказы
вает влияние так, как вам того хотелось бы, и об этом следует помнить.
Запись в файлы
Иногда возникает потребность не только читать данные из файлов, но
также создавать собственные файлы и записывать в них данные. Объ
екты типа file обладают двумя основными методами, которые позво
лят вам записывать данные в файлы. Первый метод, который уже де
монстрировался выше, – это метод write(). Метод write() принимает
один аргумент: строку, которую требуется записать в файл. В следую
щем примере демонстрируется запись данных в файл:
In [1]: f = open("some_writable_file.txt", "w")
In [2]: f.write("Test\nFile\n")
In [3]: f.close()
In [4]: g = open("some_writable_file.txt", "r")
In [5]: g.read()
Out[5]: 'Test\nFile\n'
Команда [1] открывает файл с флагом режима w, то есть в режиме для
записи. Команда [2] записывает в файл две строки. Команда [4] создает
новый объект файла и присваивает ссылку на него другой переменной,
с именем g, чтобы избежать путаницы, хотя вполне возможно было ис
пользовать и переменную f. И команда [5] показывает, что метод read()
возвращает те же самые данные, которые были записаны в файл.
Следующий основной метод записи данных в файл – это метод write
lines(). Метод writelines() принимает один обязательный параметр:
последовательность, которая должна быть записана в файл. Допуска
ется использовать последовательности любого итерируемого типа, та
кие как списки, кортежи, генераторы списков (которые можно счи
тать списками) или генераторы. Ниже приводится пример вызова ме
тода writelines(), который получает данные для записи в файл от вы
ражениягенератора:
In [1]: f = open("writelines_outfile.txt", "w")
In [2]: f.writelines("%s\n" % i for i in range(10))
In [3]: f.close()
In [4]: g = open("writelines_outfile.txt", "r")
In [5]: g.read()
Out[5]: '0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n'
И еще один пример функциигенератора, которая может использо
ваться для записи данных в файл (этот пример функционально эквива

140 Глава 3. Текст
лентен предыдущему, но для его реализации потребовалось написать
больше программного кода):
In [1]: def myRange(r):
...: i = 0
...: while i < r:
...: yield "%s\n" % i
...: i += 1
...:
...:
In [2]: f = open("writelines_generator_function_outfile", "w")
In [3]: f.writelines(myRange(10))
In [4]: f.close()
In [5]: g = open("writelines_generator_function_outfile", "r")
In [6]: g.read()
Out[6]: '0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n'
Следует заметить, что метод writelines() не записывает символы новой
строки (\n) автоматически – вы сами должны включать их в последо
вательность, предназначенную для записи в файл. Кроме того, следует
знать, что этот метод можно использовать не только для построчной
записи данных в файл. Возможно, этому методу лучше подошло бы на
звание writeiter().Так случилось, что в предыдущих примерах мы за
писывали текст, который уже содержал символы новой строки, но нет
никаких причин, которые требовали бы их наличия.
Дополнительные ресурсы
За дополнительной информацией об объектах типа file обращайтесь
к главе 7 в книге «Learning Python» Дэвида Ашера (David Ascher)
и Марка Лутца (Mark Lutz) (O’Reilly) (имеется также в Интернете по
адресу: http://safari.oreilly.com/0596002815/lpython2>chp>7>sect>2) или
к разделу «File Objects» в справочнике «Python Library Reference»
(доступному в Интернете по адресу: http://docs.python.org/lib/bltin>fi>
le>objects.html).
За дополнительной информацией о выраженияхгенераторах обра
щайтесь к разделу «generator expressions» в справочнике «Python
Language Reference» (доступному в Интернете по адресу: http://
docs.python.org/ref/genexpr.html). За дополнительной информацией об
инструкции yield обращайтесь к разделу «yield statement» в справоч
нике «Python Language Reference» (доступному в Интернете по адре
су: http://docs.python.org/ref/yield.html).
Стандартный ввод и вывод
Операции чтения текста из потока стандартного ввода процесса и за
писи в поток стандартного вывода процесса знакомы большинству сис

Встроенные компоненты Python и модули 141
темных администраторов. Стандартный ввод – это обычные данные,
поступающие в программу, которые программа может читать в ходе
своей работы. Стандартный вывод – это данные, которые программа
выводит в процессе выполнения. Преимущество использования стан
дартного ввода и вывода состоит в том, что это позволяет объединять
команды в конвейеры с другими утилитами.
Стандартная библиотека языка Python содержит встроенный модуль
с именем sys, который обеспечивает простые способы доступа к стан
дартному вводу и стандартному выводу. Стандартная библиотека пре
доставляет доступ к стандартному вводу и выводу как к объектам типа
file, несмотря на то, что они не имеют никакого отношения к файлам
на диске. А так как эти объекты напоминают объекты типа file, для
работы с ними можно использовать те же самые методы, которые ис
пользуются при работе с файлами. Вы можете работать с ними, как ес
ли бы это были файлы на диске, и обращаться к соответствующим ме
тодам для выполнения требуемых операций.
После импортирования модуля sys стандартный ввод становится дос
тупен в виде атрибута stdin этого модуля (sys.stdin). Атрибут sys.stdin –
это доступный для чтения объект типа file. Обратите внимание, что
произойдет, если создать «настоящий» объект типа file, открыв файл
с именем foo.txt на диске, и затем сравнить его с объектом sys.stdin:
In [1]: import sys
In [2]: f = open("foo.txt", "r")
In [3]: sys.stdin
Out[3]: ', mode 'r' at 0x14020>
In [4]: f
Out[4]:
In [5]: type(sys.stdin) == type(f)
Out[5]: True
Интерпретатор воспринимает их как объекты одного и того же типа,
поэтому они обладают одними и теми же методами. Несмотря на то,
что с технической точки зрения эти объекты принадлежат одному
и тому же типу и обладают одними и теми же методами, поведение не
которые методов будет отличаться. Например, методы sys.stdin.seek()
и sys.stdin.tell() доступны, но при обращении к ним возбуждается ис
ключение (в данном случае исключение IOError). Однако во всем ос
тальном стандартный ввод и вывод напоминают объекты типа file,
и вы в значительной степени можете воспринимать их как обычные
дисковые файлы.
Доступ к sys.stdin в оболочке Python (или в оболочке IPython) прак
тически лишен всякого смысла. Попытка импортировать модуль sys
ивызвать метод sys.stdin.read() просто заблокирует работу оболочки.
Чтобы продемонстрировать вам, как работает объект sys.stdin, мы

142 Глава 3. Текст
написали сценарий, который читает данные из sys.stdin и выводит об
ратно каждую прочитанную строку с соответствующим ей номером,
как показано в примере 3.22.
Пример 3.22. Нумерация строк, читаемых методом sys.stdin.readline
#!/usr/bin/env python
import sys
counter = 1
while True:
line = sys.stdin.readline()
if not line:
break
print "%s: %s" % (counter, line)
counter += 1
В этом примере мы создали переменную counter, с помощью которой
сценарий следит за номерами введенных строк. Далее следует цикл
while, в теле которого выполняется чтение строк со стандартного вво
да. Для каждой строки вводится ее порядковый номер и ее содержи
мое. Так как программа все время находится в процессе выполнения
цикла, она обрабатывает все строки, которые ей поступают, даже если
они оказываются пустыми. Но даже пустые строки – не совсем пустые:
они содержат символ новой строки (\n). Когда сценарий обнаруживает
признак «конца файла», он прерывает работу цикла.
Ниже приводится результат объединения в конвейер команды who и пре
дыдущего сценария:
jmjones@dink:~/psabook/code$ who | ./sys_stdin_readline.py
1: jmjones console Jul 9 11:01
2: jmjones ttyp1 Jul 9 19:58
3: jmjones ttyp2 Jul 10 05:10
4: jmjones ttyp3 Jul 11 11:51
5: jmjones ttyp4 Jul 13 06:48
6: jmjones ttyp5 Jul 11 21:49
7: jmjones ttyp6 Jul 15 04:38
Достаточно интересно, что предыдущий пример можно реализовать
гораздо проще и короче, если использовать функцию enumerate(), как
показано в примере 3.23.
Пример 3.23. Пример использования метода sys.stdin.readline()
#!/usr/bin/env python
import sys
for i, line in enumerate(sys.stdin):
print "%s: %s" % (i, line)

Встроенные компоненты Python и модули 143
Чтобы получить доступ к стандартному вводу, необходимо импортиро
вать модуль sys и затем воспользоваться атрибутом stdin. Точно так
же, чтобы получить доступ к стандартному выводу, необходимо им
портировать модуль sys и воспользоваться атрибутом stdout. Так же,
как sys.stdin представляет объект файла, доступного для чтения, объ
ект sys.stdout представляет объект файла, доступного для записи.
И так же, как sys.stdin имеет тот же тип, что и объект файла, доступ
ного для чтения, объект sys.stdout имеет тот же тип, что и объект фай
ла, доступного для записи:
In [1]: import sys
In [2]: f = open('foo.txt', 'w')
In [3]: sys.stdout
Out[3]: ', mode 'w' at 0x14068>
In [4]: f
Out[4]:
In [5]: type(sys.stdout) == type(f)
Out[5]: True
Важное замечание: следующее утверждение не должно быть неожи
данным, поскольку любой файл, открытый для чтения, и любой файл,
открытый для записи, относятся к одному и тому же типу:
In [1]: readable_file = open('foo.txt', 'r')
In [2]: writable_file = open('foo_writable.txt', 'w')
In [3]: readable_file
Out[3]:
In [4]: writable_file
Out[4]:
In [5]: type(readable_file) == type(writable_file)
Out[5]: True
Важно знать, что sys.stdout может в значительной степени рассматри
ваться как объект типа file, открытый для записи, точно так же как
иsys.stdin может рассматриваться как объект типа file, открытый
для чтения.
StringIO
Как быть в случае, когда функция, выполняющая обработку текста,
предназначена для работы с объектом типа file, а данные, которые
предстоит обрабатывать, доступны в виде текстовой строки, а не в виде
объекта file? Самое простое решение состоит в том, чтобы воспользо
ваться модулем StringIO:
In [1]: from StringIO import StringIO
In [2]: file_like_string = StringIO("This is a\nmultiline string.\n

144 Глава 3. Текст
readline() should see\nmultiple lines of\ninput")
In [3]: file_like_string.readline()
Out[3]: 'This is a\n'
In [4]: file_like_string.readline()
Out[4]: 'multiline string.\n'
In [5]: file_like_string.readline()
Out[5]: 'readline() should see\n'
In [6]: file_like_string.readline()
Out[6]: 'multiple lines of\n'
In [7]: file_like_string.readline()
Out[7]: 'input'
В этом примере мы создали объект StringIO, конструктору которого пе
редается строка "This is a\nmultiline string. \nreadline() should see\nmul
tiple lines of\ninput". После этого появилась возможность вызывать
метод readline() объекта StringIO. Хотя в этом примере использовался
только метод readline(), это далеко не единственный доступный метод,
заимствованный у объектов типа file:
In [8]: dir(file_like_string)
Out[8]:
['__doc__',
'__init__',
'__iter__',
'__module__',
'buf',
'buflist',
'close',
'closed',
'flush',
'getvalue',
'isatty',
'len',
'next',
'pos',
'read',
'readline',
'readlines',
'seek',
'softspace',
'tell',
'truncate',
'write',
'writelines']
Конечно, между файлами и строками существуют различия, но ин
терфейс позволяет легко переходить между использованием файлов
и строк. Ниже приводится сравнение методов и атрибутов объектов
типа file и объектов типа StringIO:

Встроенные компоненты Python и модули 145
In [9]: f = open("foo.txt", "r")
In [10]: from sets import Set
In [11]: sio_set = Set(dir(file_like_string))
In [12]: file_set = Set(dir(f))
In [13]: sio_set.difference(file_set)
Out[13]: Set(['__module__', 'buflist', 'pos', 'len', 'getvalue', 'buf'])
In [14]: file_set.difference(sio_set)
Out[14]: Set(['fileno', '__setattr__', '__reduce_ex__', '__new__',
'encoding',
'__getattribute__', '__str__', '__reduce__', '__class__', 'name',
'__delattr__', 'mode', '__repr__', 'xreadlines', '__hash__', 'readinto',
'newlines'])
Как видите, если возникнет необходимость работать со строкой как
с файлом, объект типа StringIO может оказать существенную помощь.
urllib
Что, если интересующий вас файл находится гдето в сети? Или вы хо
тите использовать уже существующий фрагмент программного кода,
который предполагает работу с объектом file? Встроенный тип file не
умеет взаимодействовать с сетью, и в этой ситуации вам поможет мо
дуль urllib.
Если вам требуется только вызвать метод read() для получения дан
ных файла, расположенного на некотором вебсервере, для этого мож
но просто воспользоваться методом urllib.urlopen(), как показано
в простом примере ниже:
In [1]: import urllib
In [2]: url_file = urllib.urlopen("http://docs.python.org/lib/module
urllib.html")
In [3]: urllib_docs = url_file.read()
In [4]: url_file.close()
In [5]: len(urllib_docs)
Out[5]: 28486
In [6]: urllib_docs[:80]
Out[6]: '\
n\n\n In [7]: urllib_docs[ 80:]
Out[7]: 'nt... for information on suggesting changes.\
n\n\n\n'
Здесь сначала импортируется модуль urllib. Затем создается fileпо
добный объект с именем url_file. Далее выполняется чтение содержи
мого url_file в строку с именем urllib_docs. И только для того, чтобы

146 Глава 3. Текст
продемонстрировать, что данные действительно были получены из
Интернета, с помощью операции извлечения среза выводятся первые
и последние 80 символов полученного документа. Обратите внимание,
что объекты файлов, созданные средствами urllib, поддерживают ме
тоды read() и close(). Кроме того, они поддерживают методы read
line(), readlines(), fileno(), info() и geturl().
Если вам потребуются более широкие возможности, такие как работа
через проксисервер, ищите дополнительную информацию о модуле
urllib по адресу: http://docs.python.org/lib/module>urllib.html. Если
вам требуются еще более широкие возможности, такие как аутентифи
кация и работа с cookie, подумайте об использовании модуля urllib2,
описание которого вы найдете по адресу: http://docs.python.org/lib/
module>urllib2.html.
Анализ журналов
С точки зрения системного администратора никакое обсуждение во
просов обработки текста не может считаться законченным без обсуж
дения проблемы анализа файлов журналов, поэтому здесь мы рассмот
рим эту проблему. Мы заложили основу знаний, которые позволят вам
открыть файл журнала, прочитать его построчно и при этом извлекать
данные тем способом, который вы сочтете наиболее подходящим. Пре
жде чем приступить к реализации очередного примера, нам необходи
мо ответить на вопрос: «Что нам необходимо получить в результате
чтения файла журнала?». Ответ достаточно прост: прочитать журнал
обращений к вебсерверу Apache и определить количество байтов, по
лученных каждым отдельным клиентом.
Согласно описанию http://httpd.apache.org/docs/1.3/logs.html «ком
бинированный» формат записи в файле журнала имеет следующий
вид:
127.0.0.1 frank [10/Oct/2000:13:55:36 0700] "GET /apache_pb.gif HTTP/1.0"
200 2326 "http://www.example.com/start.html" "Mozilla/4.08 [en] (Win98; I
;Nav)"
И это соответствует данным в нашем файле журнала вебсервера Apa
che. В каждой строке журнала интерес для нас будут представлять две
вещи: IPадрес клиента и число переданных байтов. IPадрес клиента
находится в первом поле записи, в данном случае – это адрес 127.0.0.1.
Количество переданных байтов содержится в третьем поле с конца,
в данном случае было передано 2326 байтов. Как же нам получить эти
поля? Взгляните на пример 3.24.
Пример 3.24. Сценарий анализа файла журнала веб>сервера Apache –
разбиение по пробелам
#!/usr/bin/env python
"""

Анализ журналов 147
ПОРЯДОК ИСПОЛЬЗОВАНИЯ:
apache_log_parser_split.py some_log_file
Этот сценарий принимает единственный аргумент командной строки: имя файла
журнала, который требуется проанализировать. Он анализирует содержимое файла
и генерирует отчет, содержащий перечень удаленных хостов и число байтов,
переданных каждому из них.
"""
import sys
def dictify_logline(line):
'''возвращает словарь, содержащий информацию, извлеченную из
комбинированного файла журнала
В настоящее время нас интересуют только адреса удаленных хостов
и количество переданных байтов, но для полноты картины мы
добавили выборку кода состояния.
'''
split_line = line.split()
return {'remote_host': split_line[0],
'status': split_line[8],
'bytes_sent': split_line[9],
}
def generate_log_report(logfile):
'''возвращает словарь в формате:
remote_host=>[список числа переданных байтов]
Эта функция принимает объект типа file, выполняет обход всех строк
в файле и создает отчет о количестве байтов, переданных при каждом
обращении удаленного хоста к веб серверу.
'''
report_dict = {}
for line in logfile:
line_dict = dictify_logline(line)
print line_dict
try:
bytes_sent = int(line_dict['bytes_sent'])
except ValueError:
##полностью игнорировать непонятные нам ошибки
continue
report_dict.setdefault(line_dict['remote_host'],
[]).append(bytes_sent)
return report_dict
if __name__ == "__main__":
if not len(sys.argv) > 1:
print __doc__
sys.exit(1)
infile_name = sys.argv[1]
try:
infile = open(infile_name, 'r')

148 Глава 3. Текст
except IOError:
print "You must specify a valid file to parse"
print __doc__
sys.exit(1)
log_report = generate_log_report(infile)
print log_report
infile.close()
Этот пример чрезвычайно прост. В разделе __main__ выполняется всего
несколько действий. Вопервых, осуществляется минимально необхо
димая проверка аргументов командной строки, чтобы убедиться, что
сценарий получил как минимум один аргумент. Если пользователь за
пустит сценарий без аргументов, сценарий выведет сообщение о по
рядке использования и завершит работу. Более полное обсуждение,
как лучше обрабатывать аргументы и параметры командной строки,
приводится в главе 13. Далее, в разделе __main__ предпринимается по
пытка открыть указанный файл журнала. Если попытка открыть
файл завершается неудачей, сценарий выведет сообщение о порядке
использования и завершит работу. После этого сценарий передает
файл функции generate_log_report() и выводит результаты.
Функция generate_log_report() создает словарь, который играет роль
отчета. После этого она выполняет обход всех строк в файле и передает
каждую строку функции dictify_logline(), которая в свою очередь воз
вращает словарь, содержащий необходимую нам информацию. Затем
она проверяет, является ли значение bytes_sent целым числом. Если
это целое число, обработка строки продолжается, если нет – выполня
ется переход к следующей строке. После этого она добавляет в словарь
отчета данные, полученные от функции dictify_logline(). Наконец,
она возвращает сформированный словарь отчета программному коду
вразделе __main__.
Функция dictify_logline() просто разбивает строку по пробелам, из
влекает определенные элементы из полученного списка и возвращает
словарь с данными, извлеченными из строки.
Будет ли работать такой сценарий? Проверим это с помощью модуль
ного теста из примера 3.25.
Пример 3.25. Модульный тест сценария анализа файла журнала веб>сервера
Apache – разбиение по пробелам
#!/usr/bin/env python
import unittest
import apache_log_parser_split
class TestApacheLogParser(unittest.TestCase):
def setUp(self):
pass
def testCombinedExample(self):

Анализ журналов 149
# тест комбинированного примера с сайта apache.org
combined_log_entry = '127.0.0.1 \
' frank [10/Oct/2000:13:55:36 0700] '\
'"GET /apache_pb.gif HTTP/1.0" 200 2326 '\
'"http://www.example.com/start.html" '\
'"Mozilla/4.08 [en] (Win98; I ;Nav)"'
self.assertEqual(
apache_log_parser_split.dictify_logline(combined_log_entry),
{'remote_host':'127.0.0.1', 'status':'200', 'bytes_sent':'2326'})
def testCommonExample(self):
# тест общего примера с сайта apache.org
common_log_entry = '127.0.0.1 '\
' frank [10/Oct/2000:13:55:36 0700] '\
'"GET /apache_pb.gif HTTP/1.0" 200 2326'
self.assertEqual(
apache_log_parser_split.dictify_logline(common_log_entry),
{'remote_host':'127.0.0.1', 'status':'200', 'bytes_sent':'2326'})
def testExtraWhitespace(self):
# тест для случая с дополнительными пробелами между полями
common_log_entry = '127.0.0.1 '\
' frank [10/Oct/2000:13:55:36 0700] '\
'"GET /apache_pb.gif HTTP/1.0" 200 2326'
self.assertEqual(
apache_log_parser_split.dictify_logline(common_log_entry),
{'remote_host':'127.0.0.1', 'status':'200', 'bytes_sent':'2326'})
def testMalformed(self):
# тест для случая с дополнительными пробелами между полями
common_log_entry = '127.0.0.1 '\
' frank [10/Oct/2000:13:55:36 0700] '\
'"GET /some/url/with white space.html HTTP/1.0" 200 2326'
self.assertEqual(
apache_log_parser_split.dictify_logline(common_log_entry),
{'remote_host':'127.0.0.1', 'status':'200', 'bytes_sent':'2326'})
if __name__ == '__main__':
unittest.main()
Этот сценарий работает с комбинированным и общим форматами запи
сей в журнале, но небольшое изменение в строке приводит к тому, что
тест завершается неудачей. Ниже приводятся результаты тестиро
вания:
jmjones@dinkgutsy:code$ python test_apache_log_parser_split.py
...F
======================================================================
FAIL: testMalformed (__main__.TestApacheLogParser)

Traceback (most recent call last):
File "test_apache_log_parser_split.py", line 38, in testMalformed
{'remote_host': '127.0.0.1', 'status': '200', 'bytes_sent': '2326'})

150 Глава 3. Текст
AssertionError: {'status': 'space.html', 'bytes_sent': 'HTTP/1.0"',
'remote_host': '127.0.0.1'} != {'status': '200', 'bytes_sent': '2326',
'remote_host': '127.0.0.1'}

Ran 4 tests in 0.001s
FAILED (failures=1)
Вследствие того, что в поле адреса появились два лишних пробела, все
последующие поля в этой записи сместились на две позиции вправо.
Здоровая подозрительность – хорошее качество. Основываясь на спе
цификациях форматов записей в журнале, можно достаточно уверен
но извлекать адреса удаленных хостов и число переданных байтов,
опираясь на способ выделения полей по пробелам. Пример 3.26 пред
ставляет реализацию того же самого сценария, выполненную с приме
нением регулярных выражений.
Пример 3.26. Сценарий анализа файла журнала веб>сервера Apache
#!/usr/bin/env python
"""
ПОРЯДОК ИСПОЛЬЗОВАНИЯ:
apache_log_parser_regex.py some_log_file
Этот сценарий принимает единственный аргумент командной строки: имя файла
журнала, который требуется проанализировать. Он анализирует содержимое файла
и генерирует отчет, содержащий перечень удаленных хостов и число байтов,
переданных каждому из них.
"""
import sys
import re
log_line_re = re.compile(r'''(?P\S+) #IP ADDRESS
\s+ #whitespace
\S+ #remote logname
\s+ #whitespace
\S+ #remote user
\s+ #whitespace
\[[^\[\]]+\] #time
\s+ #whitespace
"[^"]+" #first line of request
\s+ #whitespace
(?P\d+)
\s+ #whitespace
(?P |\d+)
\s* #whitespace
''', re.VERBOSE)
def dictify_logline(line):
'''возвращает словарь, содержащий информацию, извлеченную
из комбинированного файла журнала

Анализ журналов 151
В настоящее время нас интересуют только адреса удаленных хостов
и количество переданных байтов, но но для полноты картины
мы добавили выборку кода состояния.
'''
m = log_line_re.match(line)
if m:
groupdict = m.groupdict()
if groupdict['bytes_sent'] == ' ':
groupdict['bytes_sent'] = '0'
return groupdict
else:
return {'remote_host': None,
'status': None,
'bytes_sent': "0",
}
def generate_log_report(logfile):
'''возвращает словарь в формате:
remote_host=>[список числа переданных байтов]
Эта функция принимает объект типа file, выполняет обход
всех строк в файле и создает отчет о количестве байтов,
переданных при каждом обращении удаленного хоста к веб серверу.
'''
report_dict = {}
for line in logfile:
line_dict = dictify_logline(line)
print line_dict
try:
bytes_sent = int(line_dict['bytes_sent'])
except ValueError:
##полностью игнорировать непонятные нам ошибки
continue
report_dict.setdefault(line_dict['remote_host'],
[]).append(bytes_sent)
return report_dict
if __name__ == "__main__":
if not len(sys.argv) > 1:
print __doc__
sys.exit(1)
infile_name = sys.argv[1]
try:
infile = open(infile_name, 'r')
except IOError:
print "You must specify a valid file to parse"
print __doc__
sys.exit(1)
log_report = generate_log_report(infile)
print log_report
infile.close()

152 Глава 3. Текст
Единственная функция, которая изменилась по сравнению с приме
ром, основанным на «разбиении по пробелам», – это функция dicti
fy_logline(). При этом подразумевается, что тип значения, возвращае
мого этой функцией, остался прежним и в примере, основанном на
применении регулярных выражений. Вместо того, чтобы разбивать
строку из журнала по пробелам, мы воспользовались объектом скомпи
лированного регулярного выражения, log_line_re, для выявления соот
ветствий с помощью метода match(). Если соответствие обнаружено,
с помощью метода groupdict() извлекаются практически готовые к воз
врату данные, где значение bytes_sent устанавливается равным 0, если
поле содержит прочерк (
–) (потому что прочерк означает «нисколько»).
Если соответствие не было найдено, возвращается словарь с теми же са
мыми ключами, но со значениями элементов, равными None и 0.
Действительно ли версия сценария, основанная на использовании ре
гулярных выражений, работает лучше, чем предыдущая? Да, это так.
Ниже приводится модульный тест для новой версии сценария анализа
файлов журнала вебсервера Apache:
#!/usr/bin/env python
import unittest
import apache_log_parser_regex
class TestApacheLogParser(unittest.TestCase):
def setUp(self):
pass
def testCombinedExample(self):
# тест комбинированного примера с сайта apache.org
combined_log_entry = '127.0.0.1 \
' frank [10/Oct/2000:13:55:36 0700] '\
'"GET /apache_pb.gif HTTP/1.0" 200 2326 '\
'"http://www.example.com/start.html" '\
'"Mozilla/4.08 [en] (Win98; I ;Nav)"'
self.assertEqual(
apache_log_parser_regex.dictify_logline(combined_log_entry),
{'remote_host':'127.0.0.1', 'status':'200', 'bytes_sent':'2326'})
def testCommonExample(self):
# тест общего примера с сайта apache.org
common_log_entry = '127.0.0.1 '\
' frank [10/Oct/2000:13:55:36 0700] '\
'"GET /apache_pb.gif HTTP/1.0" 200 2326'
self.assertEqual(
apache_log_parser_regex.dictify_logline(common_log_entry),
{'remote_host':'127.0.0.1', 'status':'200', 'bytes_sent':'2326'})
def testMalformed(self):
# тест для модифицированного примера с ошибками с сайта apache.org
#malformed_log_entry = '127.0.0.1 '\

ElementTree 153
#' frank [10/Oct/2000 13:55:36 0700] '\
#'"GET /apache_pb.gif HTTP/1.0" 200 2326 '\
#'"http://www.example.com/start.html" '\
#'"Mozilla/4.08 [en] (Win98; I ;Nav)"'
malformed_log_entry = '127.0.0.1 '\
' frank [10/Oct/2000:13:55:36 0700] '\
'"GET /some/url/with white space.html HTTP/1.0" 200 2326'
self.assertEqual(
apache_log_parser_regex.dictify_logline(common_log_entry),
{'remote_host':'127.0.0.1', 'status':'200', 'bytes_sent':'2326'})
if __name__ == '__main__':
unittest.main()
И ниже – результаты модульного тестирования:
jmjones@dinkgutsy:code$ python test_apache_log_parser_regex.py
...

Ran 3 tests in 0.001s
OK
ElementTree
Если текст, который необходимо проанализировать, имеет формат
XML, скорее всего вам придется подходить к решению этой проблемы
с несколько иной стороны, чем, например, к анализу обычных тексто
вых файлов журналов. Едва ли вы захотите читать такие файлы стро
ку за строкой и выполнять поиск по шаблону, и едва ли получится ши
роко использовать регулярные выражения. В формате XML использу
ется древовидная структура организации данных, поэтому подход, ос
нованный на построчном чтении, здесь не годится. И использование
регулярных выражений для построения древовидной структуры дан
ных легко может превратиться в кошмар.
Что же тогда делать? Для работы с форматом XML обычно использует
ся один из двух подходов. Существует такая вещь, как «simple API for
XML» (простой прикладной интерфейс для работы с форматом XML),
или SAX. Стандартная библиотека языка Python имеет в своем составе
анализатор SAX. Он обладает высокой скоростью работы и потребляет
совсем немного памяти при анализе XML. Но он основан на примене
нии функций обратного вызова, поэтому для определенных частей
данных, когда встречаются такие разделы документа XML, как от
крывающий и закрывающий теги, он просто вызывает определенные
методы. Это означает, что вам придется задать обработчики для дан
ных и самостоятельно отслеживать информацию о состоянии, что мо
жет оказаться далеко не простым делом. Это делает утверждение
«simple» (простой) в названии «simple API for XML» не совсем соот
ветствующим истине. Другой подход к обработке XML заключается

154 Глава 3. Текст
в использовании объектной модели документа (Document Object Mo
del, DOM). В состав стандартной библиотеки языка Python входит
и библиотека DOM XML. Как правило, анализатор DOM не отличается
высокой скоростью работы и потребляет больше памяти, чем SAX, по
тому что он считывает дерево XML в память целиком и создает отдель
ные объекты для каждого узла дерева. Преимущество использования
DOM заключается в том, что вам не придется отслеживать информа
цию о состоянии, так как каждый узел хранит информацию о роди
тельских и дочерних узлах. Однако прикладной интерфейс DOM в луч
шем случае приходится признать достаточно громоздким.
Имеется и третья возможность – ElementTree. ElementTree – это биб
лиотека синтаксического анализа XML, которая входит в состав стан
дартной библиотеки языка Python, начиная с версии Python 2.5. Биб
лиотеку ElementTree можно представить себе, как легковесный анали
затор DOM, с простым и удобным прикладным интерфейсом. В дополне
ние к простоте и удобству в использовании этот анализатор потребляет
незначительный объем памяти. Мы настоятельно рекомендуем ис
пользовать ElementTree. Если у вас возникнет потребность выполнять
синтаксический анализ документов XML, попробуйте сначала вос
пользоваться библиотекой ElementTree.
Чтобы с помощью ElementTree приступить к анализу файла в формате
XML, достаточно просто импортировать библиотеку и передать требуе
мый файл функции parse():
In [1]: from xml.etree import ElementTree as ET
In [2]: tcusers = ET.parse('/etc/tomcat5.5/tomcat users.xml')
In [3]: tcusers
Out[3]:
Здесь, чтобы сократить объем ввода с клавиатуры при работе с библио
текой, мы импортировали модуль ElementTree под именем ET. Далее,
мы предложили библиотеке выполнить разбор XMLфайла со списком
пользователей, полученного от механизма сервлетов Tomcat. Объект,
созданный библиотекой ElementTree, мы назвали tcusers. Объект tcus
ers имеет тип xml.etree.ElementTree.ElementTree.
Мы удалили из файла пользователей сервера Tomcat примечания о по
рядке использования и текст лицензионного соглашения, в результате
он принял следующий вид:







ElementTree 155
Во время разбора XMLфайла метод parse() из библиотеки ElementTree
создает и возвращает объект дерева, ссылка на который записывается
в переменную tcusers. После этого данная переменная может исполь
зоваться для организации доступа к различным узлам дерева в файле
XML. Наибольший интерес для нас представляют два метода этого
объекта: find() и findall(). Метод find() отыскивает первый узел, соот
ветствующий запросу, который ему передается, и возвращает объект
Element, представляющий этот узел. Метод findall() отыскивает все уз
лы, соответствующие запросу, и возвращает список объектов Element,
которые представляют эти узлы.
Перечень шаблонов, которые можно передавать методам find() и find
all(), ограничен подмножеством выражений на языке XPath. В каче
стве критериев поиска можно указывать имя тега, символ «*», соот
ветствующий всем дочерним элементам; символ «.», соответствую
щий текущему узлу; и комбинацию «//», соответствующую всем под
чиненным узлам, начиная от точки поиска. Символ слеша (/) может
использоваться в качестве разделителя критериев поиска. С помощью
метода find() и имени тега мы попробовали отыскать первый узел user
в файле пользователей Tomcat:
In [4]: first_user = tcusers.find('/user')
In [5]: first_user
Out[5]:
Мы передали методу find() критерий "/user". Начальный символ сле
ша указывает на абсолютный путь с началом в корневом узле. Текст
'user' определяет имя тега, который требуется отыскать. Отсюда сле
дует, что метод find() вернет первый узел с тегом user. Здесь видно, что
объект с именем first_user принадлежит к типу Element.
В число наиболее интересных для нас методов и атрибутов объекта
Element входят attrib, find(), findall(), get(), tag и text. Атрибут attrib –
это словарь атрибутов, принадлежащих данному объекту Element. Ме
тоды find() и findall() этого объекта работают точно так же, как одно
именные методы объекта ElementTree. Метод get() используется для из
влечения указанного атрибута из словаря атрибутов текущего тега
XML. Атрибут tag содержит имя тега текущего объекта Element. Атри
бут text содержит текст, расположенный в текстовом узле текущего
объекта Element.
Ниже приводится элемент документа XML, соответствующий объекту
first_user:

Теперь попробуем обратиться к методам и атрибутам объекта tcusers:
In [6]: first_user.attrib
Out[6]: {'name': 'tomcat', 'password': 'tomcat', 'roles': 'tomcat'}

156 Глава 3. Текст
In [7]: first_user.get('name')
Out[7]: 'tomcat'
In [8]: first_user.get('foo')
In [9]: first_user.tag
Out[9]: 'user'
In [10]: first_user.text
Теперь, когда вы получили некоторое представление о возможностях
библиотеки ElementTree, рассмотрим более сложный пример. Мы вы
полним разбор файла пользователей Tomcat и отыщем все узлы user,
где значение атрибута name соответствует значению, заданному нами
(в данном случае 'tomcat'), как показано в примере 3.27.
Пример 3.27. Разбор файла пользователей Tomcat с помощью
библиотеки ElementTree
#!/usr/bin/env python
from xml.etree import ElementTree as ET
if __name__ == '__main__':
infile = '/etc/tomcat5.5/tomcat users.xml'
tomcat_users = ET.parse(infile)
for user in [e for e in tomcat_users.findall('/user') if
e.get('name') == 'tomcat']:
print user.attrib
Единственное, что представляет сложность в этом примере, – это ис
пользование генератора списков для поиска соответствующих атрибу
тов name. Этот сценарий возвращает следующий реультат:
jmjones@dinkgutsy:code$ python elementtree_tomcat_users.py
{'password': 'tomcat', 'name': 'tomcat', 'roles': 'tomcat'}
В заключение ниже приводится пример использования библиотеки
ElementTree для извлечения некоторой информации из неудачно
сформированного фрагмента XML. В операционной системе Mac OS X
имеется утилита с именем system_profiler, которая отображает инфор
мацию о системе. Формат XML является одним из выходных форма
тов, которые поддерживает утилита system_profiler, но похоже, что
поддержка формата XML была добавлена в самый последний момент.
Мы предполагаем извлечь информацию о версии операционной систе
мы, которая содержится в следующем фрагменте файла XML:

_dataType
SPSoftwareDataType
_detailLevel
2
_items

ElementTree 157


_name
os_overview
kernel_version
Darwin 8.11.1
os_version
Mac OS X 10.4.11 (8S2167)


Вы спросите, почему на наш взгляд этот фрагмент XML оформлен не
удачно? Дело в том, что ни в одном из тегов XML нет ни одного атрибу
та. В основной своей массе теги представляют типы данных. И такие
теги с переменными значениями, как key и string, заключены в один
и тот же родительский тег. Взгляните на пример 3.28.
Пример 3.28. Разбор файла, полученного в результате вызова утилиты
system_profiler в Mac OS X
#!/usr/bin/env python
import sys
from xml.etree import ElementTree as ET
e = ET.parse('system_profiler.xml')
if __name__ == '__main__':
for d in e.findall('/array/dict'):
if d.find('string').text == 'SPSoftwareDataType':
sp_data = d.find('array').find('dict')
break
else:
print "SPSoftwareDataType NOT FOUND"
sys.exit(1)
record = []
for child in sp_data.getchildren():
record.append(child.text)
if child.tag == 'string':
print "% 15s > %s" % tuple(record)
record = []
Сценарий отыскивает все теги dict, в которых имеется дочерний эле
мент string с текстом 'SPSoftwareataType'. Информация, которую тре
буется извлечь, находится в этом узле. В этом примере используется
единственный метод, который не обсуждался ранее, – это метод get
children(). Он просто возвращает список дочерних узлов указанного
элемента. Кроме того, этот пример достаточно ясен, хотя сам файл
XML можно было бы оформить лучше. Ниже приводится результат,
полученный от сценария, когда он был запущен на ноутбуке, работаю
щем под управлением операционной системы Mac OS X Tiger:

158 Глава 3. Текст
dink:~/code jmjones$ python elementtree_system_profile.py
_name > os_overview
kernel_version > Darwin 8.11.1
os_version > Mac OS X 10.4.11 (8S2167)
Библиотека стала прекрасным дополнением к стандартной библиотеке
языка Python. Мы долгое время пользуемся ею и рады, что у нас есть
такая возможность. Вы можете попробовать пользоваться библиотека
ми SAX и DOM, имеющимися в стандартной библиотеке языка Py
thon, но мы думаем, что рано или поздно вы вернетесь к библиотеке
ElementTree.
В заключение
В этой главе были обозначены некоторые фундаментальные принципы
обработки текста в языке Python. Мы имели дело со встроенным ти
пом string, с регулярными выражениями, стандартным вводом и вы
водом, с модулями StringIO и urllib из стандартной библиотеки. После
этого использовали все полученные знания в двух примерах анализа
файлов журналов вебсервера Apache. В заключение были рассмотре
ны основы применения библиотеки ElementTree и продемонстрирова
ны два примера использования для решения практических задач.
Складывается впечатление, что большинство специалистов по опера
ционной системе UNIX, когда речь заходит об обработке текста более
сложной, чем позволяют grep и awk, видят единственную альтерна
тиву – язык Perl. Хотя Perl представляет собой очень мощный язык
программирования, особенно в области обработки текста, мы полага
ем, что язык Python может предложить ничуть не меньше возможно
стей. Фактически, особенно если учесть чистоту синтаксиса и просто
ту, с какой можно перейти от процедурного к объектноориентирован
ному стилю программирования, мы считаем, что язык Python облада
ет преимуществами перед языком Perl. Поэтому мы надеемся, что
в следующий раз, когда вам придется столкнуться с необходимостью
реализовать обработку текста, вы сначала вспомните о языке Python.

4
Создание документации и отчетов
С нашей точки зрения, одним из самых утомительных и наименее же
лательных аспектов работы системного администратора является сбор
различной информации и составление документации для пользовате
лей. Эта работа может нести прямую выгоду вашим пользователям,
которые будут читать документацию, или, возможно, косвенную вы
году пользователям, потому что вы или человек, пришедший вам на
замену, сможете обратиться к ней при необходимости внести измене
ния в будущем. В любом случае создание документации является кри
тически важным аспектом вашей деятельности. Но если это не та рабо
та, выполнением которой вам хотелось бы заняться, тогда вы, скорее
всего, отложите ее. Выполнить эту работу вам поможет Python. Нет,
Python не напишет за вас документацию, но он поможет собрать, от
форматировать и отправить документацию заинтересованным лицам.
В этой главе мы сосредоточим все свое внимание на следующих темах:
сбор, форматирование и передача информации о написанных вами
программах. Любая информация, которой вы предполагаете поде
литься, гдето существует: она может находиться в файлах журналов,
у вас в голове, она может быть доступна в виде результатов выполне
ния некоторых команд, она может даже находиться гденибудь в базе
данных. Самое первое, что необходимо сделать, это собрать необходи
мую информацию. Следующий шаг на пути к передаче информации
другим людям заключается в том, чтобы оформить собранную инфор
мацию. Для оформления можно использовать такие форматы, как
PDF, PNG, JPG, HTML или даже обычный текст. Наконец, необходи
мо передать эту информацию людям, которые заинтересованы в ней.
Надо понять, каким образом заинтересованным лицам будет удобнее
получать требуемую информацию: по электронной почте, на вебсайте
или просматривая файлы на совместно используемом диске.

160 Глава 4. Создание документации и отчетов
Автоматизированный сбор информации
Первый шаг на пути к совместному использованию информации заклю
чается в том, чтобы собрать ее. В этой книге имеются две главы, где рас
сматриваются способы сбора информации: «Текст» (глава 3) и «SNMP»
(глава 7). В третьей главе содержатся примеры, демонстрирующие
различные способы анализа и извлечения данных из текста. В частно
сти, в одном из примеров этой главы из файлов журналов вебсервера
Apache извлекаются IPадреса клиентов, количество переданных бай
тов каждому клиенту и код состояния протокола HTTP. В главе 7 име
ются примеры выполнения запросов к системе на получение самой
разнообразной информации, начиная от объема ОЗУ до пропускной
способности сетевых интерфейсов.
Сбор информации может оказаться более сложным делом, чем простой
поиск и извлечение определенных данных. Часто этот процесс может
оказаться связанным с получением информации из одного представле
ния, например, из файла журнала вебсервера Apache, и сохранением
ее в некотором промежуточном виде для последующего использова
ния. Например, если представить, что вам необходимо создать диа
грамму, показывающую количество байтов, загруженных каждым от
дельным клиентом с уникальным IPадресом за месяц с определенного
вебсервера Apache, тогда процесс сбора информации мог бы включать
в себя ежедневный анализ файла журнала вебсервера Apache, в ходе
которого извлекается необходимая информация (в данном случае: IP
адрес и «количество переданных байтов» для каждого запроса), и со
хранение ее в некотором хранилище данных, откуда потом ее можно
будет получить. Примерами таких хранилищ данных могут служить
реляционные базы данных, объектные базы данных, файлыхранили
ща объектов, файлы в формате CSV и обычные текстовые файлы.
В оставшейся части этого раздела мы попробуем объединить некото
рые понятия из глав, посвященных обработке текста и хранению дан
ных. В частности, здесь будет показано, как соединить вместе приемы
извлечения данных из главы 3 с приемами сохранения данных, обсуж
даемыми в главе 12. При этом мы будем использовать те же библиоте
ки, что описывались в главе, касающейся вопросов обработки текста.
Мы также будем использовать модуль shelve, который будет представ
лен в главе 12, для сохранения информации об HTTPзапросах, посту
пающих от каждого конкретного клиента.
Ниже приводится простой модуль, в котором используются модуль
анализа файлов журналов вебсервера Apache, созданный в предыду
щей главе, и модуль shelve:
#!/usr/bin/env python
import shelve
import apache_log_parser_regex

Автоматизированный сбор информации 161
logfile = open('access.log', 'r')
shelve_file = shelve.open('access.s')
for line in logfile:
d_line = apache_log_parser_regex.dictify_logline(line)
shelve_file[d_line['remote_host']] = \
shelve_file.setdefault(d_line['remote_host'], 0) + \
int(d_line['bytes_sent'])
logfile.close()
shelve_file.close()
В этом примере сначала импортируются модули shelve и apache_log_
parser_regex. Модуль shelve входит в состав стандартной библиотеки
языка Python. Модуль apache_log_parser_regex – это модуль, который
был создан нами в главе 3. Затем открываются файл журнала вебсер
вера Apache access.log и файл, куда будет сохраняться извлеченная
информация, access.s. Далее выполняется обход всех строк в файле
журнала и с помощью модуля разбора журнала для каждой строки
создается словарь. Словарь содержит код состояния запроса HTTP, IP
адрес клиента и число байтов, переданных клиенту. После этого мы
прибавляем число байтов для данного запроса к общему числу байтов,
которое уже было сохранено ранее в объекте shelve для данного IPад
реса. Если в объекте shelve еще отсутствует запись для указанного IP
адреса, общее число переданных байтов автоматически устанавлива
ется равным нулю. После обхода всех строк в файле журнала мы за
крываем этот файл, а также объект shelve. Этот пример будет исполь
зоваться далее в этой главе, когда мы подойдем к вопросу форматиро
вания информации.
Прием электронной почты
Вы наверное даже подумать не могли, что электронная почта может
играть роль средства сбора информации и, тем не менее, это так. Пред
ставьте, что у вас имеется несколько серверов, ни один из которых не
может соединяться с другими, но каждый из них обладает возможно
стью отправлять сообщения электронной почты. При наличии сцена
рия, выполняющего мониторинг вебприложений путем подключения
к ним каждые несколько минут, можно было бы в этом случае исполь
зовать электронную почту как механизм передачи информации. В слу
чае удачного или неудачного подключения можно было бы отправлять
сообщения электронной почты с информацией об успехе или неудаче.
И эти сообщения можно было бы использовать для составления отче
тов – с целью предупредить ответственное лицо в случае появления
проблем.
Для получения сообщений от сервера электронной почты обычно ис
пользуются два протокола: IMAP и POP3. В стандартной поставке Py
thon, куда «входят батарейки», имеются модули, поддерживающие
оба эти протокола.

162 Глава 4. Создание документации и отчетов
Из этих двух протоколов, пожалуй, наиболее часто используется про
токол POP3 и доступ к электронной почте через этот протокол легко
можно организовать с помощью модуля poplib. В примере 4.1 демонст
рируется программный код, использующий модуль poplib для получе
ния всех сообщений, хранящихся на указанном сервере, и записываю
щий их в отдельные файлы на диске.
Пример 4.1. Получение электронной почты по протоколу POP3
#!/usr/bin/env python
import poplib
username = 'someuser'
password = 'S3Cr37'
mail_server = 'mail.somedomain.com'
p = poplib.POP3(mail_server)
p.user(username)
p.pass_(password)
for msg_id in p.list()[1]:
print msg_id
outf = open('%s.eml' % msg_id, 'w')
outf.write('\n'.join(p.retr(msg_id)[1]))
outf.close()
p.quit()
Как видите, в этом примере для начала определяются username (имя
пользователя), password (пароль) и mail_server (сервер электронной поч
ты). Затем выполняется подключение к серверу, которому передаются
предопределенные имя пользователя и пароль. Предположим, что со
единение было выполнено успешно, и мы получили возможность про
сматривать электронную почту для данной учетной записи. После это
го в цикле выполняется обход списка сообщений, извлечение и запись
этих сообщений в файлы на диске. Единственное, что не предусмотре
но в этом сценарии – сообщения не удаляются с сервера после их полу
чения. Чтобы удалить эти сообщения, достаточно было бы добавить
в сценарий вызов метода dele() после retr().
Работа с протоколом IMAP реализуется почти так же просто, как
и с протоколом POP3, но этот протокол не так хорошо описан в доку
ментации к стандартной библиотеке языка Python. В примере 4.2 при
водится программный код, который выполняет те же самые действия,
что и предыдущий пример, но с использованием протокола IMAP.
Пример 4.2. Получение электронной почты по протоколу IMAP
#!/usr/bin/env python
import imaplib
username = 'some_user'
password = '70P53Cr37'
mail_server = 'mail_server'

Сбор информации вручную 163
i = imaplib.IMAP4_SSL(mail_server)
print i.login(username, password)
print i.select('INBOX')
for msg_id in i.search(None, 'ALL')[1][0].split():
print msg_id
outf = open('%s.eml' % msg_id, 'w')
outf.write(i.fetch(msg_id, '(RFC822)')[1][0][1])
outf.close()
i.logout()
Как и в предыдущем примере, здесь также в самом начале сценария
определяются username (имя пользователя), password (пароль) и mail_
server (сервер электронной почты). Затем выполняется подключение
к серверу IMAP через SSL. Затем выполняется вход и выбор папки
электронной почты INBOX. Затем начинается обход всего, что будет най
дено в папке. Метод search() плохо описан в документации к стандарт
ной библиотеке языка Python. Этот метод имеет два обязательных ар
гумента – набор символов и критерий поиска. Какой набор символов
является допустимым? В каком формате он должен указываться? Ка
кие критерии поиска могут использоваться? Как правильно оформля
ется критерий? Мы можем, конечно, предполагать, что чтение IMAP
RFC окажется полезным, но, к счастью, в примере использования про
токола IMAP имеется достаточно информации, позволяющей органи
зовать извлечение всех сообщений, хранящихся в папке. На каждой
итерации цикла выполняется запись содержимого сообщения на диск.
Следует заметить, что при этом все сообщения в папке будут помечены
как «прочитанные». Для вас это может и не представлять большой
проблемы, проблема была бы гораздо большей, если бы сообщения
удалялись, но вам следует знать об этой особенности.
Сбор информации вручную
Рассмотрим также более сложный способ – сбор информации вруч
ную. Здесь подразумевается информация, которая собирается вами
путем просмотра и ввода вручную. В качестве примеров можно при
вести список серверов с соответствующими им IPадресами и описани
ем функций, список контактов с адресами электронной почты, номе
рами телефонов и псевдонимами IM или список с датами отпусков чле
нов вашей команды. Есть, конечно, инструменты, способные управ
лять, если не всей, то большей частью такой информации. Список
серверов можно хранить в файлах Excel или OpenOffice Spreadsheet.
Контакты можно хранить в Outlook или AddressBook.app. А расписа
ние отпусков можно хранить как в Excel/OpenOffice Spreadsheet, так
и в Outlook. Применение таких инструментов может стать решением
в ситуациях, когда технологии свободно доступны, исходные данные
могут представлять собой простой текст, а инструменты обеспечивают
легко настраиваемый вывод информации и поддерживают формат
HTML (или, что еще лучше, XHTML).

164 Глава 4. Создание документации и отчетов
ПОРТ РЕТ ЗНАМЕ НИ ТО СТИ: ПА КЕТ RESTLESS
Аарон Хиллегасс (Aaron Hillegass)
Аарон Хиллегасс, работавший в компаниях NeXT
и Apple, является экспертом в области разработ
ки приложений для операционной системы Mac.
Он является автором книги «Cocoa Programming
for Mac OS X» (Big Nerd Ranch) и преподает про
граммирование на платформе Cocoa в компании
Big Nerd Ranch.
Загрузите полные исходные тексты пакета ReSTless из репозита
рия с примерами программного кода к этой книге по адресу: http:/
/www.oreilly.com/9780596515829. Ниже показано, как вызвать
сценарий на языке Python из вымышленного Cocoaприложения:
#import "MyDocument.h"
@implementation MyDocument
(id)init
{
if (![super init]) {
return nil;
}
// Что должно быть получено в случае нового документа
textStorage = [[NSTextStorage alloc] init];
return self;
}
(NSString *)windowNibName
{
return @"MyDocument";
}
(void)prepareEditView
{
// Менеджер размещения следит за хранилищем текста
// и размещает текст в текстовом поле
NSLayoutManager *lm = [editView layoutManager];
// Отсоединить прежнее хранилище текста
[[editView textStorage] removeLayoutManager:lm];
// Присоединить новое хранилище текста
[textStorage addLayoutManager:lm];
}
(void)windowControllerDidLoadNib:(NSWindowController *) aController
{

Сбор информации вручную 165
[super windowControllerDidLoadNib:aController];
// Отобразить содержимое хранилища текста в текстовом поле
[self prepareEditView];
}
#pragma mark Сохранение и загрузка
// Сохранение (URL всегда имеет тип file:)
(BOOL)writeToURL:(NSURL *)absoluteURL
ofType:(NSString *)typeName
error:(NSError **)outError;
{
return [[textStorage string] writeToURL:absoluteURL
atomically:NO
encoding:NSUTF8StringEncoding
error:outError];
}
// Чтение (URL всегда имеет тип file:)
(BOOL)readFromURL:(NSURL *)absoluteURL
ofType:(NSString *)typeName
error:(NSError **)outError
{
NSString *string = [NSString stringWithContentsOfURL:absoluteURL
encoding:NSUTF8StringEncoding
error:outError];
// Ошибка чтения?
if (!string) {
return NO;
}
[textStorage release];
textStorage = [[NSTextStorage alloc] initWithString:string
attributes:nil];
// Это возврат?
if (editView) {
[self prepareEditView];
}
return YES;
}
#pragma mark Создание и сохранение HTML
(NSData *)dataForHTML
{
// Создать задачу для запуска rst2html.py
NSTask *task = [[NSTask alloc] init];
// Предполагаемое местонахождение программы
NSString *path = @"/usr/local/bin/rst2html.py";
// Файл отсутствует? Попробовать отыскать внутри платформы python

166 Глава 4. Создание документации и отчетов
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
path = @"/Library/Frameworks/Python.framework/Versions/
/Current/bin/rst2html.py;
}
[task setLaunchPath:path];
// Подключить канал для ввода ReST
NSPipe *inPipe = [[NSPipe alloc] init];
[task setStandardInput:inPipe];
[inPipe release];
// Подключить канал для вывода HMTL
NSPipe *outPipe = [[NSPipe alloc] init];
[task setStandardOutput:outPipe];
[outPipe release];
// Запустить процесс
[task launch];
// Получить данные из текстового поля
NSData *inData = [[textStorage string] dataUsingEncoding:
NSUTF8StringEncoding];
// Передать данные в канал и закрыть его
[[inPipe fileHandleForWriting] writeData:inData];
[[inPipe fileHandleForWriting] closeFile];
// Прочитать данные из канала
NSData *outData = [[outPipe fileHandleForReading]
readDataToEndOfFile];
// Завершить задачу
[task release];
return outData;
}
(IBAction)renderRest:(id)sender
{
// Запустить индикатор, чтобы пользователь видел,
// что идет обработка
[progressIndicator startAnimation:nil];
// Получить html в виде NSData
NSData *htmlData = [self dataForHTML];
// Выдать html в основной WebFrame
WebFrame *wf = [webView mainFrame];
[wf loadData:htmlData
MIMEType:@"text/html"
textEncodingName:@"utf 8"
baseURL:nil];
// Остановить индикатор, чтобы пользователь видел,
// что обработка закончена

Сбор информации вручную 167
[progressIndicator stopAnimation:nil];
}
// Вызывается при выборе пункта меню
(IBAction)startSavePanelForHTML:(id)sender
{
// Куда сохранять по умолчанию?
NSString *restPath = [self fileName];
NSString *directory = [restPath stringByDeletingLastPathComponent];
NSString *filename = [[[restPath lastPathComponent]
stringByDeletingPathExtension]
stringByAppendingPathExtension:@"html"];
// Запустить диалог сохранения
NSSavePanel *sp = [NSSavePanel savePanel];
[sp setRequiredFileType:@"html"];
[sp setCanSelectHiddenExtension:YES];
[sp beginSheetForDirectory:directory
file:filename
modalForWindow:[editView window]
modalDelegate:self
didEndSelector:@selector(htmlSavePanel:endedWithCode:context:)
contextInfo:NULL];
}
// Вызывается при закрытии диалога сохранения
(void)htmlSavePanel:(NSSavePanel *)sp
endedWithCode:(int)returnCode
context:(void *)context
{
// Пользователь щелкнул на кнопке Cancel?
if (returnCode != NSOKButton) {
return;
}
// Получить выбранное имя файла
NSString *savePath = [sp filename];
// Получить данные в формате HTML
NSData *htmlData = [self dataForHTML];
// Записать в файл
NSError *writeError;
BOOL success = [htmlData writeToFile:savePath
options:NSAtomicWrite
error:&writeError];
// Записать не удалось?
if (!success) {
// Показать пользователю причину
NSAlert *alert = [NSAlert alertWithError:writeError];

168 Глава 4. Создание документации и отчетов
Несмотря на наличие различных альтернатив, мы собираемся предло
жить здесь текстовый формат, который называется reStructuredText
(или reST). Вот как описывается формат reStructuredText на вебсайте:
reStructuredText – это легкая для чтения, обеспечивающая отобра
жение текста в режиме «что видишь, то и получаешь» простая тек
стовая разметка и одновременно система синтаксического анализа.
Ее удобно использовать для встроенной документации к программам
(например, в строках документирования языка Python), для быстро
го создания вебстраниц, для создания самостоятельных докумен
тов. Разметка reStructuredText предусматривает возможность рас
ширения под нужды различных прикладных областей. Синтаксиче
ский анализатор reStructuredText является составной частью пакета
Docutils. Разметка reStructuredText представляет собой пересмот
ренную реализацию легковесных систем разметки StructuredText
иSetext.
Формат ReST считается предпочтительным форматом для создания
документации в языке Python. Если вы создали программный пакет
на языке Python и решили выложить его в репозитарий PyPI, то от вас
будут ожидать, что сопроводительная документация к пакету будет
иметь формат ReST. Многие самостоятельные проекты, использую
[alert beginSheetModalForWindow:[editView window]
modalDelegate:nil
didEndSelector:NULL
contextInfo:NULL];
return;
}
}
#pragma mark Поддержка печати
(NSPrintOperation *)printOperationWithSettings:(NSDictionary *)
printSettings error:(NSError **)outError
{
// Получить информацию о параметрах настройки печати
NSPrintInfo *printInfo = [self printInfo];
// Получить поле, где отображается весь документ HTML
NSView *docView = [[[webView mainFrame] frameView] documentView];
// Создать задание печати
return [NSPrintOperation printOperationWithView:docView
printInfo:printInfo];
}
@end

Сбор информации вручную 169
щие язык Python, применяют формат ReST в качестве основного для
оформления своей документации.
Итак, какими же преимуществами обладает ReST как формат, исполь
зуемый для создания документации? Вопервых, он достаточно прост.
Вовторых, знакомство с разметкой происходит практически сразу.
Как только перед вами оказывается структура документа, вы быстро
начинаете понимать, что имел в виду автор. Ниже приводится очень
простой пример файла в формате ReST:
=======
Heading
=======
SubHeading

This is just a simple
little subsection. Now,
we'll show a bulleted list:
item one
item two
item three
Этот пример позволяет без чтения документации представить, как вы
глядит правильно оформленный файл в формате reStructuredText.
Возможно, при этом вы еще не в состоянии создать текстовый файл
в формате ReST, но, по крайней мере, вы сможете читать его.
Втретьих, преобразование документов из формата ReST в формат
HTML выполняется очень просто. И на этом третьем пункте мы сосре
доточим свое внимание в этом разделе. Мы не будем пытаться предста
вить здесь учебник по reStructuredText. Если вам захочется ознако
миться с синтаксисом разметки, посетите страницу http://docutils.so>
urceforge.net/docs/user/rst/quickref.html.
Мы пройдем все этапы преобразования разметки ReST в HTML, исполь
зуя документ, который мы только что показали в качестве примера:
In [2]: import docutils.core
In [3]: rest = '''=======
...: Heading
...: =======
...: SubHeading
...:
...: This is just a simple
...: little subsection. Now,
...: we'll show a bulleted list:
...:
...: item one
...: item two
...: item three
...: '''

170 Глава 4. Создание документации и отчетов
In [4]: html = docutils.core.publish_string(source=rest, writer_name='html')
In [5]: print html[html.find('') + 6:html.find('')]


Heading


SubHeading


This is just a simple
little subsection. Now,
we'll show a bulleted list:



  • item one

  • item two

  • item three



Это оказалось совсем несложно. Мы импортировали модуль docutils.co
re. Затем определили строку, содержащую текст в формате reStruc
turedText, передали эту строку методу docutils.core.publish_string()
и потребовали от него преобразовать строку в формат HTML. Затем с по
мощью операции извлечения среза мы извлекли текст, заключенный
между тегами и . Мы извлекли срез потому, что библиоте
ка docutils, использованная здесь для преобразования текста в формат
HTML, вставляет в страницу HTML, созданную с ее помощью, каскад
ные таблицы стилей, чтобы она не выглядела слишком уныло.
Теперь, когда вы увидели, насколько все просто, рассмотрим другой
пример, который находится ближе к системному администрирова
нию. Любой хороший сисадмин должен помнить перечень своих серве
ров и задачи, которые они решают. Поэтому ниже приводится пример,
показывающий, как можно составить список серверов сначала в про
стом текстовом виде, а затем преобразовать его в формат HTML:
In [6]: server_list = '''============== ============ ================
...: Server Name IP Address Function
...: ============== ============ ================
...: card 192.168.1.2 mail server
...: vinge 192.168.1.4 web server
...: asimov 192.168.1.8 database server
...: stephenson 192.168.1.16 file server
...: gibson 192.168.1.32 print server
...: ============== ============ ================'''
In [7]: print server_list
============== ============ ================
Server Name IP Address Function
============== ============ ================
card 192.168.1.2 mail server
vinge 192.168.1.4 web server
asimov 192.168.1.8 database server
stephenson 192.168.1.16 file server

Сбор информации вручную 171
gibson 192.168.1.32 print server
============== ============ ================
In [8]: html = docutils.core.publish_string(source=server_list,
writer_name='html')
In [9]: print html[html.find('') + 6:html.find('')]



































Server Name IP Address Function
card 192.168.1.2 mail server
vinge 192.168.1.4 web server
asimov 192.168.1.8 database server
stephenson 192.168.1.16 file server
gibson 192.168.1.32 print server


Еще одна замечательная и простая текстовая разметка называется Tex
tile. Согласно описанию на вебсайте: «Textile получает текст с *про
стой* разметкой и воспроизводит корректный код разметки XHTML.
Этот формат используется в вебприложениях, в системах управления
содержимым, программным обеспечением блогов и форумов». Если
Textile является языком разметки, то почему он описывается в книге,

172 Глава 4. Создание документации и отчетов
посвященной языку Python? Причина в том, что для языка Python су
ществует библиотека, которая позволяет обрабатывать разметку Textile
и преобразовывать ее в XHTML. Вы можете написать отдельную утили
ту командной строки, которая с помощью библиотеки будет преобразо
вывать файлы из формата Textile в формат XHTML. Или можно вызы
вать модуль, выполняющий преобразование формата Textile, в некото
ром сценарии и программным способом обработать полученную размет
ку XHTML. В любом случае разметка Textile и модуль обработки Textile
могут оказаться неплохим подспорьем в создании документации.
Установить модуль Textile можно с помощью команды easy_install
textile или с помощью системы управления пакетами, имеющейся
в вашем дистрибутиве. В Ubuntu, например, пакет называется python

textile и установить его можно командой apt –get install python –textile.
Как только модуль Textile будет установлен, вы сможете приступить
к его использованию, просто импортируя его, создавая объект Textiler
и вызывая единственный метод этого объекта. Ниже приводится при
мер преобразования маркированного списка из формата Textile в фор
мат XHTML:
In [1]: import textile
In [2]: t = textile.Textiler('''* item one
...: * item two
...: * item three''')
In [3]: print t.process()

  • item one

  • item two

  • item three


Мы не будем пытаться дать здесь описание формата Textile. В Сети
имеется множество ресурсов с этой информацией. Например, по адресу
http://hobix.com/textile/ вы найдете отличное руководство по использо
ванию Textile. Хотя мы и не собираемся слишком углубляться в описа
ние формата Textile, тем не менее, мы посмотрим, как применить фор
мат Textile для оформления информации, собранной вручную, – спис
ка серверов с соответствующими им IPадресами и функциями:
In [1]: import textile
In [2]: server_list = '''|_. Server Name|_. IP Address|_. Function|
...: |card|192.168.1.2|mail server|
...: |vinge|192.168.1.4|web server|
...: |asimov|192.168.1.8|database server|
...: |stephenson|192.168.1.16|file server|
...: |gibson|192.168.1.32|print server|'''
In [3]: print server_list
|_. Server Name|_. IP Address|_. Function|

Сбор информации вручную 173
|card|192.168.1.2|mail server|
|vinge|192.168.1.4|web server|
|asimov|192.168.1.8|database server|
|stephenson|192.168.1.16|file server|
|gibson|192.168.1.32|print server|
In [4]: t = textile.Textiler(server_list)
In [5]: print t.process()































Server Name IP Address Function
card 192.168.1.2 mail server
vinge 192.168.1.4 web server
asimov 192.168.1.8 database server
stephenson 192.168.1.16 file server
gibson 192.168.1.32 print server

Как видите, оба формата, ReST и Textile, могут эффективно использо
ваться в сценариях на языке Python для преобразования текстовых
данных. Если у вас действительно имеются такие данные, как списки
серверов и контактов, которые требуется преобразовывать в формат
HTML и затем предпринимать какиелибо дополнительные действия
(например, отправлять HTML по электронной почте или передавать
HTMLстраницы куданибудь на вебсервер по протоколу FTP), то биб
лиотека docutils или Textile может оказаться для вас полезным инст
рументом.

174 Глава 4. Создание документации и отчетов
Форматирование информации
Следующий этап на пути передачи информации в руки тех, кому она
необходима, заключается в форматировании данных так, чтобы их
легко можно было воспринимать и понимать. Мы считаем, что инфор
мация должна быть представлена в том виде, в каком, по крайней ме
ре, ее легко будет воспринимать, но еще лучше, если оформление бу
дет еще и привлекательным. С технической точки зрения применение
форматов ReST и Textile охватывает сразу оба этапа – сбора и форма
тирования информации, но в следующих примерах мы сосредоточим
ся исключительно на преобразовании уже собранных данных в более
представительный вид.
Графические изображения
В следующих двух примерах мы продолжим пример анализа файла
журнала вебсервера Apache, из которого извлекаются IPадреса кли
ентов и количество переданных байтов. В предыдущем разделе, про
должая этот пример, мы создали промежуточный файл, содержащий
информацию, которой мы предполагаем поделиться с другими. Поэто
му на основе этого промежуточного файла мы создадим диаграмму,
чтобы эти данные было проще воспринимать:
#!/usr/bin/env python
import gdchart
import shelve
shelve_file = shelve.open('access.s')
items_list = [(i[1], i[0]) for i in shelve_file.items()]
items_list.sort()
bytes_sent = [i[0] for i in items_list]
#ip_addresses = [i[1] for i in items_list]
ip_addresses = ['XXX.XXX.XXX.XXX' for i in items_list]
chart = gdchart.Bar()
chart.width = 400
chart.height = 400
chart.bg_color = 'white'
chart.plot_color = 'black'
chart.xtitle = "IP Address"
chart.ytitle = "Bytes Sent"
chart.title = "Usage By IP Address"
chart.setData(bytes_sent)
chart.setLabels(ip_addresses)
chart.draw("bytes_ip_bar.png")
shelve_file.close()
В этом примере были импортированы два модуля, gdchart и shelve. За
тем был открыт файл хранилища объектов, созданный в предыдущем
примере. Объект shelve обладает тем же интерфейсом, что и встроен

Форматирование информации 175
ный тип dictionary, поэтому имеется возможность вызвать его метод
items(). Этот метод возвращает список кортежей, где первый элемент
кортежа соответствует ключу словаря, а второй элемент – значению
этого ключа. Применение метода items() обеспечивает возможность
сортировки данных, что определенно будет иметь смысл, когда мы
начнем рисовать диаграмму. Кроме того, с помощью генератора спи
сков мы меняем порядок следования элементов в кортежах. То есть
вместо кортежей с элементами (ip_address, bytes_sent) мы получаем
кортежи (bytes_sent, ip_address). Затем выполняется сортировка кор
тежей в списке, а поскольку в каждом кортеже первым элементом яв
ляется значение bytes_sent, метод list.sort() выполнит сортировку по
этому элементу. Далее с помощью генератора списков извлекаются
значения bytes_sent и ip_address. Обратите внимание, что мы прибегли
к сокрытию IPадресов, заменив их значением XXX.XXX.XXX.XXX, потому
что данные для этого примера были получены нами из файла журнала
действующего вебсервера.
После выборки данных, на основе которых будет построена диаграм
ма, можно приступать к созданию ее графического представления, ис
пользуя модуль gdchart. Сначала создается объект gdchart.Bar. Это про
стой объект диаграммы, в котором необходимо установить значения
некоторых атрибутов, а затем с его помощью можно будет отобразить
диаграмму в файл PNG. После этого определяются размеры диаграм
мы в пикселях, цвет фона и цвет переднего плана и создаются заголов
ки. Устанавливаются данные и метки для диаграммы, полученные
в результате анализа файла журнала вебсервера Apache. В заключение
вызывается метод draw(), который выводит диаграмму в файл, и произ
водится закрытие файла хранилища. Изображение полученной диа
граммы показано на рис. 4.1.
Ниже приводится другой пример сценария, выполняющего визуали
зацию данных, находящихся в файле хранилища, но на этот раз про
грамма создает не гистограмму, а круговую диаграмму:
#!/usr/bin/env python
import gdchart
import shelve
import itertools
shelve_file = shelve.open('access.s')
items_list = [(i[1], i[0]) for i in shelve_file.items() if i[1] > 0]
items_list.sort()
bytes_sent = [i[0] for i in items_list]
#ip_addresses = [i[1] for i in items_list]
ip_addresses = ['XXX.XXX.XXX.XXX' for i in items_list]
chart = gdchart.Pie()
chart.width = 800
chart.height = 800
chart.bg_color = 'white'

176 Глава 4. Создание документации и отчетов
color_cycle = itertools.cycle([0xDDDDDD, 0x111111, 0x777777])
color_list = []
for i in bytes_sent:
color_list.append(color_cycle.next())
chart.color = color_list
chart.plot_color = 'black'
chart.title = "Usage By IP Address"
chart.setData(*bytes_sent)
chart.setLabels(ip_addresses)
chart.draw("bytes_ip_pie.png")
shelve_file.close()
Принцип действия этого сценария практически идентичен предыду
щему за несколькими исключениями. Вопервых, в этом сценарии
создается экземпляр объекта gdchart.Pie, а не gdchart.Bar. Вовторых,
мы определили отдельные цвета для каждого сектора диаграммы. Это
круговая диаграмма и поэтому, если все сектора вывести черным цве
том, такую диаграмму будет невозможно читать, в связи с чем нами
было принято решение организовать чередование трех градаций серо
го цвета. Чередование этих трех цветов было реализовано с помощью
Рис. 4.1. Гистограмма количества байтов, переданных по запросам
с каждого IP>адреса

Форматирование информации 177
функции cycle() из модуля itertools. Мы рекомендуем вам обратить
свое внимание на модуль itertools. В нем имеется значительное число
интересных функций, которые помогут вам в работе с итерируемыми
объектами (такими, как списки). Результат работы этого сценария,
создающего круговую диаграмму, приводится на рис. 4.2.
Единственный недостаток круговой диаграммы состоит в том, что про
изошло наложение (сокрытие) IPадресов, соответствующих секторам
с минимальными значениями переданных байтов. Гистограммы и кру
говые диаграммы существенно облегчают восприятие данных, находя
щихся в файле хранилища, процесс создания диаграмм оказался на
удивление простым, и включение данных было удивительно легким
делом.
Рис. 4.2. Круговая диаграмма с количеством байтов, переданных
по запросам с каждого IP>адреса

178 Глава 4. Создание документации и отчетов
PDF
Другой способ представления информации из файлов с данными за
ключается в сохранении их в формате PDF. Формат PDF приобрел гос
подствующее положение, и мы готовы предполагать, что все докумен
ты можно преобразовать в PDF. Знание и умение создавать документы
в формате PDF может существенно облегчить жизнь вам как систем
ным администраторам. После прочтения этого раздела вы сможете
применять полученные знания для создания отчетов в формате PDF
о загруженности сети, об учетных записях пользователей и так далее.
Мы также опишем способ автоматического встраивания документов
PDF в сообщения электронной почты с использованием языка Python.
Библиотеку ReportLab в мире библиотек, предназначенных для рабо
ты с форматом PDF, можно сравнить с 350килограммовой гориллой.
В документе, расположенном по адресу http://www.reportlab.com/
docs/userguide.pdf, вы найдете значительное число примеров исполь
зования библиотеки ReportLab. Кроме этого раздела мы настоятельно
рекомендуем вам ознакомиться с официальной документацией проек
та ReportLab. Для установки библиотеки ReportLab в Ubuntu доста
точно дать команду apt
–get install python –reportlab. Если вы пользуе
тесь другим дистрибутивом, воспользуйтесь помощью менеджера па
кетов в своей операционной системе. Иначе у вас всегда есть возмож
ность получить дистрибутив с исходными текстами.
В примере 4.3 приводится пример использования библиотеки Report
Lab для создания простого документа PDF «Hello World».
Пример 4.3. Документ PDF – «Hello World»
#!/usr/bin/env python
from reportlab.pdfgen import canvas
def hello():
c = canvas.Canvas("helloworld.pdf")
c.drawString(100,100,"Hello World")
c.showPage()
c.save()
hello()
Нам хотелось бы сделать несколько замечаний к процессу создания
PDFдокумента «Hello World». Вопервых, сначала был создан объект
canvas. Далее был использован метод drawString(), который можно счи
тать эквивалентом метода file_obj.write() в случае текстовых файлов.
В заключение были вызваны метод showPage(), завершающий процесс
рисования, и метод save(), который фактически создает файл PDF. Ес
ли выполнить этот сценарий, в результате будет получен пустой одно
страничный документ PDF с надписью «Hello World» в самом низу.
Если вы загрузили дистрибутив с исходными текстами библиотеки Re
portLab, вы можете воспользоваться тестами, которые были включены

Форматирование информации 179
как примеры оформления документации. То есть при запуске эти тес
ты создают комплект файлов PDF, которые можно изучить и посмот
реть, как с помощью библиотеки ReportLab можно добиться различ
ных визуальных эффектов.
Теперь, когда вы увидели, как с помощью библиотеки ReportLab соз
даются документы PDF, посмотрим, как с ее же помощью создать от
чет об использовании дискового пространства. Такой отчет может ока
заться весьма полезным. Взгляните на пример 4.4.
Пример 4.4. Отчет об использовании дискового пространства
#!/usr/bin/env python
import subprocess
import datetime
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch
def disk_report():
p = subprocess.Popen("df h", shell=True,
stdout=subprocess.PIPE)
return p.stdout.readlines()
def create_pdf(input,output="disk_report.pdf"):
now = datetime.datetime.today()
date = now.strftime("%h %d %Y %H:%M:%S")
c = canvas.Canvas(output)
textobject = c.beginText()
textobject.setTextOrigin(inch, 11*inch)
textobject.textLines('''
Disk Capacity Report: %s
''' % date)
for line in input:
textobject.textLine(line.strip())
c.drawText(textobject)
c.showPage()
c.save()
report = disk_report()
create_pdf(report)
Этот сценарий генерирует отчет с заголовком «Disk Capacity Report»
(Отчет об использовании дискового пространства), отображающий те
кущую информацию об использовании дискового пространства, а так
же указывается дата и время создания отчета. Для сценария такого не
значительного размера это очень неплохо. Рассмотрим некоторые осо
бенности этого примера. Вопервых, функция disk_report() просто при
нимает вывод команды df
–h и возвращает его в виде списка строк.
Далее, в функции create_pdf() сначала создается надпись с текущей да
той и временем. Самая важная часть этого примера – объект textobject.
Объект textobject создается, чтобы потом поместить его в документ
PDF. Создание объекта textobject производится с помощью метода

180 Глава 4. Создание документации и отчетов
beginText(). Затем определяется способ размещения данных на страни
це. Наш документ будет содержать страницы размером 8.5×11 дюй
мов, поэтому, чтобы поместить текст в самом верху страницы, мы со
общаем текстовому объекту, что текст будет находиться в 11 дюймах
от начала координат. После этого создается заголовок выводом строки
в текстовый объект, и мы завершаем работу, выполняя обход списка
строк, полученных в результате работы команды df. Обратите внима
ние: здесь использован метод line.strip() для удаления символов но
вой строки. Если этого не сделать, символы новой строки будут при
сутствовать в документе в виде черных квадратов.
Имеется возможность создавать намного более сложные документы
PDF, добавляя цвета и изображения, но обо всем этом вы сможете
узнать во время чтения превосходного руководства пользователя, по
ставляемого вместе с библиотекой ReportLib. Главное, что следует из
этих примеров, текст является основным объектом, хранящим дан
ные, которые требуется отобразить.
Распространение информации
После того как данные будут собраны и отформатированы, необходи
мо передать их тем, кто заинтересован в их получении. В этом разделе
мы сосредоточим основное внимание на передаче документации с по
мощью электронной почты. Если вам потребуется передать некоторую
документацию на вебсервер, где ее смогут увидеть ваши пользовате
ли, вы сможете использовать для этого протокол FTP. Использование
стандартного модуля Python для работы с протоколом FTP мы рас
смотрим в следующей главе.
Передача электронной почты
Работа с электронной почтой является важной составляющей в дея
тельности системного администратора. Мало того, что нам приходится
управлять почтовыми серверами, но нам часто приходится придумы
вать способы отправки предупреждений по электронной почте. Стан
дартная библиотека языка Python обладает потрясающей поддержкой
возможности отправлять сообщения электронной почты, но в книгах
об этом упоминается очень редко. Любой системный администратор
должен иметь тщательно налаженный механизм автоматизированной
отправки электронной почты, поэтому с этом разделе будет показано,
как можно решать разнообразные задачи, связанные с электронной
почтой, используя язык Python.
Передача простых сообщений
В состав Python входят два независимых друг от друга пакета, позво
ляющих отправлять сообщения по электронной почте. Один низко
уровневый пакет, smtplib, представляет собой интерфейс к протоколу

Распространение информации 181
SMTP, отвечающий требованиям различных спецификаций RFC. Дру
гой пакет, email, помогает выполнять анализ и создание сообщений
электронной почты. В примере 4.5 с помощью средств пакета smtplib
создается строка, представляющая тело сообщения, а затем с помощью
пакета email производится его отправка серверу электронной почты.
Пример 4.5. Отправка сообщений по протоколу SMTP
#!/usr/bin/env python
import smtplib
mail_server = 'localhost'
mail_server_port = 25
from_addr = 'sender@example.com'
to_addr = 'receiver@example.com'
from_header = 'From: %s\r\n' % from_addr
to_header = 'To: %s\r\n\r\n' % to_addr
subject_header = 'Subject: nothing interesting'
body = 'This is a not very interesting email.'
email_message = '%s\n%s\n%s\n\n%s' % (from_header, to_header,
subject_header, body)
s = smtplib.SMTP(mail_server, mail_server_port)
s.sendmail(from_addr, to_addr, email_message)
s.quit()
Здесь мы определили имя хоста и номер порта сервера электронной
почты, а также адреса «to» (получатель) и «from» (отправитель). За
тем производится сборка самого сообщения путем объединения заго
ловков с телом сообщения. В заключение выполняется подключение
к серверу SMTP и производится отправка сообщения по адресу to_addr
с адреса from_addr. Следует также заметить, что добавление комбина
ций символов \r\n в поля From: и To: выполнено в соответствии с требо
ваниями RFC.
В главе 10 в разделе «Планирование процессов Python» приводится
пример программного кода на языке Python, который запускается как
задание планировщика cron выполняющее отправку сообщений элек
тронной почты. А теперь перейдем от этого простого примера к более
сложным операциям с электронной почтой, которые можно реализо
вать на языке Python.
Аутентификация по протоколу SMTP
Наш последний пример был чрезвычайно прост, поскольку нет ничего
сложного в том, чтобы реализовать отправку почты на языке Python,
но, к сожалению, подавляющее большинство серверов SMTP вынудят
вас проходить процедуру аутентификации, поэтому предыдущий при
мер в таких ситуациях окажется бесполезным. Порядок выполнения
аутентификации демонстрируется в примере 4.6.

182 Глава 4. Создание документации и отчетов
Пример 4.6. Аутентификация по протоколу SMTP
#!/usr/bin/env python
import smtplib
mail_server = 'smtp.example.com'
mail_server_port = 465
from_addr = 'foo@example.com'
to_addr = 'bar@exmaple.com'
from_header = 'From: %s\r\n' % from_addr
to_header = 'To: %s\r\n\r\n' % to_addr
subject_header = 'Subject: Testing SMTP Authentication'
body = 'This mail tests SMTP Authentication'
email_message = '%s\n%s\n%s\n\n%s' % (from_header, to_header,
subject_header, body)
s = smtplib.SMTP(mail_server, mail_server_port)
s.set_debuglevel(1)
s.starttls()
s.login("fatalbert", "mysecretpassword")
s.sendmail(from_addr, to_addr, email_message)
s.quit()
Основное отличие от предыдущего примера заключается в том, что
здесь указываются имя пользователя и пароль. Перед отправкой вызо
вом метода debuglevel() мы активировали режим отладки и затем за
пустили соединение SSL с использованием метода starttls(). Включе
ние режима отладки перед прохождением аутентификации – это заме
чательная идея. Если взглянуть на отладочную информацию, полу
ченную в случае неудачи, она будет иметь вид, как показано ниже:
$ python2.5 mail.py
send: 'ehlo example.com\r\n'
reply: '250 example.com Hello example.com [127.0.0.1], pleased to meet
you\r\n'
reply: '250 ENHANCEDSTATUSCODES\r\n'
reply: '250 PIPELINING\r\n'
reply: '250 8BITMIME\r\n'
reply: '250 SIZE\r\n'
reply: '250 DSN\r\n'
reply: '250 ETRN\r\n'
reply: '250 DELIVERBY\r\n'
reply: '250 HELP\r\n'
reply: retcode (250); Msg: example.com example.com [127.0.0.1], pleased to
meet you
ENHANCEDSTATUSCODES
PIPELINING
8BITMIME
SIZE
DSN
ETRN

Распространение информации 183
DELIVERBY
HELP
send: 'STARTTLS\r\n'
reply: '454 4.3.3 TLS not available after start\r\n'
reply: retcode (454); Msg: 4.3.3 TLS not available after start
В этом примере сервер, с которым мы попытались установить соедине
ние SSL, не поддерживает такую возможность. Можно без особого тру
да обойти эту и другие потенциальные проблемы, создавая сценарии,
которые включают в себя обработку ошибок отправки электронной
почты, реализуя попытки отправки через каскад серверов, вплоть до
попытки отправить почту через локальный сервер.
Реализация отправки вложений на языке Python
Отправка сообщений, состоящих исключительно из текста, выполня
ется очень просто. Однако на языке Python можно реализовать отправ
ку сообщений с использованием стандарта MIME, что означает воз
можность добавлять вложения в исходящие сообщения. В предыду
щем разделе этой главы мы рассматривали возможность создания от
четов в формате PDF. Системные администраторы – нетерпеливые
люди, поэтому мы опустим подробности о происхождении MIME и сра
зу же перейдем к отправке электронной почты с вложениями, как по
казано в примере 4.7.
Пример 4.7. Отправка документа PDF, вложенного в сообщение
электронной почты
import email
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email import encoders
import smtplib
import mimetypes
from_addr = 'noah.gift@gmail.com'
to_addr = 'jjinux@gmail.com'
subject_header = 'Subject: Sending PDF Attachemt'
attachment = 'disk_usage.pdf'
body = '''
This message sends a PDF attachment created with Report
Lab.
'''
m = MIMEMultipart()
m["To"] = to_addr
m["From"] = from_addr
m["Subject"] = subject_header
ctype, encoding = mimetypes.guess_type(attachment)
print ctype, encoding
maintype, subtype = ctype.split('/', 1)

184 Глава 4. Создание документации и отчетов
print maintype, subtype
m.attach(MIMEText(body))
fp = open(attachment, 'rb')
msg = MIMEBase(maintype, subtype)
msg.set_payload(fp.read())
fp.close()
encoders.encode_base64(msg)
msg.add_header("Content Disposition", "attachment", filename=attachment)
m.attach(msg)
s = smtplib.SMTP("localhost")
s.set_debuglevel(1)
s.sendmail(from_addr, to_addr, m.as_string())
s.quit()
Итак, мы совсем немного поколдовали, чтобы закодировать и отпра
вить по электронной почте наш созданный ранее отчет в формате PDF
об использовании дискового пространства.
Trac
Trac – это вики (wiki) и система отслеживания проблем. Она обычно
используется в процессе разработки программного обеспечения и на
писана на языке Python, но в действительности может использоваться
везде, где необходима вики или система регистрации сообщений. По
следнюю версию документации к системе Trac можно найти по адресу:
http://trac.edgewall.org/. Детальное обсуждение Trac выходит далеко
за рамки этой книги, но это достаточно хороший инструмент, который
может использоваться для регистрации поступающих сообщений об
ошибках. Одна из интересных особенностей Trac состоит в том, что эта
система допускает возможность расширения с помощью дополнитель
ных модулей.
Мы упомянули эту систему, потому что она вписывается во все три по
следние темы, которые мы обсуждали: сбор информации, форматиро
вание и распространение. Реализация вики в системе дает возмож
ность пользователям создавать вебстраницы с помощью броузеров.
Информация, которую они добавляют таким способом, становится
доступна в формате HTML другим пользователям. Таким образом, сис
тема реализует полный цикл, обсуждаемый в этой главе.
Точно так же система регистрации и отслеживания сообщений дает
возможность пользователям помещать свои предложения или сообще
ния об обнаруженных проблемах. Вы с ее помощью сможете состав
лять отчеты о сообщениях, введенных через вебинтерфейс, и даже ге
нерировать отчеты в формате CSV. Напомним еще раз, что система
Trac охватывает полный цикл от сбора до распространения информа
ции, который рассматривается в этой главе.

В заключение 185
Мы рекомендуем вам познакомиться с системой Trac поближе, чтобы
понять, насколько полно она отвечает вашим потребностям. Может
быть, вам потребуется нечто более мощное или наоборот, чтонибудь
попроще, но эта система достойна того, чтобы познакомиться с ней по
ближе.
В заключение
В этой главе мы рассмотрели автоматизированный и ручной способы
сбора информации. Мы также рассмотрели способы объединения соб
ранных данных в документы наиболее распространенных форматов,
а именно: HTML, PDF и PNG. В заключение мы рассмотрели способы
передачи информации заинтересованным в ней лицам. Как мы уже го
ворили в начале главы, составление документации может быть не са
мой приятной частью вашей работы. Возможно, при поступлении на
работу вы даже не представляли себе, что придется заниматься доку
ментацией. Но ясная и понятная документация – это чрезвычайно
важный элемент системного администрирования. Мы надеемся, что
советы из этой главы помогут сделать несколько рутинную работу по
созданию документации намного более увлекательной.

5
Сети
Под сетями обычно подразумевается объединение множества компью
теров с целью обеспечить разного рода взаимодействия между ними.
Однако нас в первую очередь интересуют не взаимодействия между
компьютерами, а взаимодействия между процессами. При этом, с точ
ки зрения приемов, которые мы собираемся продемонстрировать, со
вершенно неважно, выполняются взаимодействующие процессы на
разных компьютерах или на одном и том же компьютере.
В этой главе рассматриваются вопросы создания программ на языке
Python, которые соединяются с другими процессами с помощью стан
дартной библиотеки socket (а также библиотек, реализованных на ос
нове библиотеки socket) и затем взаимодействуют с этими процессами.
Сетевые клиенты
Роль серверов – находиться в ожидании, когда клиенты соединятся
с ними, а роль клиентов – инициировать соединения. В состав стан
дартной библиотеки языка Python входят реализации множества сете
вых клиентов. В этом разделе мы обсудим наиболее типичные и часто
используемые разновидности клиентов.
socket
Модуль socket реализует интерфейс доступа к реализации сокетов опе
рационной системы. Это означает, что на языке Python можно реали
зовать любые действия с сокетами. На случай, если ранее вам не при
ходилось заниматься разработкой программ, работающих с сетью, эта
глава предоставляет краткий обзор средств работы в сети. Это должно
помочь вам составить представление о том, какие действия можно реа
лизовать с помощью сетевых библиотек языка Python.

Сетевые клиенты 187
Модуль socket содержит фабричную функцию socket(), которая созда
ет и возвращает объект socket. Несмотря на то, что функция socket()
может принимать большое число аргументов, определяющих тип соз
даваемого сокета, при вызове функции socket() без аргументов по
умолчанию возвращается объект сокет TCP/IP:
In [1]: import socket
In [2]: s = socket.socket()
In [3]: s.connect(('192.168.1.15', 80))
In [4]: s.send("GET / HTTP/1.0\n\n")
Out[4]: 16
In [5]: s.recv(200)
Out[5]: 'HTTP/1.1 200 OK\r\n\
Date: Mon, 03 Sep 2007 18:25:45 GMT\r\n\
Server: Apache/2.0.55 (Ubuntu) DAV/2 PHP/5.1.6\r\n\
Content Length: 691\r\n\
Connection: close\r\n\
Content Type: text/html; charset=UTF 8\r\n\
\r\n\
In [6]: s.close()
В этом примере с помощью фабричной функции socket() создается объ
ект типа socket с именем s. После этого он подключается к порту 80 (но
мер порта, используемый протоколом HTTP по умолчанию) локально
го вебсервера. Затем серверу передается текстовая строка "GET / HTTP/
1.0\n\n" (которая представляет собой простейший запрос HTTP). По
сле посылки запроса сокет получает первые 200 байтов ответа сервера,
в котором содержится сообщение о состоянии "200 OK" и заголовки
HTTP. В самом конце мы закрываем соединение.
В этом примере демонстрируется использование методов объекта socket,
к которым вы, вероятно, будете обращаться наиболее часто. Метод
connect() устанавливает соединение между вашим объектом socket
и удаленным сокетом (то есть «не с этим объектом сокета»). Метод
send() выполняет передачу данных от вашего объекта socket удаленно
му хосту. Метод recv() выполняет прием любых данных, которые бы
ли переданы удаленным хостом. И метод close() закрывает соедине
ние между двумя сокетами. Этот очень простой пример демонстриру
ет, насколько легко можно создавать объекты socket и с их помощью
осуществлять передачу и прием данных.
Теперь рассмотрим немного более полезный пример. Предположим,
что у вас имеется сервер, на котором выполняется сетевое приложе
ние, такое как вебсервер. И вам необходимо следить за его работой,
чтобы гарантировать возможность соединения с ним в течение дня.
Это очень простой вид мониторинга, но он позволяет убедиться, что

188 Глава 5. Сети
сервер продолжает работу и что вебсервер попрежнему ожидает со
единений с клиентами на некотором порту. Взгляните на пример 5.1.
Пример 5.1. Проверка порта TCP
#!/usr/bin/env python
import socket
import re
import sys
def check_server(address, port):
#Создать сокет TCP
s = socket.socket()
print "Attempting to connect to %s on port %s" % (address, port)
try:
s.connect((address, port))
print "Connected to %s on port %s" % (address, port)
return True
except socket.error, e:
print "Connection to %s on port %s failed: %s" % (address, port, e)
return False
if __name__ == '__main__':
from optparse import OptionParser
parser = OptionParser()
parser.add_option(" a", " address", dest="address", default='localhost',
help="ADDRESS for server", metavar="ADDRESS")
parser.add_option(" p", " port", dest="port", type="int", default=80,
help="PORT for server", metavar="PORT")
(options, args) = parser.parse_args()
print 'options: %s, args: %s' % (options, args)
check = check_server(options.address, options.port)
print 'check_server returned %s' % check
sys.exit(not check)
Все необходимые действия выполняются функцией check_server(). Она
создает объект socket. Затем пытается установить соединение с указан
ным номером порта по указанному адресу. Если соединение удалось
установить, функция возвращает значение True. В случае неудачи ме
тод socket.connect() возбуждает исключение, которое перехватывается
функцией, и в этом случае она возвращает значение False. В разделе
main этого сценария выполняется вызов функции check_server(). В этом
разделе выполняется разбор аргументов командной строки, получен
ных от пользователя, и переданные аргументы преобразуются в соот
ветствующий формат для последующей передачи функции check_ser
ver(). В процессе своей работы этот сценарий постоянно выводит сооб
щения о ходе выполнения. Самое последнее, что выводит сценарий, –
это возвращаемое значение функции check_server(). В качестве собст

Сетевые клиенты 189
венного кода завершения сценарий возвращает командной оболочке
значение, противоположное возвращаемому значению функции check_
server(). Сделано это для того, чтобы превратить сценарий в более или
менее полезную утилиту. Обычно утилиты, подобные этой, возвраща
ют командной оболочке значение 0 в случае успеха и некоторое другое
значение, отличное от 0 (обычно некоторое положительное число),
в случае неудачи. Ниже приводится пример вывода сценария, полу
ченного при успешном соединении с вебсервером, к которому мы уже
подключались ранее:
jmjones@dinkgutsy:code$ python port_checker_tcp.py a 192.168.1.15 p 80
options: {'port': 80, 'address': '192.168.1.15'}, args: []
Attempting to connect to 192.168.1.15 on port 80
Connected to 192.168.1.15 on port 80
check_server returned True
Последняя строка в выводе, содержащая текст check_server returned
True, означает, что соединение было благополучно установлено.
Ниже приводится пример вывода сценария, полученного в результате
неудачной попытки соединения:
jmjones@dinkgutsy:code$ python port_checker_tcp.py a 192.168.1.15 p 81
options: {'port': 81, 'address': '192.168.1.15'}, args: []
Attempting to connect to 192.168.1.15 on port 81
Connection to 192.168.1.15 on port 81 failed: (111, 'Connection refused')
check_server returned False
Последняя строка в выводе, содержащая текст check_server returned
False, означает, что попытка соединения не удалась. В предпоследней
строке вывода, содержащей текст Connection to 192.168.1.15 on port 81
failed, можно увидеть причину: 'Connection refused' (Соединение от
вергнуто). Об этом можно строить самые разнообразные предположе
ния – например, возможно, что на данном сервере нет никаких про
цессов, обрабатывающих порт с номером 81.
Мы создали три примера, чтобы продемонстрировать, как можно ис
пользовать эту утилиту в сценариях на языке командной оболочки.
Первый пример представляет собой команду, которая запускает сцена
рий и выводит слово SUCCESS (успешно) в случае успеха. Здесь был ис
пользован оператор &&, играющий роль условной инструкции ifthen:
$ python port_checker_tcp.py a 192.168.1.15 p 80 && echo "SUCCESS"
options: {'port': 80, 'address': '192.168.1.15'}, args: []
Attempting to connect to 192.168.1.15 on port 80
Connected to 192.168.1.15 on port 80
check_server returned True
SUCCESS
В этом случае сценарий благополучно установил соединение, поэтому
после того, как он выполнился и вывел результаты, командная обо
лочка напечатала слово SUCCES.

190 Глава 5. Сети
$ python port_checker_tcp.py a 192.168.1.15 p 81 && echo "FAILURE"
options: {'port': 81, 'address': '192.168.1.15'}, args: []
Attempting to connect to 192.168.1.15 on port 81
Connection to 192.168.1.15 on port 81 failed: (111, 'Connection refused')
check_server returned False
На этот раз сценарий завершился неудачей, но, несмотря на это, ко
манда не вывела слово FAILURE (неудача).
$ python port_checker_tcp.py a 192.168.1.15 p 81 || echo "FAILURE"
options: {'port': 81, 'address': '192.168.1.15'}, args: []
Attempting to connect to 192.168.1.15 on port 81
Connection to 192.168.1.15 on port 81 failed: (111, 'Connection refused')
check_server returned False
FAILURE
В этом случае сценарий тоже потерпел неудачу, но на этот раз мы за
менили оператор && оператором ||. Это просто означает, что в случае,
если сценарий возвращает признак неудачи, следует вывести слово
FAILURE, что и было сделано.
Сам факт, что сервер позволяет выполнить подключение к порту с но
мером 80, еще не означает доступность вебсервера. Более точно опреде
лить состояние вебсервера поможет тест, который определяет, спосо
бен ли вебсервер генерировать заголовки HTTP с ожидаемым кодом со
стояния для заданного URL. Сценарий в примере 5.2 реализует именно
такой тест.
Пример 5.2. Проверка веб>сервера с помощью сокетов
#!/usr/bin/env python
import socket
import re
import sys
def check_webserver(address, port, resource):
#Создать строку запроса HTTP
if not resource.startswith('/'):
resource = '/' + resource
request_string = "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n" % (resource,
address)
print 'HTTP request:'
print '|||%s|||' % request_string
#Создать сокет TCP
s = socket.socket()
print "Attempting to connect to %s on port %s" % (address, port)
try:
s.connect((address, port))
print "Connected to %s on port %s" % (address, port)
s.send(request_string)
#Нам достаточно получить только первые 100 байтов
rsp = s.recv(100)

Сетевые клиенты 191
print 'Received 100 bytes of HTTP response'
print '|||%s|||' % rsp
except socket.error, e:
print "Connection to %s on port %s failed: %s" % (address, port, e)
return False
finally:
#Будучи добропорядочными программистами закроем соединение
print "Closing the connection"
s.close()
lines = rsp.splitlines()
print 'First line of HTTP response: %s' % lines[0]
try:
version, status, message = re.split(r'\s+', lines[0], 2)
print 'Version: %s, Status: %s, Message: %s' % (version,
status, message)
except ValueError:
print 'Failed to split status line'
return False
if status in ['200', '301']:
print 'Success status was %s' % status
return True
else:
print 'Status was %s' % status
return False
if __name__ == '__main__':
from optparse import OptionParser
parser = OptionParser()
parser.add_option(" a", " address", dest="address", default='localhost',
help="ADDRESS for webserver", metavar="ADDRESS")
parser.add_option(" p", " port", dest="port", type="int", default=80,
help="PORT for webserver", metavar="PORT")
parser.add_option(" r", " resource", dest="resource",
default='index.html',
help="RESOURCE to check", metavar="RESOURCE")
(options, args) = parser.parse_args()
print 'options: %s, args: %s' % (options, args)
check = check_webserver(options.address, options.port, options.resource)
print 'check_webserver returned %s' % check
sys.exit(not check)
Как и в предыдущем примере, где основную работу выполняла функция
check_server(), в этом примере также все необходимые действия вы
полняются единственной функцией check_webserver(). Сначала функ
ция check_webserver() создает строку запроса HTTP. Протокол HTTP,
если вы не в курсе, достаточно четко регламентирует порядок взаимо
действий серверов и клиентов. Запрос HTTP, который создается в функ
ции check_webserver(), является чуть ли не самым простым запросом.
Затем функция check_webserver() создает объект socket, с его помощью

192 Глава 5. Сети
устанавливает соединение с сервером и отправляет запрос HTTP. После
этого она читает ответ сервера и закрывает соединение. Когда в сокете
возникает ошибка, функция возвращает значение False, указывая тем
самым, что проверка вебсервера потерпела неудачу. Затем она берет
ответ, полученный от сервера, и извлекает из него код состояния. Если
код состояния имеет значение 200, что означает «OK» (все в порядке),
или 301, что означает «Moved Permanently» (запрошенная страница
была перемещена), функция check_webserver() возвращает значение
True, в противном случае она возвращает значение False. В разделе main
сценарий выполняет разбор аргументов командной строки, получен
ных от пользователя, и вызывает функцию check_webserver(). После
выполнения функции check_webserver() сценарий возвращает команд
ной оболочке значение, противоположное значению, полученному от
функции. Сделано это по тем же причинам, что и в предыдущем при
мере. Нам хотелось бы иметь возможность вызывать этот сценарий из
сценариев командной оболочки и определять случаи успеха или неуда
чи. Ниже приводится пример использования этого сценария:
$ python web_server_checker_tcp.py a 192.168.1.15 p 80 r apache2 default
options: {'resource': 'apache2 default', 'port': 80, 'address':
'192.168.1.15'}, args: []
HTTP request:
|||GET /apache2 default HTTP/1.1
Host: 192.168.1.15
|||
Attempting to connect to 192.168.1.15 on port 80
Connected to 192.168.1.15 on port 80
Received 100 bytes of HTTP response
|||HTTP/1.1 301 Moved Permanently
Date: Wed, 16 Apr 2008 23:31:24 GMT
Server: Apache/2.0.55 (Ubuntu) |||
Closing the connection
First line of HTTP response: HTTP/1.1 301 Moved Permanently
Version: HTTP/1.1, Status: 301, Message: Moved Permanently
Success status was 301
check_webserver returned True
Последние четыре строки вывода показывают, что код состояния HTTP
для адреса /apache2
–default на этом сервере имеет значение 301, что оз
начает успех.
Ниже приводится пример повторной попытки. На этот раз мы указали
ресурс, отсутствующий на вебсервере, чтобы продемонстрировать, что
произойдет, когда функция check_webserver() вернет значение False:
$ python web_server_checker_tcp.py a 192.168.1.15 p 80 r foo
options: {'resource': 'foo', 'port': 80, 'address': '192.168.1.15'}, args: []
HTTP request:
|||GET /foo HTTP/1.1
Host: 192.168.1.15

Сетевые клиенты 193
|||
Attempting to connect to 192.168.1.15 on port 80
Connected to 192.168.1.15 on port 80
Received 100 bytes of HTTP response
|||HTTP/1.1 404 Not Found
Date: Wed, 16 Apr 2008 23:58:55 GMT
Server: Apache/2.0.55 (Ubuntu) DAV/2 PH|||
Closing the connection
First line of HTTP response: HTTP/1.1 404 Not Found
Version: HTTP/1.1, Status: 404, Message: Not Found
Status was 404
check_webserver returned False
Последние четыре строки в предыдущем примере свидетельствовали
об успешной проверке, но в данном случае четыре последние строки
вывода показывают, что проверка завершилась неудачей. На этом сер
вере отсутствует ресурс с адресом /foo, поэтому функция проверки вер
нула значение False.
В этом разделе было показано, как создавать низкоуровневые утили
ты, устанавливающие соединение с серверами в сети и выполняющие
простейшие проверки. Цель этих примеров состояла в том, чтобы по
казать вам, как серверы и клиенты взаимодействуют друг с другом.
Если у вас имеется возможность написать сетевой программный ком
понент, использующий более высокоуровневую библиотеку, чем мо
дуль socket, вам следует использовать ее. Нет смысла тратить свое вре
мя на создание сетевых программных компонентов, использующих та
кую низкоуровневую библиотеку, как модуль socket.
httplib
Предыдущий пример продемонстрировал, как можно выполнить за
прос HTTP с помощью модуля socket. В этом примере будет показано,
как то же самое можно реализовать с помощью модуля httplib. Как
определить, когда предпочтительнее использовать модуль httplib,
а когда модуль socket? Или, в более широком смысле, когда предпоч
тительнее использовать более высокоуровневый модуль, а когда – бо
лее низкоуровневый? Одно хорошее правило, выработанное на прак
тике, гласит – всякий раз, когда имеется такая возможность, следует
использовать более высокоуровневый модуль. Возможно, вам потребу
ется нечто, что пока отсутствует в библиотеке, или вам необходимо ор
ганизовать более точное управление чемто, что уже имеется в библио
теке, или вы захотите воспользоваться преимуществами производи
тельности. Но даже в этом случае нет никаких причин полностью отка
зываться от использования такой библиотеки, как httplib, и отдавать
предпочтение такой низкоуровневой библиотеке, как socket.
Пример 5.3 реализует ту же самую функциональность, что и предыду
щий, используя для этого возможности модуля httplib.

194 Глава 5. Сети
Пример 5.3. Проверка веб>сервера с помощью httplib
#!/usr/bin/env python
import httplib
import sys
def check_webserver(address, port, resource):
#Создать соединение
if not resource.startswith('/'):
resource = '/' + resource
try:
conn = httplib.HTTPConnection(address, port)
print 'HTTP connection created successfully'
#Выполнить запрос
req = conn.request('GET', resource)
print 'request for %s successful' % resource
#Получить ответ
response = conn.getresponse()
print 'response status: %s' % response.status
except sock.error, e:
print 'HTTP connection failed: %s' % e
return False
finally:
conn.close()
print 'HTTP connection closed successfully'
if response.status in [200, 301]:
return True
else:
return False
if __name__ == '__main__':
from optparse import OptionParser
parser = OptionParser()
parser.add_option(" a", " address", dest="address", default='localhost',
help="ADDRESS for webserver", metavar="ADDRESS")
parser.add_option(" p", " port", dest="port", type="int", default=80,
help="PORT for webserver", metavar="PORT")
parser.add_option(" r", " resource", dest="resource",
default='index.html',
help="RESOURCE to check", metavar="RESOURCE")
(options, args) = parser.parse_args()
print 'options: %s, args: %s' % (options, args)
check = check_webserver(options.address, options.port, options.resource)
print 'check_webserver returned %s' % check
sys.exit(not check)
Концептуально этот пример достаточно близок к предыдущему. Два
самых существенных отличия состоят в том, что здесь отсутствует не
обходимость вручную создавать строку запроса HTTP и нет никакой
необходимости вручную выполнять анализ полученного ответа. Объ

Сетевые клиенты 195
ект соединения библиотеки httplib имеет метод request(), который
конструирует и отправляет строку запроса HTTP. Кроме того, у объек
та соединения имеется метод getresponse(), который создает объект
с полученным ответом. Объект ответа предоставляет возможность по
лучить код состояния ответа HTTP, обратившись к атрибуту status.
Хотя объем программного кода в этом примере уменьшился ненамно
го, тем не менее, нам удалось избавиться от необходимости вручную
создавать, отправлять и получать запрос и ответ HTTP. Да и выглядит
этот пример более аккуратным.
Ниже приводится результат запуска сценария с теми же аргументами
командной строки, которые в предыдущем примере привели к успеху.
Мы запрашиваем ресурс с адресом / на вебсервере и находим его:
$ python web_server_checker_httplib.py a 192.168.1.15 r /
options: {'resource': '/', 'port': 80, 'address': '192.168.1.15'}, args: []
HTTP connection created successfully
request for / successful
response status: 200
HTTP connection closed successfully
check_webserver returned True
А ниже – результат запуска сценария с аргументами командной стро
ки, которые в предыдущем примере привели к неудаче. Мы запраши
ваем ресурс с адресом /foo на вебсервере и не находим его:
$ python web_server_checker_httplib.py a 192.168.1.15 r /foo
options: {'resource': '/foo', 'port': 80, 'address': '192.168.1.15'}, args: []
HTTP connection created successfully
request for /foo successful
response status: 404
HTTP connection closed successfully
check_webserver returned False
Как уже говорилось выше, всякий раз, когда имеется возможность ис
пользовать более высокоуровневую библиотеку, следует использовать
ее. Использование httplib вместо модуля socket оказалось более про
стым и более ясным. А чем проще программный код, тем ниже вероят
ность появления ошибок в нем.
ftplib
Помимо модулей httplib и socket в состав стандартной библиотеки
языка Python входит также реализация клиента FTP в виде модуля
ftplib. Модуль ftplib – это полнофункциональная клиентская библио
тека для работы с протоколом FTP, которая позволяет реализовать лю
бые задачи, выполняемые приложениями FTPклиентов. Например,
с ее помощью в сценарии на языке Python можно выполнить вход на
FTPсервер, получить список файлов в определенном каталоге, загру
зить файлы, выгрузить файлы, перейти в другой каталог и выйти.

196 Глава 5. Сети
Можно даже воспользоваться одной из множества платформ, предна
значенных для реализации графического интерфейса, доступных в язы
ке Python, и создать для работы с протоколом FTP свое приложение
с графическим интерфейсом.
Вместо того чтобы дать краткий обзор библиотеки, мы приведем при
мер 5.4 и затем поясним, как он работает.
Пример 5.4. Загрузка файлов с помощью ftplib
#!/usr/bin/env python
from ftplib import FTP
import ftplib
import sys
from optparse import OptionParser
parser = OptionParser()
parser.add_option(" a", " remote_host_address", dest="remote_host_address",
help="REMOTE FTP HOST.",
metavar="REMOTE FTP HOST")
parser.add_option(" r", " remote_file", dest="remote_file",
help="REMOTE FILE NAME to download.",
metavar="REMOTE FILE NAME")
parser.add_option(" l", " local_file", dest="local_file",
help="LOCAL FILE NAME to save remote file to", metavar="LOCAL FILE NAME")
parser.add_option(" u", " username", dest="username",
help="USERNAME for ftp server", metavar="USERNAME")
parser.add_option(" p", " password", dest="password",
help="PASSWORD for ftp server", metavar="PASSWORD")
(options, args) = parser.parse_args()
if not (options.remote_file and
options.local_file and
options.remote_host_address):
parser.error('REMOTE HOST, LOCAL FILE NAME, ' \
'and REMOTE FILE NAME are mandatory')
if options.username and not options.password:
parser.error('PASSWORD is mandatory if USERNAME is present')
ftp = FTP(options.remote_host_address)
if options.username:
try:
ftp.login(options.username, options.password)
except ftplib.error_perm, e:
print "Login failed: %s" % e
sys.exit(1)
else:
try:

Сетевые клиенты 197
ftp.login()
except ftplib.error_perm, e:
print "Anonymous login failed: %s" % e
sys.exit(1)
try:
local_file = open(options.local_file, 'wb')
ftp.retrbinary('RETR %s' % options.remote_file, local_file.write)
finally:
local_file.close()
ftp.close()
В первой части сценария (сразу вслед за инструкциями, выполняющи
ми разбор аргументов командной строки) создается объект FTP вызо
вом конструктора FTP(), которому передается адрес сервера. Можно
было бы создать объект FTP, вызвав конструктор без аргументов, и за
тем вызвать метод connect(), передав ему адрес сервера FTP. Затем сце
нарий выполняет вход на сервер, используя имя пользователя и па
роль, если таковые были указаны, в противном случае выполняется
анонимный вход. Далее он создает объект типа file, куда будут сохра
няться данные, получаемые из файла на сервере FTP. После этого вы
зывается метод retrbinary() объекта FTP. Метод retrbinary(), как следу
ет из его имени, получает двоичный файл с сервера FTP. Метод прини
мает два параметра: команду, извлекающую файл, и функцию обрат
ного вызова. Обратите внимание, что в качестве функции обратного
вызова используется метод write() объекта file, который был создан на
предыдущем шаге. Важно отметить, что в этом случае мы сами не вы
зываем метод write(). Мы передаем метод write() методу retrbinary(),
чтобы он сам мог вызывать метод write(). Метод retrbinary() будет вы
зывать функцию обратно вызова при получении каждого блока дан
ных, получаемого от сервера FTP. Функция обратного вызова может
выполнять над данными любые действия. Например, эта функция
могла бы просто подсчитывать число байтов, принимаемых от сервера
FTP. Передача метода write() объекта file приводит к тому, что дан
ные, получаемые от сервера FTP, будут записаны в объект file. В за
ключение сценарий закрывает объект file и соединение с сервером
FTP. В сценарии предусматривается обработка ошибок: процедура по
лучения двоичного файла с сервера FTP заключена в инструкцию try,
а в блоке finally выполняется закрытие локального файла и соедине
ния с сервером FTP. Если случится чтото непредвиденное, файл и со
единение будут закрыты перед завершением сценария. Краткое обсу
ждение функций обратного вызова вы найдете в приложении.
urllib
Перемещаясь ко все более высокоуровневым модулям, входящим в со
став стандартной библиотеки, мы наконец достигли модуля urllib. Час
то, рассматривая возможность применения библиотеки urllib, предпо
лагают использовать ее для работы с протоколом HTTP, забывая, что

198 Глава 5. Сети
ресурсы FTP также можно идентифицировать посредством URL. По
этому вы, возможно, даже не предполагали, что библиотека urllib по
зволяет обращаться к ресурсам FTP, хотя такая возможность сущест
вует. Пример 5.5 обладает той же функциональностью, что и предыду
щий пример, созданный на основе использования ftplib, но использу
ет модуль urllib.
Пример 5.5. Загрузка файлов с помощью urllib
#!/usr/bin/env python
"""
url retriever
Порядок использования:
url_retrieve_urllib.py URL FILENAME
URL:
Если адрес URL – это адрес FTP URL, то формат адреса должен быть следующим:
ftp://[username[:password]@]hostname/filename
Если имеется необходимость использовать абсолютный путь к загружаемому файлу,
Вы должны определить URL, который выглядит примерно так:
ftp://user:password@host/%2Fpath/to/myfile.txt
Обратите внимание на последовательность '%2F' в начале пути к файлу.
FILENAME:
Абсолютный или относительный путь к локальному файлу
"""
import urllib
import sys
if ' h' in sys.argv or ' help' in sys.argv:
print __doc__
sys.exit(1)
if not len(sys.argv) == 3:
print 'URL and FILENAME are mandatory'
print __doc__
sys.exit(1)
url = sys.argv[1]
filename = sys.argv[2]
urllib.urlretrieve(url, filename)
Этот сценарий выглядит короче и опрятнее. Он наглядно демонстри
рует мощь библиотеки urllib. На самом деле, большую часть сценария
занимает документация, описывающая порядок его использования.
Более того, даже для анализа аргументов командной строки потребо
валось больше программного кода, чем для фактического выполнения
действий. Мы решили упростить процедуру анализа аргументов ко
мандной строки. Поскольку оба аргумента являются обязательными,
мы предпочли использовать позиционные аргументы и избавиться от
ключей. По сути, всю работу в этом примере выполняет единственная
строка:

Средства вызова удаленных процедур 199
urllib.urlretrieve(url, filename)
После получения аргументов командной строки с помощью sys.argv
эта строка обращается по указанному адресу URL и сохраняет полу
ченные данные в локальном файле с указанным именем. Этот сцена
рий будет работать как с адресами HTTP, так и с адресами FTP, и будет
работать, даже когда имя пользователя и пароль включены в URL.
Ценность этого примера заключается в следующем: если вы предполо
жили, что в языке Python некоторые действия должны выполняться
проще, чем в других языках программирования, скорее всего так оно
и окажется. Наверняка имеется какаянибудь высокоуровневая биб
лиотека, которая реализует именно то, что вам необходимо, и эта биб
лиотека входит в состав стандартной библиотеки языка Python. В дан
ном случае библиотека urllib реализует именно то, что необходимо,
и оказалось достаточно заглянуть в документацию к стандартной биб
лиотеке, чтобы узнать об этом. Впрочем, иногда вам, возможно, при
дется покидать стандартную библиотеку и искать другие ресурсы Py
thon, такие как каталог пакетов Python (Python Package Index, PyPI)
по адресу: http://pypi.python.org/pypi.
urllib2
Примером другой высокоуровневой библиотеки является библиотека
urllib2. Эта библиотека реализует практически те же самые функцио
нальные возможности, что и библиотека urllib. Например, urllib2 об
ладает улучшенной поддержкой аутентификации и улучшенной под
держкой cookie. Поэтому, когда вы начнете использовать библиотеку
urllib и обнаружите, что вам чегото не хватает, обратитесь к библио
теке urllib2– возможно, она лучше будет соответствовать вашим по
требностям.
Средства вызова удаленных процедур
Как правило, причиной создания сценариев для работы с сетью стано
вится необходимость организации взаимодействий между процессами.
Часто бывает вполне достаточно ограничиться простыми взаимодейст
виями, например, с помощью протокола HTTP или сокетов. Однако,
иногда возникает необходимость выполнять программный код в раз
ных процессах и даже на разных компьютерах, как если бы это был
один и тот же процесс. Если бы у вас имелась возможность выполнять
программный код удаленно, в некотором другом процессе, запущен
ном из программы на языке Python, то вы, скорее всего, хотели бы,
чтобы возвращаемые значения таких удаленных вызовов были объек
тами языка Python, работать с которыми намного проще, чем с фраг
ментами текста, которые необходимо анализировать вручную. Так
вот, существует несколько инструментов, позволяющих организовать
вызов удаленных процедур (Remote Procedure Call, RPC).

200 Глава 5. Сети
XMLRPC
Технология XMLRPC, позволяющая организовать вызов удаленных
процедур, основана на обмене специально сформированными докумен
тами XML между двумя процессами. Однако пусть вас не беспокоит
часть XML в названии – вам, скорее всего, даже не придется вникать
в формат документов, которыми будут обмениваться процессы. Един
ственное, что вам действительно необходимо знать, чтобы использо
вать технологию XMLRPC – это то, что в стандартной библиотеке
языка Python имеются реализации как клиентской, так и серверной
частей этой технологии. К тому же, вам полезно будет узнать, что реа
лизации XMLRPC имеются в большинстве языков программирова
ния и что эта технология очень проста в использовании.
В примере 5.6 приводится реализация простого сервера XMLRPC.
Пример 5.6. Простой сервер XML>RPC
#!/usr/bin/env python
import SimpleXMLRPCServer
import os
def ls(directory):
try:
return os.listdir(directory)
except OSError:
return []
def ls_boom(directory):
return os.listdir(directory)
def cb(obj):
print "OBJECT::", obj
print "OBJECT.__class__::", obj.__class__
return obj.cb()
if __name__ == '__main__':
s = SimpleXMLRPCServer.SimpleXMLRPCServer(('127.0.0.1', 8765))
s.register_function(ls)
s.register_function(ls_boom)
s.register_function(cb)
s.serve_forever()
Этот сценарий создает новый объект SimpleXMLRPCServer и связывает его
с портом 8765 и с петлевым интерфейсом, имеющим IPадрес 127.0.0.1,
что делает его доступным только для процессов, выполняющихся на
данном компьютере. Затем сценарий регистрирует функции ls() и ls_
boom(), которые определены тут же, в сценарии. Назначение функции
cb() мы объясним чуть погодя. Функция ls() с помощью os.listdir()
получает содержимое указанного каталога и возвращает его в виде
списка. Функция ls() маскирует любые исключения OSError, которые
только могут возникнуть. Функция ls_boom() не выполняет обработку

Средства вызова удаленных процедур 201
исключений и возвращает их клиенту XMLRPC. После этого сцена
рий входит в бесконечный цикл serve_forever(), в котором он ожидает
поступления запросов от клиентов и обрабатывает их. Ниже приводит
ся пример взаимодействия с этим сервером в оболочке IPython:
In [1]: import xmlrpclib
In [2]: x = xmlrpclib.ServerProxy('http://localhost:8765')
In [3]: x.ls('.')
Out[3]:
['.svn',
'web_server_checker_httplib.py',
....
'subprocess_arp.py',
'web_server_checker_tcp.py']
In [4]: x.ls_boom('.')
Out[4]:
['.svn',
'web_server_checker_httplib.py',
....
'subprocess_arp.py',
'web_server_checker_tcp.py']
In [5]: x.ls('/foo')
Out[5]: []
In [6]: x.ls_boom('/foo')

Traceback (most recent call last)
...
.
.
<<большой блок диагностической информации>>
.
.
...
786 if self._type == "fault":
> 787 raise Fault(**self._stack[0])
788 return tuple(self._stack)
789
:
:[Errno 2] No such file or directory: '/foo'">
(:[Errno 2] Нет такого файла или каталога: '/foo')
Прежде всего мы создали объект ServerProxy, указав ему адрес сервера
XMLRPC. Затем мы вызвали функцию x.ls('.'), чтобы получить со
держимое текущего рабочего каталога. Сервер был запущен из катало
га, содержащего программный код примеров к этой книге, поэтому спи
сок включает файлы примеров. Самое интересное, что на стороне кли
ента функция x.ls('.') возвращает список языка Python. Независимо

202 Глава 5. Сети
от языка реализации сервера – Java, Perl, Ruby или C# – можно рас
считывать на получение подобного результата. На языке реализации
сервера может быть получен перечень файлов в каталоге, создан спи
сок, массив или коллекция имен файлов; после этого программный
код сервера XMLRPC может преобразовать этот список или массив
в формат XML и передать его обратно клиенту. Мы также попробовали
вызвать функцию ls_boom(). Благодаря тому, что в функции ls_boom()
не предусматривается обработка исключений, в отличие от ls(), мы
смогли увидеть, как исключение передается от сервера клиенту. Более
того, на стороне клиента мы увидели даже диагностическую инфор
мацию.
Возможности функциональной совместимости, которыми обладает
технология XMLRPC, безусловно интересны. Но гораздо более инте
ресен тот факт, что существует возможность написать некоторый про
граммный код, запустить его на произвольном числе машин и затем
вызывать этот код удаленно в случае необходимости.
Однако в технологии XMLRPC имеются свои ограничения. Эти огра
ничения могут представлять определенную проблему, или сама техно
логия может не соответствовать нуждам и чаяниям разработчика. На
пример, когда удаленному программному коду передается нестандарт
ный объект на языке Python, библиотека XMLRPC преобразует этот
объект в словарь, переводит его в формат XML и отправляет удаленной
стороне. Безусловно, вы сможете обработать эту ситуацию, но для это
го потребуется написать программный код, который будет извлекать
данные из XMLверсии словаря, чтобы превратить его обратно в ори
гинальный объект. Так почему бы не использовать объекты непосред
ственно на сервере RPC, чтобы избежать таких сложностей с преобра
зованиями? Это нельзя сделать с помощью XMLRPC, но существуют
другие возможности.
Pyro
Pyro – это платформа, которая лишена некоторых недостатков, свой
ственных XMLRPC. Название Pyro происходит от Python Remote Ob
jects (удаленные объекты Python). Она позволяет реализовать те же са
мые действия, которые позволяет XMLRPC, но вместо того, чтобы
преобразовывать объекты в форму словаря, она обеспечивает возмож
ность передачи информации о типе вместе с самим объектом. Если вы
действительно захотите воспользоваться платформой Pyro, вам при
дется установить ее отдельно. Она не поставляется вместе с Python.
Кроме того, вы должны понимать, что Pyro работает только со сцена
риями на языке Python, тогда как технология XMLRPC в состоянии
обеспечить взаимодействие между сценариями на языке Python и про
граммами, написанными на других языках. В примере 5.7 приводится
реализация той же самой функции ls(), что и в примере, использую
щем технологию XMLRPC.

Средства вызова удаленных процедур 203
Пример 5.7. Простой сервер Pyro
#!/usr/bin/env python
import Pyro.core
import os
from xmlrpc_pyro_diff import PSACB
class PSAExample(Pyro.core.ObjBase):
def ls(self, directory):
try:
return os.listdir(directory)
except OSError:
return []
def ls_boom(self, directory):
return os.listdir(directory)
def cb(self, obj):
print "OBJECT:", obj
print "OBJECT.__class__:", obj.__class__
return obj.cb()
if __name__ == '__main__':
Pyro.core.initServer()
daemon=Pyro.core.Daemon()
uri=daemon.connect(PSAExample(),"psaexample")
print "The daemon runs on port:",daemon.port
print "The object's uri is:",uri
daemon.requestLoop()
Пример на базе Pyro похож на пример XMLRPC. Сначала мы создали
класс PSAExample с методами ls(), ls_boom() и cb(). Затем из глубин Pyro
был извлечен демон. После этого мы связали объект PSAExample с демо
ном. Наконец, мы запустили демон для обслуживания запросов.
Ниже приводится сеанс взаимодействия с сервером Pyro в оболочке
IPython:
In [1]: import Pyro.core
/usr/lib/python2.5/site packages/Pyro/core.py:11: DeprecationWarning:
The sre module is deprecated, please import re.
import sys, time, sre, os, weakref
In [2]: psa = Pyro.core.getProxyForURI("PYROLOC://localhost:7766/psaexample")
Pyro Client Initialized. Using Pyro V3.5
In [3]: psa.ls(".")
Out[3]:
['pyro_server.py',
....
'subprocess_arp.py',
'web_server_checker_tcp.py']
In [4]: psa.ls_boom('.')

204 Глава 5. Сети
Out[4]:
['pyro_server.py',
....
'subprocess_arp.py',
'web_server_checker_tcp.py']
In [5]: psa.ls("/foo")
Out[5]: []
In [6]: psa.ls_boom("/foo")

Traceback (most recent call last)
/home/jmjones/local/Projects/psabook/oreilly/ in ()
.
.
...
<<большой блок диагностической информации>>
...
.
.
> 115 raise self.excObj
116 def __str__(self):
117 s=self.excObj.__class__.__name__
: [Errno 2] No such file or directory: '/foo'
(type 'exceptions.OSError'>: [Errno 2] Нет такого файла или каталога: '/foo')
Отлично. Мы получили те же результаты, что и в примере XMLRPC.
Именно этого мы и ожидали. Но что произойдет, если попробовать пе
редать нестандартный объект? Попробуем определить новый класс,
создать из него объект и передать его функции cb() в реализации на
основе XMLRPC и методу cb() в реализации на основе Pyro. В приме
ре 5.8 приводится фрагмент программного кода, который будет вы
полняться.
Пример 5.8. Различия между XML>RPC и Pyro
import Pyro.core
import xmlrpclib
class PSACB:
def __init__(self):
self.some_attribute = 1
def cb(self):
return "PSA callback"
if __name__ == '__main__':
cb = PSACB()
print "PYRO SECTION"
print "*" * 20
psapyro = Pyro.core.getProxyForURI("PYROLOC://localhost:7766/psaexample")
print " >>", psapyro.cb(cb)

Средства вызова удаленных процедур 205
print "*" * 20
print "XML RPC SECTION"
print "*" * 20
psaxmlrpc = xmlrpclib.ServerProxy('http://localhost:8765')
print " >>", psaxmlrpc.cb(cb)
print "*" * 20
Обращение к функции cb() в обеих реализациях, XMLRPC и Pyro,
должно привести к вызову метода cb() переданного объекта. И в обоих
случаях этот метод должен вернуть строку PSA callback. Ниже показа
но, что произошло, когда мы запустили этот сценарий:
jmjones@dinkgutsy:code$ python xmlrpc_pyro_diff.py
/usr/lib/python2.5/site packages/Pyro/core.py:11: DeprecationWarning:
The sre module is deprecated, please import re.
import sys, time, sre, os, weakref
PYRO SECTION
********************
Pyro Client Initialized. Using Pyro V3.5
>> PSA callback
********************
XML RPC SECTION
********************
>>
Traceback (most recent call last):
File "xmlrpc_pyro_diff.py", line 23, in
print " >>", psaxmlrpc.cb(cb)
File "/usr/lib/python2.5/xmlrpclib.py", line 1147, in __call__
return self.__send(self.__name, args)
File "/usr/lib/python2.5/xmlrpclib.py", line 1437, in __request
verbose=self.__verbose
File "/usr/lib/python2.5/xmlrpclib.py", line 1201, in request
return self._parse_response(h.getfile(), sock)
File "/usr/lib/python2.5/xmlrpclib.py", line 1340, in _parse_response
return u.close()
File "/usr/lib/python2.5/xmlrpclib.py", line 787, in close
raise Fault(**self._stack[0])
xmlrpclib.Fault: :'dict' object
has no attribute 'cb'">
(xmlrpclib.Fault: :'dict' объект
не имеет атрибут 'cb'">)
Реализация на основе Pyro работает, но реализация на основе XML
RPC потерпела неудачу и оставила нас наедине с кучей диагностиче
ской информации. Последняя строка в этом блоке информации сооб
щает, что в объекте dict отсутствует атрибут cb. Эта строка обретет
смысл, если взглянуть на вывод, полученный от сервера XMLRPC.
Вспомните, что в функции cb() имеется пара инструкций print, кото
рые выводят дополнительную информацию о том, что происходит. Ни
же приводится вывод сервера XMLRPC:

206 Глава 5. Сети
OBJECT:: {'some_attribute': 1}
OBJECT.__class__::
localhost [17/Apr/2008 16:39:02] "POST /RPC2 HTTP/1.0" 200 –
После преобразования в словарь объекта, который был создан в клиен
те, реализованном на базе XMLRPC, атрибут some_attribute превра
тился в ключ словаря. Атрибут сохранился при передаче объекта на
сервер, а метод cb() был утрачен.
Ниже приводится вывод сервера Pyro:
OBJECT:
OBJECT.__class__: xmlrpc_pyro_diff.PSACB
Обратите внимание, что класс объекта – PSACB, т.е. тот, который и был
создан. На стороне сервера на основе Pyro мы должны включить про
граммный код, импортирующий тот же программный код, который
используется клиентом. Это означает, что сервер Pyro вынужден им
портировать программный код клиента. Для сериализации объектов
платформа Pyro использует стандартный модуль pickle, что объясня
ет, почему Pyro обладает схожим поведением.
Подводя итоги, можно сказать: если вам необходимо простое решение
RPC, не имеющее внешних зависимостей, если вам не мешают имею
щиеся ограничения XMLRPC, и вам важна поддержка функциональ
ной совместимости с другими языками программирования, то, скорее
всего, хорошим выбором будет XMLRPC. С другой стороны, если огра
ничения XMLRPC слишком тесны для вас, вы не возражаете против
установки дополнительных библиотек и предполагаете ограничиться
только языком Python, то наилучшим вариантом для вас будет Pyro.
SSH
SSH – это невероятно мощный и широко используемый протокол. Его
можно также воспринимать и как инструмент, потому что наиболее
распространенная его реализация носит то же самое имя. SSH обеспе
чивает безопасное соединение с удаленным сервером, выполнение ко
манд оболочки, передачу файлов и переназначение портов в обоих на
правлениях через соединение.
Если у вас имеется утилита командной строки ssh, почему бы тогда не
воспользоваться протоколом SSH в сценарии? Вся прелесть здесь со
стоит в том, что вы получаете всю мощь протокола SSH в комбинации
с широкими возможностями языка Python.
Протокол SSH2 реализован на языке Python в виде библиотеки с име
нем paramiko. Из сценариев, которые содержат только программный
код на языке Python, вы можете организовать подключение к серверу
SSH и реализовать выполнение задач SSH. В примере 5.9 демонстри
руется, как можно выполнить соединение с сервером SSH и выполнить
простую команду.

SSH 207
Пример 5.9. Соединение с сервером SSH и выполнение команды
в удаленной системе
#!/usr/bin/env python
import paramiko
hostname = '192.168.1.15'
port = 22
username = 'jmjones'
password = 'xxxYYYxxx'
if __name__ == "__main__":
paramiko.util.log_to_file('paramiko.log')
s = paramiko.SSHClient()
s.load_system_host_keys()
s.connect(hostname, port, username, password)
stdin, stdout, stderr = s.exec_command('ifconfig')
print stdout.read()
s.close()
Как видно из листинга, мы импортируем модуль paramiko и определяем
три переменные. Затем создаем объект SSHClient. После этого произво
дится загрузка ключей хоста, которые в операционной системе Linux
извлекаются из файла «known_hosts». Затем выполняется соединение
с сервером SSH. Ни в одном из этих действий нет ничего сложного,
особенно, если вы уже знакомы с SSH.
Теперь мы готовы выполнить команду в удаленной системе. Метод
exec_command() выполняет указанную команду и возвращает три файло
вых дескриптора, ассоциированных с выполняемой командой: стан
дартный ввод, стандартный вывод и стандартный вывод сообщений об
ошибках. Чтобы показать, что команда выполняется на машине с IPад
ресом, который был использован для создания соединения SSH, мы вы
вели результаты выполнения команды ifconfig на удаленном сервере:
jmjones@dinkbuntu:~/code$ python paramiko_exec.py
eth0 Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX
inet addr:192.168.1.15 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: xx00::000:x0xx:xx0x:0x00/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:9667336 errors:0 dropped:0 overruns:0 frame:0
TX packets:11643909 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1427939179 (1.3 GiB) TX bytes:2940899219 (2.7 GiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:123571 errors:0 dropped:0 overruns:0 frame:0
TX packets:123571 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:94585734 (90.2 MiB) TX bytes:94585734 (90.2 MiB)

208 Глава 5. Сети
Выглядит так, как если бы мы выполнили команду ifconfig на локаль
ной машине, только IPадрес отличается.
В примере 5.10 показано, как можно с помощью модуля paramiko вы
полнять передачу файлов по протоколу SFTP между удаленной и ло
кальной машинами. В данном случае пример только получает файлы
с удаленной машины, используя для этого метод get(). Если у вас воз
никнет потребность передать файл на удаленную машину, вы можете
воспользоваться методом put().
Пример 5.10. Получение файлов с сервера SSH
#!/usr/bin/env python
import paramiko
import os
hostname = '192.168.1.15'
port = 22
username = 'jmjones'
password = 'xxxYYYxxx'
dir_path = '/home/jmjones/logs'
if __name__ == "__main__":
t = paramiko.Transport((hostname, port))
t.connect(username=username, password=password)
sftp = paramiko.SFTPClient.from_transport(t)
files = sftp.listdir(dir_path)
for f in files:
print 'Retrieving', f
sftp.get(os.path.join(dir_path, f), f)
t.close()
В случае, если вы захотите использовать открытые/закрытые ключи
вместо паролей, в примере 5.11 приводится модифицированная вер
сия сценария, выполняющего команду в удаленной системе, с исполь
зованием ключа RSA.
Пример 5.11. Соединение с сервером SSH и удаленное выполнение команды –
с использованием закрытого ключа
#!/usr/bin/env python
import paramiko
hostname = '192.168.1.15'
port = 22
username = 'jmjones'
pkey_file = '/home/jmjones/.ssh/id_rsa'
if __name__ == "__main__":
key = paramiko.RSAKey.from_private_key_file(pkey_file)
s = paramiko.SSHClient()
s.load_system_host_keys()
s.connect(hostname, port, pkey=key)

Twisted 209
stdin, stdout, stderr = s.exec_command('ifconfig')
print stdout.read()
s.close()
И в примере 5.12 приводится модифицированная версия сценария,
выполняющего передачу файлов и использующего ключ RSA.
Пример 5.12. Получение файлов с сервера SSH
#!/usr/bin/env python
import paramiko
import os
hostname = '192.168.1.15'
port = 22
username = 'jmjones'
dir_path = '/home/jmjones/logs'
pkey_file = '/home/jmjones/.ssh/id_rsa'
if __name__ == "__main__":
key = paramiko.RSAKey.from_private_key_file(pkey_file)
t = paramiko.Transport((hostname, port))
t.connect(username=username, pkey=key)
sftp = paramiko.SFTPClient.from_transport(t)
files = sftp.listdir(dir_path)
for f in files:
print 'Retrieving', f
sftp.get(os.path.join(dir_path, f), f)
t.close()
Twisted
Twisted – это платформа разработки сетевых приложений на языке
Python, управляемых событиями. Эта платформа позволяет решать
практически любые задачи, имеющие отношение к работе в сети. Как
и любое единое комплексное решение, эта платформа отличается вы
сокой сложностью. Twisted начнет становиться понятной только после
неоднократной работы с ней, а в самом начале работа с платформой мо
жет оказаться непростым делом. Кроме того, изучение Twisted пред
ставляет собой настолько большой труд, что поиск отправного пункта
в решении конкретной проблемы часто может оказаться устрашаю
щим.
Тем не менее, мы настоятельно рекомендуем познакомиться с этой
платформой и оценить, насколько она соответствует вашему образу
мыслей. Если вы легко сможете настроиться на «твистовое» мышле
ние, то изучение платформы Twisted скорее всего будет ценным вло
жением усилий. Отличной отправной точкой в изучении может слу
жить книга «Twisted Network Programming Essentials» Эйба Феттига
(Abe Fettig) (O’Reilly). Эта книга поможет уменьшить влияние отрица
тельных факторов, о которых упоминалось выше.

210 Глава 5. Сети
Twisted – это сетевая платформа, управляемая событиями, то есть вам
придется сконцентрироваться не на написании программного кода,
который инициализирует созданные соединения и закрывает их, и на
низкоуровневых деталях приема данных, а на программном коде, об
рабатывающем происходящие события.
Какие преимущества вы получите при использовании платформы
Twisted? Эта платформа стимулирует, а иногда даже вынуждает вас
разбивать свои задачи на маленькие части. Организация сетевого со
единения отделена от логики обработки событий, происходящих по
сле установления соединения. Эти два фактора до некоторой степени
обеспечивают автоматическую возможность повторного использова
ния вашего программного кода. Еще одно преимущество, которое не
сет в себе применение платформы Twisted, заключается в том, что вам
не придется беспокоиться о низкоуровневых подключениях и зани
маться обработкой ошибок, возникающих в них. Ваша основная зада
ча заключается в том, чтобы решить, что необходимо предпринять при
появлении определенных событий.
В примере 5.13 приводится сценарий, проверяющий сетевой порт и реа
лизованный на платформе Twisted. Это очень простой сценарий, но он
наглядно демонстрирует управляемую событиями природу Twisted,
в чем вы убедитесь при изучении пояснений к программному коду. Но
перед этим мы коснемся нескольких основных концепций, которые
вам необходимо знать. В число этих концепций входят реакторы, фаб
рики, протоколы и отложенное выполнение. Реакторы – это основа
главного цикла обработки событий любого приложения, основанного
на платформе Twisted. Реакторы занимаются доставкой событий, сете
выми взаимодействиями и многопоточным выполнением. Фабрики от
вечают за создание новых экземпляров протоколов. Каждый экземп
Twisted
У большинства из тех, кто пишет программный код, складыва
ется отчетливая аналогия логике выполнения программы или
сценария: это похоже на ручей, текущий по склону холма, ны
ряющий в провалы, ветвящийся и т. п. Такой программный код
легко писать и отлаживать. Программный код, использующий
особенности платформы Twisted, совершенно иной. Вследствие
асинхронной природы его скорее можно сравнить с падающими
каплями дождя, чем с ручьем, текущим по склону, но на этом
аналогии и заканчиваются. Платформа вводит новый компо
нент: перехватчик событий (реактор) сотоварищи. Чтобы напи
сать и отладить программный код, использующий Twisted, при
дется отказаться от привычных убеждений и начать изобретать
аналогии под другую логику выполнения.

Twisted 211
ляр фабрики может порождать экземпляры только одного типа прото
кола. Протоколы определяют принцип действия определенного соеди
нения. Во время выполнения приложения для каждого соединения
создается свой экземпляр протокола. А механизм отложенного выпол
нения обеспечивает возможность объединения действий в цепочки.
Пример 5.13. Проверка порта, реализованная на платформе Twisted
#!/usr/bin/env python
from twisted.internet import reactor, protocol
import sys
class PortCheckerProtocol(protocol.Protocol):
def __init__(self):
print "Created a new protocol"
def connectionMade(self):
print "Connection made"
reactor.stop()
class PortCheckerClientFactory(protocol.ClientFactory):
protocol = PortCheckerProtocol
def clientConnectionFailed(self, connector, reason):
print "Connection failed because", reason
reactor.stop()
if __name__ == '__main__':
host, port = sys.argv[1].split(':')
factory = PortCheckerClientFactory()
print "Testing %s" % sys.argv[1]
reactor.connectTCP(host, int(port), factory)
reactor.run()
Обратите внимание, что здесь мы определили два класса (PortChecker
Protocol и PortCheckerClientFactory), каждый из которых наследует клас
сы платформы Twisted. Мы связали свою фабрику PortCheckerClient
Factory с PortCheckerProtocol, присвоив класс PortCheckerProtocol атри
буту protocol класса PortCheckerClientFactory. Если попытка установить
соединение окончится неудачей, будет вызван метод фабрики client
ConnectionFailed(). Метод clientConnectionFailed() является общим для
всех фабрик платформы Twisted, и это единственный метод, который
мы определили для нашей фабрики. Определяя метод, «поставляе
мый» вместе с фабричным классом, мы переопределили поведение это
го класса, заданное по умолчанию. Когда на стороне клиента попытка
установить соединение терпит неудачу, мы выводим соответствующее
сообщение и останавливаем работу реактора.
PortCheckerProtocol – это представитель протоколов, о которых говори
лось выше. Экземпляр этого класса будет создан сразу же после того,
как будет установлено соединение с сервером, порт которого проверяет
сценарий. В классе PortCheckerProtocol мы определили единственный
метод: connectionMade(). Этот метод является общим для всех классов

212 Глава 5. Сети
протоколов платформы Twisted. Определяя этот метод, мы тем самым
переопределяем поведение по умолчанию. Когда соединение будет
благополучно установлено, платформа Twisted вызовет метод connec
tionMade() этого протокола. Как видно из сценария, этот метод просто
выводит сообщение и останавливает реактор. (К реакторам мы подой
дем очень скоро.)
Здесь оба метода – connectionMade() и clientConnectionFailed() – демон
стрируют «управляемую событиями» природу платформы Twisted. Ус
тановленное соединение – это событие. Точно так же событием являет
ся и неудача при попытке установить соединение. Когда возникают та
кие события, платформа Twisted вызывает соответствующие методы,
выполняющие их обработку, которые так и называются – обработчики
событий.
В основном разделе этого сценария мы создаем экземпляр класса
PortCheckerClientFactory. Затем предписываем реактору платформы
Twisted с помощью заданной фабрики выполнить подключение к за
данному порту указанного сервера (эти значения передаются сцена
рию как аргументы командной строки). После того как реактору будет
дано указание подключиться к заданному порту указанного сервера,
мы запускаем его в работу. Если этого не сделать, тогда вообще ничего
не произойдет.
С точки зрения хронологического порядка выполнения можно сказать,
что мы запускаем реактор после того, как дадим ему указание. В дан
ном случае было указано установить соединение с портом сервера и ис
пользовать класс PortCheckerClientFactory для доставки событий. Если
попытка соединения с указанным портом заданного хоста потерпит
неудачу, цикл обработки событий вызовет метод clientConnectionFai
led() класса PortCheckerClientFactory. Если соединение будет успешно
установлено, фабрика создаст экземпляр протокола PortCheckerProto
col и вызовет метод connectionMade() этого экземпляра. Завершится ли
попытка подключения успехом или неудачей, соответствующий обра
ботчик события остановит реактор и программа завершит свою работу.
Это был очень простой пример, но он демонстрирует суть управляемой
событиями природы платформы Twisted. Ключевыми концепциями
программирования Twisted, которые не были продемонстрированы
в этом примере, являются идея отложенного выполнения и функции
обратного вызова. Механизм отложенного выполнения берет на себя
обязательство выполнить запрошенное действие. А функции обратно
го вызова обеспечивают способ, дающий возможность определить тре
буемое действие. Функции, выполняющие отложенные действия, мо
гут объединяться в цепочки и передавать результаты друг другу. Эта
особенность платформы Twisted действительно очень сложна для по
нимания. (Механизм отложенных действий демонстрируется в приме
ре 5.14.)

Twisted 213
В примере 5.14 представлен сценарий, использующий брокер перспек
тивы (Perspective Broker) – уникальный механизм вызова удаленных
процедур (RPC) в Twisted. Этот пример представляет собой еще одну
реализацию сервера «ls», который ранее в этой же главе был реализо
ван с использованием XMLRPC и Pyro. В первую очередь рассмотрим
реализацию сервера.
Пример 5.14. Сервер брокера перспективы на платформе Twisted
import os
from twisted.spread import pb
from twisted.internet import reactor
class PBDirLister(pb.Root):
def remote_ls(self, directory):
try:
return os.listdir(directory)
except OSError:
return []
def remote_ls_boom(self, directory):
return os.listdir(directory)
if __name__ == '__main__':
reactor.listenTCP(9876, pb.PBServerFactory(PBDirLister()))
reactor.run()
В этом примере определяется единственный класс, PBDirLister. Это
класс брокера перспективы (PB), который действует как удаленный
объект, когда клиент соединяется с ним. В этом примере данный класс
определяет всего два метода: remote_ls() и remote_ls_boom(). Метод remo
te_ls() – это один из удаленных методов, которые будут вызываться
клиентом. Этот метод remote_ls() просто возвращает содержимое ука
занного каталога. Метод remote_ls_boom() выполняет те же действия,
что и метод remote_ls(), за исключением того, что он не предусматри
вает обработку исключений. В главном разделе примера мы предписы
ваем брокеру перспективы присоединиться к порту с номером 9876
и запустить реактор.
Пример 5.15. Клиент брокера перспективы платформы Twisted
#!/usr/bin/python
from twisted.spread import pb
from twisted.internet import reactor
def handle_err(reason):
print "an error occurred", reason
reactor.stop()
def call_ls(def_call_obj):
return def_call_obj.callRemote('ls', '/home/jmjones/logs')
def print_ls(print_result):

214 Глава 5. Сети
print print_result
reactor.stop()
if __name__ == '__main__':
factory = pb.PBClientFactory()
reactor.connectTCP("localhost", 9876, factory)
d = factory.getRootObject()
d.addCallback(call_ls)
d.addCallback(print_ls)
d.addErrback(handle_err)
reactor.run()
В этом сценарии клиента определяются три функции: handle_err(),
call_ls() и print_ls(). Функция handle_err() обрабатывает все возни
кающие ошибки. Функция call_ls() инициирует вызов удаленного ме
тода «ls». Функция print_ls() выводит результаты вызова удаленного
метода «ls». Может показаться немного странным, что одна функция
инициирует вызов удаленного метода, а другая выводит результаты
этого вызова. Но, так как Twisted является асинхронной платформой,
управляемой событиями, такое положение вещей обретает определен
ный смысл. Сама платформа способствует созданию программного ко
да, который делит работу на мелкие части.
В основном разделе примера видно, как реактор определяет, когда и ка
кие функции обратного вызова следует вызывать. Сначала мы создаем
фабрику клиента брокера перспективы и предписываем реактору вы
полнить подключение к порту 9876 сервера localhost, используя фаб
рику клиента PB для обработки запросов. Затем вызовом метода facto
ry.getRootObject() создается заготовка удаленного объекта. Фактиче
ски это объект отложенного действия, поэтому мы имеем возможность
объединить действия в конвейер, вызвав метод addCallback() объекта.
Первой функцией обратного вызова, которую мы добавляем, является
функция call_ls(). Функция call_ls() вызывает метод remote_ls() объ
екта отложенного действия, созданного на предыдущем шаге. Метод
callRemote() возвращает сам объект. Вторая функция обратного вызова
в цепочке обработки – это функция print_ls(). Когда реактор вызыва
ет print_ls(), она выводит результаты обращения к удаленному мето
ду remote_ls() на предыдущем шаге. Фактически реактор передает ре
зультаты вызова удаленного метода функции print_ls(). Третья функ
ция обратного вызова в цепочке – handle_err(), которая является обыч
ным обработчиком ошибок, она просто сообщает о появлении ошибок
в процессе работы. Когда в ходе выполнения возникает ошибка или
когда процесс достигает функции print_ls(), соответствующие методы
останавливают реактор.
Результат работы клиентского сценария выглядит, как показано ниже:
jmjones@dinkgutsy:code$ python twisted_perspective_broker_client.py
['test.log']

Twisted 215
Вывод представляет собой список файлов в указанном каталоге, имен
но это мы и ожидали получить.
Этот сценарий выглядит несколько сложнее, чем можно было ожидать
для такого простого примера RPC. Серверный сценарий выглядит со
поставимым. Создание клиента выгдядит несколько перегруженным
изза объединения функций обратного вызова в конвейер, создания
объекта отложенного действия, реакторов и фабрик. Но это был очень
простой пример. Преимущества платформы Twisted проявляются осо
бенно ярко, когда задача, которую требуется решить, имеет более вы
сокий уровень сложности.
В примере 5.16 представлена немного модифицированная версия толь
ко что продемонстрированного клиента брокера перспективы. Вместо
удаленной функции ls он вызывает удаленную функцию ls_boom. Этот
пример демонстрирует, как производится обслуживание исключений
на стороне клиента и сервера.
Пример 5.16. Клиент брокера перспективы платформы Twisted –
обработка ошибок
#!/usr/bin/python
from twisted.spread import pb
from twisted.internet import reactor
def handle_err(reason):
print "an error occurred", reason
reactor.stop()
def call_ls(def_call_obj):
return def_call_obj.callRemote('ls_boom', '/foo')
def print_ls(print_result):
print print_result
reactor.stop()
if __name__ == '__main__':
factory = pb.PBClientFactory()
reactor.connectTCP("localhost", 9876, factory)
d = factory.getRootObject()
d.addCallback(call_ls)
d.addCallback(print_ls)
d.addErrback(handle_err)
reactor.run()
Ниже показано, что произошло, когда мы запустили этот сценарий:
jmjones@dinkgutsy:code$ python twisted_perspective_broker_client_boom.py an
error occurred [Failure instance: Traceback from remote host Traceback
unavailable
]
И на стороне сервера:

216 Глава 5. Сети
Peer will receive following PB traceback:
Traceback (most recent call last):
...
<большой объем диагностической информации>
...
state = method(*args, **kw)
File "twisted_perspective_broker_server.py", line 13, in remote_ls_boom
return os.listdir(directory)
exceptions.OSError: [Errno 2] No such file or directory: '/foo'
(exceptions.OSError: [Errno 2] Нет такого файла или каталога: '/foo')
Конкретное сообщение об ошибке появилось на стороне сервера, а не
на стороне клиента. На стороне клиента мы лишь увидели, что про
изошла какаято ошибка. Если бы Pyro или XMLRPC вели себя подоб
ным образом, мы посчитали бы, что это недостаток. Но ведь наш обра
ботчик ошибки был вызван в клиентском сценарии, реализованном на
платформе Twisted. Так как эта модель программирования (основан
ная на событиях) отличается от модели программирования, применяе
мой при использовании Pyro и XMLRPC, мы предполагаем, что обра
ботка ошибок будет производиться иначе, и программный код брокера
перспективы сделал именно то, что мы должны были ожидать от него.
Здесь мы представили вашему вниманию даже меньше, чем вершину
айсберга Twisted. На первых порах работа с платформой Twisted мо
жет оказаться достаточно сложным делом изза такой широты воз
можностей этого проекта и подходов к решению задач, так не похожих
на то, к чему привыкло большинство из нас. Платформа Twisted опре
деленно заслуживает внимательного изучения и включения ее в свой
арсенал.
Scapy
Если вам доставляет удовольствие писать программный код для рабо
ты с сетью, вы полюбите Scapy. Scapy – это невероятно удобная инте
рактивная программа и библиотека манипулирования сетевыми паке
тами. Scapy позволяет исследовать сеть, производить сканирование,
производить трассировку маршрутов и выполнять зондирование. Бо
лее того, для Scapy имеется превосходная документация. Если вам по
нравилось это вступление, вам следует приобрести книгу, описываю
щую Scapy более подробно.
Первое, что следует отметить о Scapy, это то, что к моменту написания
этих строк данный программный продукт распространялся в виде
единственного файла. Вы можете загрузить последнюю версию Scapy
по адресу: http://hg.secdev.org/scapy/raw>file/tip/scapy.py. После это
го вы сможете запускать Scapy как самостоятельную программу или
импортировать и использовать этот продукт как библиотеку. Для на
чала воспользуемся им как интерактивной программой. Пожалуйста,
имейте в виду, что программу Scapy придется запускать с привилегия

Scapy 217
ми суперпользователя root, так как ей требуется получить привилеги
рованный доступ к сетевым интерфейсам.
После того как вы загрузите и установите Scapy, вы увидите следую
щее:
Welcome to Scapy (1.2.0.2)
>>>
В этой программе вы можете делать все, что обычно делаете в интерак
тивной оболочке интерпретатора Python, и дополнительно в ваше рас
поряжение поступают специальные команды Scapy. Первое, что мы
сделаем, это вызовем функцию ls() в Scapy, которая выводит все дос
тупные уровни:
>>> ls()
ARP : ARP
ASN1_Packet : None
BOOTP : BOOTP
CookedLinux : cooked linux
DHCP : DHCP options
DNS : DNS
DNSQR : DNS Question Record
DNSRR : DNS Resource Record
Dot11 : 802.11
Dot11ATIM : 802.11 ATIM
Dot11AssoReq : 802.11 Association Request
Dot11AssoResp : 802.11 Association Response
Dot11Auth : 802.11 Authentication
[обрезано]
Мы обрезали вывод, потому что он слишком объемный. Ниже мы вы
полнили рекурсивный запрос DNS имени www.oreilly.com, использовав
общественный сервер DNS Калифорнийского политехнического уни
верситета (Caltech University):
>>> sr1(IP(dst="131.215.9.49")/UDP()/DNS(rd=1,qd=DNSQR(qname="
www.oreilly.com")))
Begin emission:
Finished to send 1 packets.
...*
Received 4 packets, got 1 answers, remaining 0 packets
IP version=4L ihl=5L tos=0x0 len=223 id=59364 flags=DF
frag=0L ttl=239 proto=udp chksum=0xb1e src=131.215.9.49 dst=10.0.1.3
options=''
|UDP sport=domain dport=domain len=203 chksum=0x843 |
DNS id=0 qr=1L opcode=QUERY aa=0L tc=0L rd=1L ra=1L z=0L
rcode=ok qdcount=1 ancount=2 nscount=4 arcount=3 qd=
DNSQR qname='www.oreilly.com.' qtype=A qclass=IN |>
an=DNSRR rrname='www.oreilly.com.' type=A rclass=IN ttl=21600
rdata='208.201.239.36'
[обрезано]

218 Глава 5. Сети
Затем выполнили трассировку маршрута:
>>> ans,unans=sr(IP(dst="oreilly.com",
>>> ttl=(4,25),id=RandShort())/TCP(flags=0x2))
Begin emission:
..............*Finished to send 22 packets.
*...........*********.***.***.*.*.*.*.*
Received 54 packets, got 22 answers, remaining 0 packets
>>> for snd, rcv in ans:
... print snd.ttl, rcv.src, isinstance(rcv.payload, TCP)
...
[обрезано]
20 208.201.239.37 True
21 208.201.239.37 True
22 208.201.239.37 True
23 208.201.239.37 True
24 208.201.239.37 True
25 208.201.239.37 True
Программе Scapy также под силу воспроизводить содержимое паке
тов, на манер утилиты tcpdump:
>>> sniff(iface="en0", prn=lambda x: x.show())
###[ Ethernet ]###
dst= ff:ff:ff:ff:ff:ff
src= 00:16:cb:07:e4:58
type= IPv4
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 78
id= 27957
flags=
frag= 0L
ttl= 64
proto= udp
chksum= 0xf668
src= 10.0.1.3
dst= 10.0.1.255
options= ''
[обрезано]
Кроме того, возможно реализовать трассировку маршрутов в графиче
ском режиме, если в системе установлены graphviz и imagemagic. Сле
дующий пример взят из официальной документации к Scapy:
>>> res,unans = traceroute(["www.microsoft.com","www.cisco.com",
"www.yahoo.com","www.wanadoo.fr","www.pacsec.com"
],dport=[80,443],maxttl=20,
retry= 2)
Begin emission:
************************************************************************

Создание сценариев с использованием Scapy 219
Finished to send 200 packets.
******************Begin emission:
*******************************************Finished to send 110 packets.
**************************************************************Begin emission:
Finished to send 5 packets.
Begin emission:
Finished to send 5 packets.
Received 195 packets, got 195 answers, remaining 5 packets
193.252.122.103:tcp443 193.252.122.103:tcp80 198.133.219.25:tcp443
198.133.219.25:tcp80 207.46.193.254:tcp443 207.46.193.254:tcp80
69.147.114.210:tcp443 69.147.114.210:tcp80 72.9.236.58:tcp443
72.9.236.58:tcp80
Теперь из полученных результатов можно создать неплохой график:
>>> res.graph()
>>> res.graph(type="ps",target="| lp")
>>> res.graph(target="> /tmp/graph.svg")
Теперь, если у вас в системе установлены graphviz и imagemagic, вы
будете поражены красотой графической визуализации!
Однако истинная прелесть Scapy проявляется при создании своих соб
ственных инструментов командной строки и сценариев. В следующем
разделе мы посмотрим на Scapy как на библиотеку.
Создание сценариев с использованием Scapy
Теперь, когда с помощью Scapy мы можем создавать нечто существен
ное, мы покажем реализацию такого интересного инструмента, как
arping. Сначала рассмотрим платформозависимую реализацию инст
румента arping:
#!/usr/bin/env python
import subprocess
import re
import sys
def arping(ipaddress="10.0.1.1"):
"""Функция arping принимает IP адрес хоста или сети,
возвращает вложенный список адресов mac/ip"""
#Предполагается, что arping используется в Red Hat Linux
p = subprocess.Popen("/usr/sbin/arping c 2 %s" % ipaddress, shell=True,
stdout=subprocess.PIPE)
out = p.stdout.read()
result = out.split()
#pattern = re.compile(":")
for item in result:
if ':' in item:
print item

220 Глава 5. Сети
if __name__ == '__main__':
if len(sys.argv) > 1:
for ip in sys.argv[1:]:
print "arping", ip
arping(ip)
else:
arping()
А теперь посмотрим, как с помощью Scapy можно реализовать то же
самое, но платформонезависимым способом:
#!/usr/bin/env python
from scapy import srp,Ether,ARP,conf
import sys
def arping(iprange="10.0.1.0/24"):
"""Функция arping принимает IP адрес хоста или сети,
возвращает вложенный список адресов mac/ip"""
conf.verb=0
ans,unans=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=iprange),
timeout=2)
collection = []
for snd, rcv in ans:
result = rcv.sprintf(r"%ARP.psrc% %Ether.src%").split()
collection.append(result)
return collection
if __name__ == '__main__':
if len(sys.argv) > 1:
for ip in sys.argv[1:]:
print "arping", ip
print arping(ip)
else:
print arping()
Результатом работы сценария является весьма полезная информация,
которая содержит MAC и IPадреса всех узлов подсети:
# sudo python scapy_arp.py
[['10.0.1.1', '00:00:00:00:00:10'], ['10.0.1.7', '00:00:00:00:00:12'],
['10.0.1.30', '00:00:00:00:00:11'], ['10.0.1.200', '00:00:00:00:00:13']]
Эти примеры позволяют получить представление о том, насколько
простой и удобной в использовании является Scapy.

6
Данные
Введение
Управление данными, файлами и каталогами – это одна из причин, по
которым ИТорганизациям необходимы системные администраторы.
У какого системного администратора не возникало необходимости об
рабатывать все файлы в дереве каталогов, отыскивать или заменять
некоторый текст, и если вам еще не пришлось писать сценарий, кото
рый переименовывает все файлы в дереве каталогов, скорее всего это
ожидает вас в будущем. Эти умения составляют суть деятельности сис
темного администратора или, по крайней мере, хорошего системного
администратора. В этой главе мы сосредоточим свое внимание на дан
ных, файлах и каталогах.
Сисадмины постоянно должны перегонять данные из одного места
в другое. Ежедневное перемещение данных у одних системных адми
нистраторов составляет большую часть их работы, у других меньшую.
В индустрии производства мультипликационных фильмов постоянная
«перегонка» данных из одного места в другое является необходимым
условием, потому что для производства цифровых фильмов требуются
терабайты и терабайты пространства. Различные требования предъяв
ляются к операциям ввода/вывода на дисковые накопители, исходя из
качества и разрешения изображения, просматриваемого в каждый
конкретный момент времени. Если данные необходимо «перегонять»
на жесткий диск для просмотра, чтобы к ним был постоянный доступ
в ходе оцифровки, то объектами перемещения будут «свежие» несжа
тые или с незначительной степенью сжатия файлы изображений с вы
соким разрешением. Необходимость перемещения файлов обусловле
на тем, что в анимационной индустрии вообще используются два типа
накопителей. Существуют недорогие, емкие, медленные, надежные
накопители и быстрые, дорогостоящие накопители, которые нередко

222 Глава 6. Данные
представляют собой JBOD («just a bunch of disks» – простой дисковый
массив), объединенные в массив RAID 0 для обеспечения большей про
изводительности. Системного администратора, которому прежде всего
приходится иметь дело с данными, в киноиндустрии часто называют
«погонщиком данных».
Погонщик данных должен постоянно перемещать и переносить новые
данные из одного места в другое. Часто для этого используются такие
утилиты, как rsync, scp или mv. Эти простые, но мощные инструмен
ты могут использоваться в сценариях на языке Python для выполне
ния самых невероятных действий.
С помощью стандартной библиотеки языка Python можно делать по
трясающие вещи без дополнительных затрат. Преимущества стан
дартной библиотеки состоят в том, что ваши сценарии перемещения
данных будут работать везде, независимо от наличия платформозави
симой версии, например, утилиты tar.
Кроме того, не забывайте про резервное копирование. Существует мас
са сценариев и приложений резервного копирования, для создания ко
торых требуется смехотворный объем программного кода на языке Py
thon. Мы хотим предупредить вас, что создание дополнительных тес
тов для проверки программного кода, выполняющего резервное копи
рование, не только желательно, но и необходимо. Вы обязательно
должны провести как модульное, так и функциональное тестирование,
если вы используете собственные сценарии резервного копирования.
Кроме того, часто бывает необходимо выполнить обработку данных до,
после или в процессе перемещения. Конечно, Python прекрасно подхо
дит для решения и таких задач. Инструмент дедупликации, то есть
инструмент, который отыскивает дубликаты файлов и выполняет не
которые действия над ними, очень полезно иметь под рукой, поэтому
мы покажем, как создать его. Это один из примеров работы с непре
кращающимся потоком данных, с чем часто приходится сталкиваться
системным администраторам.
Использование модуля OS
для взаимодействия с данными
Если вам когданибудь приходилось создавать кроссплатформенные
сценарии командной оболочки, вы по достоинству оцените то обстоя
тельство, что модуль OS предоставляет переносимый прикладной ин
терфейс доступа к системным службам. В Python 2.5 модуль OS содер
жит более 200 методов, многие из которых предназначены для работы
с данными. В этом разделе мы рассмотрим многие из методов этого мо
дуля, которые пригодятся системным администраторам, которым час
то приходится иметь дело с данными.

Использование модуля OS для взаимодействия с данными 223
Всякий раз, когда приходится исследовать новый модуль, оболочка
IPython оказывается незаменимым инструментом для этого, поэтому
давайте начнем наше путешествие по модулю OS с помощью оболочки
IPython, в которой будем выполнять действия, наиболее часто встре
чающиеся в практике. В примере 6.1 показано, как это делается.
Пример 6.1. Исследование методов модуля OS, наиболее часто
используемых при работе с данными
In [1]: import os
In [2]: os.getcwd()
Out[2]: '/private/tmp'
In [3]: os.mkdir("/tmp/os_mod_explore")
In [4]: os.listdir("/tmp/os_mod_explore")
Out[4]: []
In [5]: os.mkdir("/tmp/os_mod_explore/test_dir1")
In [6]: os.listdir("/tmp/os_mod_explore")
Out[6]: ['test_dir1']
In [7]: os.stat("/tmp/os_mod_explore")
Out[7]: (16877, 6029306L, 234881026L, 3, 501, 0, 102L,
1207014425, 1207014398, 1207014398)
In [8]: os.rename("/tmp/os_mod_explore/test_dir1",
"/tmp/os_mod_explore/test_dir1_renamed")
In [9]: os.listdir("/tmp/os_mod_explore")
Out[9]: ['test_dir1_renamed']
In [10]: os.rmdir("/tmp/os_mod_explore/test_dir1_renamed")
In [11]: os.rmdir("/tmp/os_mod_explore/")
В этом примере, после того как был импортирован модуль OS, в строке
[2] мы получили имя текущего рабочего каталога, затем в строке [3]
создали новый каталог. После этого в строке [4] с помощью метода
os.listdir() было получено содержимое этого вновь созданного катало
га. Затем мы воспользовались методом os.stat(), который похож на ко
манду stat в Bash, а затем в строке [8] переименовали каталог. В строке
[9] мы убедились, что каталог был переименован, и после этого мы уда
лили все созданные нами каталоги с помощью метода os.rmdir().
Этот пример ни в коем случае нельзя считать исчерпывающим иссле
дованием модуля OS. Кроме этого существует большое число методов,
которые могут вам пригодиться при работе с данными, включая мето
ды изменения прав доступа и методы создания символических ссылок.
Чтобы познакомиться с перечнем доступных методов модуля OS, обра
щайтесь к документации для своей версии Python или воспользуйтесь
функцией дополнения по клавише табуляции в оболочке IPython.

224 Глава 6. Данные
Копирование, перемещение, переименование
и удаление данных
Во вводном разделе главы мы говорили о перегонке данных, кроме то
го у вас уже есть некоторое представление о том, как можно использо
вать модуль OS, поэтому теперь мы можем сразу перейти на более вы
сокий уровень – к модулю shutil, который предназначен для работы
с более крупномасштабными элементами данных. Модуль shutil со
держит методы копирования, перемещения, переименования и удале
ния данных, как и модуль OS, но эти действия могут выполняться над
целыми деревьями данных.
Исследование модуля shutil в оболочке IPython – это самый увлека
тельный способ знакомства с ним. В примере ниже мы будем использо
вать метод shutil.copytree(), но в этом модуле имеется множество дру
гих методов копирования, принцип действия которых несколько отли
чается. Чтобы увидеть, в чем заключается разница между различными
методами копирования, обращайтесь к документации по стандартной
библиотеке языка Python. Взгляните на пример 6.2.
Пример 6.2. Использование модуля shutil для копирования дерева данных
In [1]: import os
In [2]: os.chdir("/tmp")
In [3]: os.makedirs("test/test_subdir1/test_subdir2")
In [4]: ls lR
total 0
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test/
./test:
total 0
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test_subdir1/
./test/test_subdir1:
total 0
drwxr xr x 2 ngift wheel 68 Mar 31 22:27 test_subdir2/
./test/test_subdir1/test_subdir2:
In [5]: import shutil
In [6]: shutil.copytree("test", "test copy")
In [19]: ls lR
total 0
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test/
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test copy/
./test:
total 0
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test_subdir1/

Копирование, перемещение, переименование и удаление данных 225
./test/test_subdir1:
total 0
drwxr xr x 2 ngift wheel 68 Mar 31 22:27 test_subdir2/
./test/test_subdir1/test_subdir2:
./test copy:
total 0
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test_subdir1/
./test copy/test_subdir1:
total 0
drwxr xr x 2 ngift wheel 68 Mar 31 22:27 test_subdir2/
./test copy/test_subdir1/test_subdir2:
Очевидно, что это очень простые и, вместе с тем, невероятно полезные
действия, а кроме того, вы легко можете использовать подобный про
граммный код внутри более сложного, кроссплатформенного сцена
рия, выполняющего перемещение данных. Первое, что приходит в го
лову, – подобный программный код можно использовать для переме
щения данных из одной файловой системы в другую по определенному
событию. При производстве мультипликационных фильмов часто бы
вает необходимо дождаться завершения работы над последними кад
рами, чтобы потом преобразовать их в последовательность, пригодную
для редактирования.
Мы могли бы написать сценарий, который в качестве задания для пла
нировщика cron дожидается, пока в каталоге появится «x» кадров.
После того как сценарий обнаружит, что в каталоге находится необхо
димое число кадров, он мог бы переместить этот каталог в другой ката
лог, где эти кадры будут подвергнуты обработке, или просто перемес
тить их на другой накопитель, достаточно быстрый, чтобы иметь воз
можность воспроизводить несжатый фильм с высоким разрешением.
Однако модуль shutil может не только копировать файлы, в нем также
имеются методы для перемещения и удаления деревьев данных. В при
мере 6.3 демонстрируется возможность перемещения нашего дерева,
а в примере 6.4 – возможность его удаления.
Пример 6.3. Перемещение дерева данных с помощью модуля shutil
In [20]: shutil.move("test copy", "test copy moved")
In [21]: ls lR
total 0
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test/
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test copy moved/
./test:
total 0
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test_subdir1/
./test/test_subdir1:

226 Глава 6. Данные
total 0
drwxr xr x 2 ngift wheel 68 Mar 31 22:27 test_subdir2/
./test/test_subdir1/test_subdir2:
./test copy moved:
total 0
drwxr xr x 3 ngift wheel 102 Mar 31 22:27 test_subdir1/
./test copy moved/test_subdir1:
total 0
drwxr xr x 2 ngift wheel 68 Mar 31 22:27 test_subdir2/
./test copy moved/test_subdir1/test_subdir2:
Пример 6.4. Удаление дерева данных с помощью модуля shutil
In [22]: shutil.rmtree("test copy moved")
In [23]: shutil.rmtree("test copy")
In [24]: ll
Перемещение дерева данных является более впечатляющей операци
ей, чем удаление, поскольку после удаления нам нечего демонстриро
вать. Многие из этих простых примеров можно было бы объединить
с другими действиями в более сложные сценарии. Одна из разновидно
стей сценариев, которая могла бы быть полезна на практике, – это сце
нарий резервного копирования, копирующий дерево каталогов на се
тевой диск и затем создающий архив, имя которого включает текущие
дату и время. К счастью, у нас имеется пример, реализующий на язы
ке Python именно эти действия, который приводится в разделе этой
главы, посвященном резервному копированию.
Работа с путями, каталогами и файлами
Невозможно говорить о работе с данными, не принимая во внимание
пути, каталоги и файлы. Любой системный администратор должен
уметь написать сценарий, который производит обход каталога, выпол
няет поиск по условию и затем какимнибудь образом обрабатывает
результат. Мы опишем некоторые интересные способы, позволяющие
это сделать.
Как всегда, все необходимые для выполнения задания инструменты
можно найти в стандартной библиотеке языка Python. Язык Python не
зря пользуется репутацией «батарейки входят в комплект поставки».
В примере 6.5 демонстрируется, как создать сценарий обхода катало
га, содержащий функции, которые явно возвращают файлы, каталоги
и пути.
Пример 6.5. Сценарий обхода каталога
import os
path = "/tmp"

Работа с путями, каталогами и файлами 227
def enumeratepaths(path=path):
"""Возвращает пути ко всем файлам в каталоге в виде списка"""
path_collection = []
for dirpath, dirnames, filenames in os.walk(path):
for file in filenames:
fullpath = os.path.join(dirpath, file)
path_collection.append(fullpath)
return path_collection
def enumeratefiles(path=path):
"""Возвращает имена всех файлов в каталоге в виде списка"""
file_collection = []
for dirpath, dirnames, filenames in os.walk(path):
for file in filenames:
file_collection.append(file)
return file_collection
def enumeratedir(path=path):
"""Возвращает имена всех подкаталогов в каталоге в виде списка"""
dir_collection = []
for dirpath, dirnames, filenames in os.walk(path):
for dir in dirnames:
dir_collection.append(dir)
return dir_collection
if __name__ == "__main__":
print "\nRecursive listing of all paths in a dir:"
for path in enumeratepaths():
print path
print "\nRecursive listing of all files in dir:"
for file in enumeratefiles():
print file
print "\nRecursive listing of all dirs in dir:"
for dir in enumeratedir():
print dir
На ноутбуке, работающем под управлением Mac OS, вывод этого сце
нария выглядит, как показано ниже:
[ngift@Macintosh 7][H:12022][J:0]# python enumarate_file_dir_path.py
Recursive listing of all paths in a dir:
/tmp/.aksusb
/tmp/ARD_ABJMMRT
/tmp/com.hp.launchport
/tmp/error.txt
/tmp/liten.py
/tmp/LitenDeplicationReport.csv
/tmp/ngift.liten.log
/tmp/hsperfdata_ngift/58920

228 Глава 6. Данные
/tmp/launch h36okI/Render
/tmp/launch qy1S9C/Listeners
/tmp/launch RTJzTw/:0
/tmp/launchd 150.wDvODl/sock
Recursive listing of all files in dir:
.aksusb
ARD_ABJMMRT
com.hp.launchport
error.txt
liten.py
LitenDeplicationReport.csv
ngift.liten.log
58920
Render
Listeners
:0
sock
Recursive listing of all dirs in dir:
.X11 unix
hsperfdata_ngift
launch h36okI
launch qy1S9C
launch RTJzTw
launchd 150.wDvODl
ssh YcE2t6PfnO
Небольшое примечание к предыдущему фрагменту программного ко
да: метод os.walk() возвращает объектгенератор, благодаря которому
вы сможете выполнить обход дерева каталогов самостоятельно:
In [2]: import os
In [3]: os.walk("/tmp")
Out[3]: [generator object at 0x508e18]
Вот как это выглядит при вызове метода в оболочке IPython. Вы може
те заметить, что наличие генератора дает нам возможность использо
вать его метод path.next(). Мы не будем углубляться в обсуждение ге
нераторов, но вы должны знать, что метод os.walk() возвращает объ
ектгенератор. Генераторы очень полезны для системного программи
рования. Все, что вам необходимо знать о генераторах, вы найдете на
сайте Дэвида Бизли (David Beazely) по адресу: http://www.dabeaz.com/
generators/.
In [2]: import os
In [3]: os.walk("/tmp")
Out[3]: [generator object at 0x508e18]
In [4]: path = os.walk("/tmp")
In [5]: path.

Работа с путями, каталогами и файлами 229
path.__class__ path.__init__ path.__repr__ path.gi_running
path.__delattr__ path.__iter__ path.__setattr__ path.next
path.__doc__ path.__new__ path.__str__ path.send
path.__getattribute__ path.__reduce__ path.close path.throw
path.__hash__ path.__reduce_ex__ path.gi_frame
In [5]: path.next()
Out[5]:
('/tmp',
['.X11 unix',
'hsperfdata_ngift',
'launch h36okI',
'launch qy1S9C',
'launch RTJzTw',
'launchd 150.wDvODl',
'ssh YcE2t6PfnO'],
['.aksusb',
'ARD_ABJMMRT',
'com.hp.launchport',
'error.txt',
'liten.py',
'LitenDeplicationReport.csv',
'ngift.liten.log'])
Вскоре мы познакомимся с генераторами поближе, но сначала созда
дим модуль, обладающий прозрачным прикладным интерфейсом, с по
мощью которого можно будет получать файлы, каталоги и пути.
Теперь, когда мы выполнили основную реализацию задачи обхода ка
талога, попробуем создать объектноориентированный модуль, чтобы
впоследствии его легко можно было импортировать и использовать.
Модуль с жестко заданными исходными данными получился бы коро
че, но универсальный модуль, который потом можно будет использо
вать в разных сценариях, облегчит нам жизнь гораздо существеннее.
Взгляните на пример 6.6.
Пример 6.6. Модуль многократного использования для обхода каталога
import os
class diskwalk(object):
"""Интерфейс доступа к коллекциям, получаемым при обходе каталога """
def __init__(self, path):
self.path = path
def enumeratePaths(self):
"""Возвращает пути ко всем файлам в каталоге в виде списка"""
path_collection = []
for dirpath, dirnames, filenames in os.walk(self.path):
for file in filenames:
fullpath = os.path.join(dirpath, file)
path_collection.append(fullpath)
return path_collection

230 Глава 6. Данные
def enumerateFiles(self):
"""Возвращает имена всех файлов в каталоге в виде списка"""
file_collection = []
for dirpath, dirnames, filenames in os.walk(self.path):
for file in filenames:
file_collection.append(file)
return file_collection
def enumerateDir(self):
"""Возвращает имена всех подкаталогов в каталоге в виде списка"""
dir_collection = []
for dirpath, dirnames, filenames in os.walk(self.path):
for dir in dirnames:
dir_collection.append(dir)
return dir_collection
Как видите, внесением небольших изменений нам удалось создать от
личный интерфейс для модификаций в будущем. Главная прелесть
этого нового модуля заключается в том, что его можно импортировать
в другие сценарии.
Сравнение данных
Сравнение данных – очень важная операция для системного админи
стратора. Вы часто могли задавать себе вопросы: «Какие файлы в этих
двух каталогах различны? Сколько копий одного и того же файла су
ществует у меня в системе?» В этом разделе вы найдете способы, кото
рые позволят вам ответить на эти и другие вопросы.
Когда приходится иметь дело с огромными объемами важных данных,
часто бывает необходимо сравнить деревья каталогов и файлов, чтобы
узнать, какие изменения были внесены. Это становится еще более
важным, когда дело доходит до создания сценариев, перемещающих
данные. Судный день будет вам гарантирован, если ваш сценарий пе
ремещения больших объемов данных повредит какиелибо критиче
ски важные данные.
В этом разделе мы сначала исследуем несколько легковесных методов
сравнения файлов и каталогов, а затем перейдем к вычислению и срав
нению контрольных сумм файлов. В стандартной библиотеке языка
Python имеется несколько модулей, которые помогут выполнить срав
нение; мы рассмотрим filecmp и os.listdir.
Использование модуля filecmp
Модуль filecmp содержит функции для быстрого и эффективного срав
нения файлов и каталогов. Модуль filecmp вызывает функцию os.stat()
для двух файлов и возвращает значение True, если результаты вызова
os.stat() одни и те же для обоих файлов, и False, если полученные ре

Сравнение данных 231
зультаты отличаются. Обычно функция os.stat() вызывается, чтобы
определить, не используют ли два файла одни и те же индексные узлы
на диске и не имеют ли они одинаковые размеры, но сравнение содер
жимого файлов при этом не производится.
Чтобы полностью понять, как работает модуль filecmp, нам потребуется
создать три файла. Для этого перейдем в каталог /tmp, создадим файл
с именем file0.txt и запишем в него «0». Затем создадим файл с именем
file1.txt и запишем в него «1». Наконец, создадим файл с именем
file00.txt и запишем в него «0». Эти файлы будут использоваться в ка
честве объектов сравнения в следующем фрагменте:
In [1]: import filecmp
In [2]: filecmp.cmp("file0.txt", "file1.txt")
Out[2]: False
In [3]: filecmp.cmp("file0.txt", "file00.txt")
Out[3]: True
Как видите, функция cmp() вернула значение True при сравнении фай
лов file0.txt и file00.txt, и False при сравнении файлов file1.txt
иfile0.txt.
Функция dircmp() имеет множество атрибутов, которые сообщают о раз
личиях между двумя деревьями каталогов. Мы не будем рассматри
вать каждый атрибут, но продемонстрируем несколько примеров вы
полнения действий, которые могут быть вам полезны. Для этого при
мера в каталоге /tmp были созданы два подкаталога, в каждый из ко
торых были скопированы файлы из предыдущего примера. В каталоге
dirB был создан дополнительный файл с именем file11.txt, в который
была записана строка «11»:
In [1]: import filecmp
In [2]: pwd
Out[2]: '/private/tmp'
In [3]: filecmp.dircmp("dirA", "dirB").diff_files
Out[3]: []
In [4]: filecmp.dircmp("dirA", "dirB").same_files
Out[4]: ['file1.txt', 'file00.txt', 'file0.txt']
In [5]: filecmp.dircmp("dirA", "dirB").report()
diff dirA dirB
Only in dirB : ['file11.txt']
Identical files : ['file0.txt', 'file00.txt', 'file1.txt']
Возможно, вас удивило, что атрибут diff_files не содержит ничего,
хотя мы создали файл file11.txt с уникальной информацией в нем. Де
ло в том, что атрибут diff_files выявляет различия только между од
ноименными файлами.

232 Глава 6. Данные
Затем взгляните на результат вывода атрибута same_files и обратите
внимание, что он сообщает об идентичных файлах в двух каталогах.
Наконец, в последнем примере был сгенерирован отчет. Он наглядно
сообщает о различиях между двумя файлами. Это был лишь очень
краткий обзор возможностей модуля filecmp, поэтому мы рекомендуем
обратиться к документации в стандартной библиотеке языка Python,
чтобы получить полное представление о имеющихся возможностях,
для описания которых мы не располагаем достаточным пространством
в книге.
Использование os.listdir
Еще один легковесный способ сравнения двух каталогов основан на
использовании метода os.listdir(). Метод os.listdir() можно пред
ставлять себе как аналог команды ls – он возвращает список обнару
женных файлов. Язык Python поддерживает множество интересных
способов работы со списками, поэтому вы можете использовать метод
os.listdir() для выявления различий между каталогами, просто пре
образуя списки во множества и затем вычитая одно множество из дру
гого. Ниже показано, как это делается в оболочке IPython:
In [1]: import os
In [2]: dirA = set(os.listdir("/tmp/dirA"))
In [3]: dirA
Out[3]: set(['file1.txt', 'file00.txt', 'file0.txt'])
In [4]: dirB = set(os.listdir("/tmp/dirB"))
In [5]: dirB
Out[5]: set(['file1.txt', 'file00.txt', 'file11.txt', 'file0.txt'])
In [6]: dirA dirB
Out[6]: set([])
In [7]: dirB dirA
Out[7]: set(['file11.txt'])
В этом примере можно видеть, что мы преобразовали два списка во
множества, а затем выполнили операцию вычитания, чтобы выявить
различия. Обратите внимание, что в строке [7] было получено имя
file11.txt, потому что dirB является надмножеством для dirA, но в стро
ке [6] был получен пустой результат, потому что множество dirA содер
жит элементы, которые содержатся в множестве dirB. При использова
нии множеств легко можно создать простое объединение двух струк
тур данных, вычитая полные пути в одном каталоге из путей в другом
каталоге, и копируя найденные различия. Объединение данных мы
рассмотрим в следующем разделе.
Однако этот подход имеет существенные ограничения. Фактическое
имя файла часто может вводить в заблуждение, поскольку ничто не ме

Объединение данных 233
шает иметь файл с нулевым размером, имя которого совпадает с име
нем файла, имеющим размер 200 Гбайт. В следующем разделе мы
представим несколько лучший способ обнаружения различий между
каталогами и объединения их содержимого.
Объединение данных
Как быть, когда необходимо не просто сравнить файлы с данными, но
еще и объединить два дерева каталогов в одно? Главная проблема со
стоит в том, чтобы объединить содержимое одного дерева с другим без
создания дубликатов файлов.
Вы могли бы просто вслепую скопировать файлы из одного каталога
в другой и затем удалить дубликаты файлов, но гораздо эффективнее
было бы вообще не создавать дубликаты. Достаточно простое решение
этой проблемы состоит в том, чтобы сравнить два каталога с помощью
функции dircmp() из модуля filecmp и затем скопировать уникальные
файлы с помощью приема, основанного на использовании os.listdir,
описанного выше. Наилучшее решение заключается в использовании
контрольных сумм MD5, о чем рассказывается в следующем разделе.
Сравнение контрольных сумм MD5
Вычисление контрольной суммы MD5 файла и сравнение ее с кон
трольной суммой другого файла напоминает стрельбу из гранатомета
по движущейся мишени. Такое мощное оружие вводится в действие,
когда требуется полная уверенность в своих действиях, хотя 100про
центную гарантию может дать только побайтовое сравнение файлов.
В примере 6.7 показана функция, которая принимает путь к файлу
и возвращает его контрольную сумму.
Пример 6.7. Вычисление контрольной суммы MD5 файла
import hashlib
def create_checksum(path):
"""
Читает файл. Вычисляет контрольную сумму файла, строку за строкой.
Возвращает полную контрольную сумму для всего файла.
"""
fp = open(path)
checksum = hashlib.md5()
while True:
buffer = fp.read(8192)
if not buffer: break
checksum.update(buffer)
fp.close()
checksum = checksum.digest()
return checksum

234 Глава 6. Данные
Ниже приводится пример использования этой функции в интерактив
ной оболочке IPython для сравнения двух файлов:
In [2]: from checksum import createChecksum
In [3]: if createChecksum("image1") == createChecksum("image2"):
...: print "True"
...:
...:
True
In [5]: if createChecksum("image1") == createChecksum("image_unique"):
print "True"
...:
...:
В этом примере контрольные суммы файлов сравниваются вручную, но
мы вполне можем использовать программный код, написанный ранее,
который возвращает список путей, для рекурсивного сравнивания де
рева каталогов и получить список дубликатов. Прелесть удобного API
состоит в том, что его теперь можно использовать в оболочке IPython
с целью тестирования наших решений в интерактивном режиме. За
тем, если решение работает, мы можем создать другой модуль. В при
мере 6.8 приводится программный код, который отыскивает дублика
ты файлов.
Пример 6.8. Вычисление контрольных сумм MD5 в дереве каталогов
с целью поиска дубликатов файлов
In [1]: from checksum import createChecksum
In [2]: from diskwalk_api import diskwalk
In [3]: d = diskwalk('/tmp/duplicates_directory')
In [4]: files = d.enumeratePaths()
In [5]: len(files)
Out[5]: 12
In [6]: dup = []
In [7]: record = {}
In [8]: for file in files:
compound_key = (getsize(file),create_checksum(file))
if compound_key in record:
dup.append(file)
else:
record[compound_key] = file
...:
...:
In [9]: print dup
['/tmp/duplicates_directory/image2']

Объединение данных 235
Фрагмент программного кода, который еще не встречался нам в пре
дыдущих примерах, начинается в строке [7]. Здесь мы создали пустой
словарь, и затем сохраняем вычисленные контрольные суммы в виде
ключей. Благодаря этому легко можно определить, была ли ранее вы
числена та или иная контрольная сумма. Если была, мы помещаем
файл в список дубликатов. Теперь давайте выделим часть программ
ного кода, которую позднее мы сможем использовать в разных сцена
риях. В конце концов, это очень удобно. Как это сделать, показано
в примере 6.9.
Пример 6.9. Поиск дубликатов
from checksum import create_checksum
from diskwalk_api import diskwalk
from os.path import getsize
def findDupes(path = '/tmp'):
dup = []
record = {}
d = diskwalk(path)
files = d.enumeratePaths()
for file in files:
compound_key = (getsize(file),create_checksum(file))
if compound_key in record:
dup.append(file)
else:
#print "Creating compound key record:", compound_key
record[compound_key] = file
return dup
if __name__ == "__main__":
dupes = findDupes()
for dup in dupes:
print "Duplicate: %s" % dup
Запустив этот сценарий, мы получили следующий результат:
[ngift@Macintosh 7][H:10157][J:0]# python find_dupes.py
Duplicate: /tmp/duplicates_directory/image2
Мы надеемся, вы заметили, что этот пример демонстрирует преиму
щества повторного использования существующего программного ко
да. Теперь у нас имеется универсальный модуль, получающий путь
к дереву каталогов и возвращающий список дубликатов файлов. Это
уже само по себе удобно, но мы можем пойти еще дальше и автомати
чески удалить дубликаты.
Удаление файлов в языке выполняется очень просто – с помощью ме
тода os.remove(). Для этого примера у нас имеется множество файлов
размером 10 Мбайт в нашем каталоге /tmp. Попробуем удалить один
из них, воспользовавшись методом os.remove():

236 Глава 6. Данные
In [1]: import os
In [2]: os.remove("10
10mbfile.0 10mbfile.1 10mbfile.2 10mbfile.3 10mbfile.4
10mbfile.5 10mbfile.6 10mbfile.7 10mbfile.8
In [2]: os.remove("10mbfile.1")
In [3]: os.remove("10
10mbfile.0 10mbfile.2 10mbfile.3 10mbfile.4 10mbfile.5
10mbfile.6 10mbfile.7 10mbfile.8
Обратите внимание, как функция дополнения по клавише табуляции
в оболочке IPython позволяет увидеть список соответствующих фай
лов. Вы должны знать, что метод os.remove() удаляет файлы, ничего не
сообщая и навсегда, что может не всегда соответствовать нашим жела
ниям. Учитывая это обстоятельство, мы можем реализовать простой
метод, который будет удалять дубликаты, и затем расширить его. По
скольку интерактивная оболочка IPython позволяет легко проверить
эту идею, мы напишем проверочную функцию прямо в ней и сразу же
проверим ее:
In [1]: from find_dupes import findDupes
In [2]: dupes = findDupes("/tmp")
In [3]: def delete(file):
import os
...: print "deleting %s" % file
...: os.remove(file)
...:
...:
In [4]: for dupe in dupes:
...: delete(dupe)
...:
...:
In [5]: for dupe in dupes:
delete(dupe)
...:
...:
deleting /tmp/10mbfile.2
deleting /tmp/10mbfile.3
deleting /tmp/10mbfile.4
deleting /tmp/10mbfile.5
deleting /tmp/10mbfile.6
deleting /tmp/10mbfile.7
deleting /tmp/10mbfile.8
В этом примере мы несколько усложнили свой метод удаления, доба
вив в него инструкцию print, которая выводит имена удаляемых фай
лов. Мы уже создали достаточно много программного кода, пригодного
для многократного использования, поэтому у нас нет никаких причин

Объединение данных 237
останавливаться на достигнутом. Мы можем создать еще один модуль,
который будет выполнять различные операции удаления, получая объ
ект типа file. Этот модуль даже не требуется привязывать к поиску
дубликатов, его можно использовать для удаления любых файлов. Ис
ходный текст модуля приводится в примере 6.10.
Пример 6.10. Модуль delete
#!/usr/bin/env python
import os
class Delete(object):
"""Методы удаления, работающие с объектами типа file"""
def __init__(self, file):
self.file = file
def interactive(self):
"""Интерактивный режим удаления"""
input = raw_input("Do you really want to delete %s [N]/Y" % self.file)
if input.upper() == "Y":
print "DELETING: %s" % self.file
status = os.remove(self.file)
else:
print "Skipping: %s" % self.file
return
def dryrun(self):
"""Имитация удаления"""
print "Dry Run: %s [NOT DELETED]" % self.file
return
def delete(self):
"""Удаляет файл без дополнительных условий"""
print "DELETING: %s" % self.file
try:
status = os.remove(self.file)
except Exception, err:
print err
return status
if __name__ == "__main__":
from find_dupes import findDupes
dupes = findDupes('/tmp')
for dupe in dupes:
delete = Delete(dupe)
#delete.dryrun()
#delete.delete()
#delete.interactive()
В этом модуле имеется три различных метода удаления. Метод удале
ния в интерактивном режиме запрашивает у пользователя подтверж

238 Глава 6. Данные
дение для каждого файла, который предполагается удалить. Это может
показаться раздражающим, но этот метод обеспечивает хорошую за
щиту для тех, кто впоследствии будет сопровождать или изменять этот
программный код.
Метод пробного режима всего лишь имитирует удаление. И, наконец,
имеется метод, который удаляет файлы безвозвратно. В конце модуля
можно увидеть закомментированные варианты использования каждо
го из трех методов. Ниже приводятся примеры каждого из методов
в действии:
•Пробный режим
ngift@Macintosh 7][H:10197][J:0]# python delete.py
Dry Run: /tmp/10mbfile.1 [NOT DELETED]
Dry Run: /tmp/10mbfile.2 [NOT DELETED]
Dry Run: /tmp/10mbfile.3 [NOT DELETED]
Dry Run: /tmp/10mbfile.4 [NOT DELETED]
Dry Run: /tmp/10mbfile.5 [NOT DELETED]
Dry Run: /tmp/10mbfile.6 [NOT DELETED]
Dry Run: /tmp/10mbfile.7 [NOT DELETED]
Dry Run: /tmp/10mbfile.8 [NOT DELETED]
•Интерактивный режим
ngift@Macintosh 7][H:10201][J:0]# python delete.py
Do you really want to delete /tmp/10mbfile.1 [N]/YY
DELETING: /tmp/10mbfile.1
Do you really want to delete /tmp/10mbfile.2 [N]/Y
Skipping: /tmp/10mbfile.2
Do you really want to delete /tmp/10mbfile.3 [N]/Y
•Удаление
[ngift@Macintosh 7][H:10203][J:0]# python delete.py
DELETING: /tmp/10mbfile.1
DELETING: /tmp/10mbfile.2
DELETING: /tmp/10mbfile.3
DELETING: /tmp/10mbfile.4
DELETING: /tmp/10mbfile.5
DELETING: /tmp/10mbfile.6
DELETING: /tmp/10mbfile.7
DELETING: /tmp/10mbfile.8
Вы можете согласиться, что приемы инкапсуляции, подобные тем, что
были продемонстрированы выше, очень удобны, когда приходится
иметь дело с данными, потому что вы можете предотвратить возникно
вение проблем в будущем, абстрагировавшись от конкретной ситуа
ции и решая универсальную задачу. В данном случае нам необходимо
было реализовать удаление дубликатов файлов, поэтому был создан
модуль, который универсальным способом отыскивает файлы и удаля
ет их. Мы могли бы создать еще один инструмент, который получает

Поиск файлов и каталогов по шаблону 239
объект типа file и выполняет сжатие файла. И мы действительно
вскоре подойдем к этому примеру.
Поиск файлов и каталогов по шаблону
До сих пор мы рассматривали способы обработки каталогов и файлов
и такие действия, как поиск дубликатов, удаление каталогов, переме
щение каталогов и так далее. Следующий шаг в освоении дерева ката
логов состоит в применении поиска по шаблону либо как самостоя
тельной операции, либо в комбинации с предыдущими приемами. Как
и все прочее в языке Python, реализация поиска по шаблону расшире
ния или имени файла выполняется очень просто. В этом разделе мы
продемонстрируем несколько общих проблем, связанных с поиском по
шаблону, и применим приемы, использовавшиеся ранее, для создания
простых, но мощных инструментов.
Очень часто системным администраторам приходится сталкиваться
с необходимостью отыскать и удалить, переместить, переименовать или
скопировать файлы определенных типов. Самый простой подход к ре
шению этой задачи в языке Python заключается в использовании моду
ля fnmatch или glob. Основное отличие между этими двумя модулями
заключается в том, что функция fnmatch() при сопоставлении имени
файла с шаблоном UNIX возвращает значение True или False, а функ
ция glob() возвращает список путей к файлам, имена которых соответ
ствуют шаблону. Для создания более сложных инструментов поиска по
шаблону можно использовать регулярные выражения. Об использова
нии регулярных выражений более подробно рассказывается в главе 3.
В примере 6.11 показано, как используются функции fnmatch() и glob().
Здесь мы снова повторно использовали программный код, созданный
нами ранее, импортировав класс diskwalk из модуля diskwalk_api.
Пример 6.11. Использование функций fnmatch() и glob()
в интерактивном режиме для поиска файлов
In [1]: from diskwalk_api import diskwalk
In [2]: files = diskwalk("/tmp")
In [3]: from fnmatch import fnmatch
In [4]: for file in files:
...: if fnmatch(file,"*.txt"):
...: print file
...:
...:
/tmp/file.txt
In [5]: from glob import glob
In [6]: import os

240 Глава 6. Данные
In [7]: os.chdir("/tmp")
In [8]: glob("*")
Out[8]: ['file.txt', 'image.iso', 'music.mp3']
В этом примере, после того как мы воспользовались нашим модулем
diskwalk_api, у нас появился список полных путей к файлам, находя
щимся в каталоге /tmp. После этого мы использовали функцию
fnmatch(), чтобы определить соответствие каждого файла шаблону
"*.txt". Функция glob() отличается тем, что она сразу выполняет со
поставление с шаблоном и возвращает список имен файлов. Функция
glob() является более высокоуровневой по отношению к функции
fnmatch(), но обе они являются незаменимыми инструментами при ре
шении немного разных задач.
Функцию fnmatch() особенно удобно использовать в комбинации с про
граммным кодом, создающим фильтр для поиска данных в дереве ка
талогов. Часто при работе с каталогами бывает необходимо работать
с файлами, имена которых соответствуют определенным шаблонам.
Чтобы увидеть этот прием в действии, мы попробуем решить классиче
скую задачу системного администрирования по переименованию всех
файлов в дереве каталогов, имена которых соответствуют заданному
шаблону. Имейте в виду, что переименовывать файлы так же просто,
как удалять, сжимать или обрабатывать их. Для решения подобных
задач используется простой алгоритм:
1. Получить путь к файлу в каталоге.
2. Выполнить дополнительную фильтрацию – в эту операцию может
быть вовлечено несколько фильтров, таких как имя файла, расши
рение, размер, уникальность и так далее.
3. Выполнить действие над файлом – скопировать, удалить, сжать,
прочитать и так далее. Как это делается, показано в примере 6.12.
Пример 6.12. Переименование файлов с расширением .mp3 в файлы
с расширением .txt
In [1]: from diskwalk_api import diskwalk
In [2]: from shutil import move
In [3]: from fnmatch import fnmatch
In [4]: files = diskwalk("/tmp")
In [5]: for file in files:
if fnmatch(file, "*.mp3"):
#здесь можно сделать все, что угодно: удалить, переместить
#переименовать ...хм м, переименовать
move(file, "%s.txt" % file)

In [6]: ls l /tmp/
total 0
rw r r 1 ngift wheel 0 Apr 1 21:50 file.txt

Обертка для rsync 241
rw r r 1 ngift wheel 0 Apr 1 21:50 image.iso
rw r r 1 ngift wheel 0 Apr 1 21:50 music.mp3.txt
rw r r 1 ngift wheel 0 Apr 1 22:45 music1.mp3.txt
rw r r 1 ngift wheel 0 Apr 1 22:45 music2.mp3.txt
rw r r 1 ngift wheel 0 Apr 1 22:45 music3.mp3.txt
При использовании программного кода, разработанного ранее, пере
именование всех файлов с расширением .mp3 в каталоге уложилось
в четыре строки легко читаемого программного кода на языке Python.
Если вы один из немногих системных администраторов, кто не прочи
тал ни одного эпизода из «BOFH» («Bastard Operator From Hell»), то
вам не сразу станет очевидно, что можно было бы дальше сделать
сэтим фрагментом кода.
Представьте, что у вас имеется технологический файловый сервер, ко
торый используется исключительно как высокопроизводительное
хранилище файлов с далеко не безграничной емкостью. Вы стали за
мечать, что диски сервера стали часто переполняться, потому что па
рочка недобросовестных пользователей принялись размещать на них
сотни гигабайтов файлов MP3. Конечно, вы могли бы ввести квотиро
вание дискового пространства для пользователей, но нередко квотиро
вание порождает больше проблем, чем решает. Одно из решений состо
ит в том, чтобы написать сценарий для запуска его из планировщика
cron каждую ночь, который будет отыскивать файлы MP3 и выпол
нять над ними «случайные» операции. По понедельникам он мог бы
давать этим файлам расширение .txt, по вторникам – сжимать их
в ZIPархивы, по средам – перемещать в каталог /tmp, по четвергам –
удалять их и отсылать владельцу полный список удаленных файлов
MP3 по электронной почте. Мы не можем советовать вам сделать это,
если, конечно, вы не являетесь владельцем компании, на которую ра
ботаете, но для настоящего «чертова ублюдка оператора» этот пример
можно считать воплощением мечты.
Обертка для rsync
Как вы уже, наверное, знаете, rsync – это инструмент командной стро
ки, первоначально разрабатывавшийся Эндрю Триджеллом (Andrew
Tridgell) и Полом Маккерра (Paul Mackerra). В конце 2007 года стала
доступна для тестирования версия 3, включающая еще более широкий
перечень параметров, чем оригинальная версия.
За эти годы для нас rsync превратился в основной инструмент переме
щения данных из пункта А в пункт Б. Объем страницы справочного
руководства и количество возможных параметров просто поражают,
поэтому мы рекомендуем познакомиться с ними поближе. Без преуве
личения утилиту rsync можно считать уникальным, наиболее полез
ным инструментом командной строки, из всех, что когдалибо созда
вались для системных администраторов.

242 Глава 6. Данные
К этому стоит добавить, что язык Python предоставляет несколько спо
собов управления поведением rsync. Одна из проблем, с которой мы
столкнулись, состояла в том, чтобы обеспечить копирование данных
в запланированное время. Мы не раз попадали в ситуации, когда было
необходимо синхронизировать терабайты данных между двумя файло
выми серверами настолько быстро, насколько это возможно, но мы со
всем не хотели контролировать этот процесс вручную. Это как раз та си
туация, в которой Python действительно может сыграть значимую
роль.
С помощью языка Python можно придать утилите rsync немного искус
ственного интеллекта и настроить ее под свои нужды. В такой ситуа
ции сценарий на языке Python используется как связующий про
граммный код, который заставляет утилиты UNIX выполнять такие
вещи, для которых они никогда не предназначались, и благодаря это
му вы получаете возможность создавать очень гибкие и легко настраи
ваемые инструменты. Вы здесь действительно ограничены только ва
шим воображением. В примере 6.13. приводится очень простой сцена
рий, который представляет собой обертку для rsync.
Пример 6.13. Простая обертка для rsync
#!/usr/bin/env python
#обертка вокруг rsync для синхронизации содержимого двух каталогов
from subprocess import call
import sys
source = "/tmp/sync_dir_A/" #Обратите внимание на завершающий символ слеша
target = "/tmp/sync_dir_B"
rsync = "rsync"
arguments = " a"
cmd = "%s %s %s %s" % (rsync, arguments, source, target)
def sync():
ret = call(cmd, shell=True)
if ret != 0:
print "rsync failed"
sys.exit(1)
sync()
Этот пример жестко определяет синхронизацию двух каталогов и вы
водит сообщение об ошибке, если команда не сработала. Однако мы
могли бы реализовать нечто более интересное и решить проблему, с ко
торой часто приходится сталкиваться. Нас часто вызывали, чтобы
синхронизировать два очень больших каталога, но мы при этом не со
бирались следить за синхронизацией данных всю ночь. Но, если вы не
контролируете процесс синхронизации, вы можете обнаружить, что
процесс был прерван на полпути, при этом данные и целая ночь време
ни были потрачены впустую, а сам процесс синхронизации придется

Обертка для rsync 243
опять запускать на следующий день. Используя Python, вы можете
создать более агрессивную, высокомотивированную команду rsync.
Что могла бы делать высокомотивированная команда rsync? Она могла
бы делать то же самое, что и вы, если бы контролировали процесс син
хронизации двух каталогов: она могла бы пытаться продолжать син
хронизацию до самого конца и затем посылала бы сообщение по элек
тронной почте с описанием того, что было сделано. В примере 6.14
приводится немного более продвинутый сценарийобертка для rsync.
Пример 6.14. Команда rsync, которая не завершается,
пока не выполнит задание
#!/usr/bin/env python
#обертка вокруг rsync для синхронизации содержимого двух каталогов
from subprocess import call
import sys
import time
"""эта мотивированная команда rsync будет пытаться синхронизировать
каталоги, пока не синхронизирует их"""
source = "/tmp/sync_dir_A/" #Note the trailing slash
target = "/tmp/sync_dir_B"
rsync = "rsync"
arguments = " av"
cmd = "%s %s %s %s" % (rsync, arguments, source, target)
def sync():
while True:
ret = call(cmd, shell=True)
if ret !=0:
print "resubmitting rsync"
time.sleep(30)
else:
print "rsync was succesful"
subprocess.call("mail s 'jobs done' bofh@example.com",
shell=True)
sys.exit(0)
sync()
Этот сценарий максимально упрощен и содержит жестко определен
ные данные, но это – пример полезного инструмента, который можно
создать для автоматизации чегото, что вам обычно приходится кон
тролировать вручную. В этот сценарий можно добавить такие особен
ности, как возможность устанавливать интервал между попытками
и ограничивать количество попыток, проверять объем свободного дис
кового пространства на машине, с которой устанавливается соедине
ние, и так далее.

244 Глава 6. Данные
Метаданные: данные о данных
Большинство системных администраторов начинают вникать в суть де
ла, когда принимаются интересоваться не только данными, но и дан
ными о данных. Метаданные, или данные о данных, часто могут иг
рать более важную роль, чем сами данные. Например, в кинопроиз
водстве и в телевидении одни и те же данные часто хранятся в несколь
ких каталогах внутри файловой системы или даже в разных файловых
системах. Слежение за такими данными часто приводит к созданию
своего рода системы управления метаданными.
Метаданные – это данные о том, как организованы и как используют
ся определенные файлы, что может быть очень важно для приложе
ния, для процесса производства мультипликационного фильма или
для процедуры восстановления из резервной копии. Python также
сможет помочь в этом, поскольку на этом языке легко можно реализо
вать как чтение, так и запись метаданных.
Рассмотрим использование популярного средства ORM (ObjectRelati
onal Mapping – объектнореляционная проекция) SQLAlchemy для соз
дания метаданных о файловой системе. К счастью, для SQLAlchemy
имеется очень качественная документация, а кроме того, этот продукт
работает с SQLite. На наш взгляд, это потрясающая комбинация, по
зволяющая разрабатывать собственные решения по управлению мета
данными.
В примерах выше мы выполняли обход файловой системы в режиме
реального времени, производили запросы и выполняли действия над
обнаруженными файлами. Это невероятно удобно, но поиск по круп
ным файловым системам, содержащим миллионы файлов, отнимет
слишком много времени иногда только для того, чтобы выполнить
единственную операцию. В примере 6.15 мы покажем, на что могут
быть похожи самые простые метаданные, объединив приемы обхода
каталогов со средством ORM.
Пример 6.15. Создание метаданных о файловой системе
с помощью SQLAlchemy
#!/usr/bin/env python
from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
from sqlalchemy.orm import mapper, sessionmaker
import os
#путь
path = " /tmp"
#Часть 1: Создание механизма
engine = create_engine('sqlite:///:memory:', echo=False)
#Часть 2: метаданные
metadata = MetaData()

Метаданные: данные о данных 245
filesystem_table = Table('filesystem', metadata,
Column('id', Integer, primary_key=True),
Column('path', String(500)),
Column('file', String(255)),
)
metadata.create_all(engine)
#Часть 3: класс отображения
class Filesystem(object):
def __init__(self, path, file):
self.path = path
self.file = file
def __repr__(self):
return "[Filesystem('%s','%s')]" % (self.path, self.file)
#Часть 4: функция отображения
mapper(Filesystem, filesystem_table)
#Часть 5: создание сеанса
Session = sessionmaker(bind=engine, autoflush=True, transactional=True)
session = Session()
#Часть 6: обход файловой системы и заполнение базы данных результатами
for dirpath, dirnames, filenames in os.walk(path):
for file in filenames:
fullpath = os.path.join(dirpath, file)
record = Filesystem(fullpath, file)
session.save(record)
#Часть 7: подтверждение записи данных в базу
session.commit()
#Часть 8: запрос
for record in session.query(Filesystem):
print "Database Record Number: %s, Path: %s , File: %s " \
% (record.id,record.path, record.file)
Этот сценарий проще представлять себе как последовательность проце
дур, выполняемых одна за другой. В первой части создается механизм,
который в действительности является лишь несколько необычным
способом определения базы данных, которая будет использоваться для
хранения метаданных. Во второй части определяется экземпляр клас
са метаданных и создается таблица в базе данных. В третьей части оп
ределяется класс, который будет отображаться в только что созданную
таблицу базы данных. В четвертой части вызывается функция отобра
жения, которая производит объектнореляционную проекцию, а по
просту – отображает класс в таблицу. В пятой части создается сеанс
связи с базой данных. Обратите внимание, что здесь указано несколь
ко именованных аргументов, включая autoflush и transactional.
Теперь, когда создание объектнореляционной проекции закончено,
в шестой части мы выполняем уже знакомые нам действия – извлекаем

246 Глава 6. Данные
имена файлов и полные пути при обходе дерева каталогов. Однако здесь
имеется пара интересных приемов. Обратите внимание, что для каждо
го пути и имени файла создается отдельная запись, которая затем со
храняется в базе данных. После этого – в седьмой части – мы подтвер
ждаем транзакцию в нашей базе данных, «расположенной в памяти».
Наконец, в восьмой части выполняется запрос – на языке Python, ко
нечно, возвращающий записи, которые мы поместили в базу данных.
Этот пример мог бы стать для вас прекрасной возможностью поэкспе
риментировать в создании собственных решений использования мета
данных с применением SQLAlchemy в вашей компании или у клиен
тов. Этот пример можно расширить такими дополнительными воз
можностями, как выполнение реляционных запросов или запись ре
зультатов в файл и так далее.
Архивирование, сжатие, отображение
и восстановление
Действия с большими объемами данных представляют собой пробле
му, с которой системные администраторы сталкиваются изо дня
в день. Для выполнения своей работы они часто используют tar, dd,
gzip, bzip, bzip2, hdiutil, asr и другие утилиты.
Хотите верьте, хотите нет, но и в этом случае «батарейки входят в ком
плект поставки» – стандартная библиотека языка Python имеет встро
енную поддержку TARфайлов, zlibфайлов и gzipфайлов. Если вам
требуется сжатие и архивирование, значит, у вас не будет никаких про
блем, т. к. язык Python предлагает богатый выбор необходимых инст
рументов. Давайте поближе посмотрим на дедушку всех архиваторов –
tar – и увидим, как стандартная библиотека реализует его поддержку.
Использование модуля tarfile для создания
архивов TAR
Создать архив TAR очень просто, даже слишком просто. В примере 6.16
мы создаем очень большой файл. Обратите внимание, что синтаксис
создания архива намного более простой, чем даже синтаксис использо
вания самой команды tar.
Пример 6.16. Создание большого текстового файла
In [1]: f = open("largeFile.txt", "w")
In [2]: statement = "This is a big line that I intend to write over and over
again."
ln [3]: x = 0
In [4]: for x in xrange(20000):
...: x += 1

Использование модуля tarfile для создания архивов TAR 247
...: f.write("%s\n" % statement)
...:
...:
In [4]: ls l
rw r r 1 root root 1236992 Oct 25 23:13 largeFile.txt
Теперь, когда у нас имеется большой файл, наполненный мусором, пе
редадим его архиватору TAR, как показано в примере 6.17.
Пример 6.17. Архивирование содержимого файла
In [1]: import tarfile
In [2]: tar = tarfile.open("largefile.tar", "w")
In [3]: tar.add("largeFile.txt")
In [4]: tar.close()
In [5]: ll
rw r r 1 root root 1236992 Oct 25 23:15 largeFile.txt
rw r r 1 root root 1236992 Oct 26 00:39 largefile.tar
Как видите, был создан обычный архив TAR, причем намного более
простым способом, чем с использованием команды tar. Этот пример оп
ределенно создает прецедент к использованию оболочки IPython для
выполнения повседневной работы по системному администрированию.
Несмотря на удобство создания архивов TAR с помощью Python, тем
не менее, практически бесполезно упаковывать в архив одинединст
венный файл. Используя тот же самый прием обхода каталогов, кото
рый мы уже столько раз применяли в этой главе, можно упаковать
вархив TAR весь каталог /tmp, для чего достаточно выполнить обход
дерева каталогов и добавить в архив каждый файл, находящийся в ка
талоге /tmp, как показано в примере 6.18.
Пример 6.18. Архивирование содержимого дерева каталогов
In [27]: import tarfile
In [28]: tar = tarfile.open("temp.tar", "w")
In [29]: import os
In [30]: for root, dir, files in os.walk("/tmp"):
....: for file in filenames:
....:
KeyboardInterrupt
In [30]: for root, dir, files in os.walk("/tmp"):
....: for file in files:
....: fullpath = os.path.join(root,file)
....: tar.add(fullpath)
....:
....:
In [33]: tar.close()

248 Глава 6. Данные
В том, чтобы добавить в архив содержимое дерева каталогов при его
обходе, нет ничего сложного, и это очень неплохой прием, потому что
его можно объединить с другими приемами, рассматривавшимися
в этой главе. Представьте, что вы архивируете каталог, заполненный
мультимедийными файлами. Было бы неразумно архивировать дубли
каты, поэтому у вас вполне может появиться желание перед архивиро
ванием заменить дубликаты символическими ссылками. Обладая зна
ниями, полученными в этой главе, вы легко сможете написать сцена
рий, который сделает это и сэкономит вам немного дискового про
странства.
Поскольку создание простых архивов TAR – занятие довольно скуч
ное, давайте приправим его сжатием bzip2, что заставит ваш процес
сор скулить и жаловаться на то, как много выпало работы на его долю.
Алгоритм сжатия bzip2 иногда может оказаться отличной штукой.
Посмотрим, насколько впечатляющим он действительно может быть.
Создадим текстовый файл размером 60 Мбайт и сожмем его до 10 Кбайт,
как показано в примере 6.19!
Пример 6.19. Создание архива TAR, сжатого по алгоритму bzip2
In [1: tar = tarfile.open("largefilecompressed.tar.bzip2", "w|bz2")
In [2]: tar.add("largeFile.txt")
In [3]: ls h
foo1.txt fooDir1/ largeFile.txt largefilecompressed.tar.bzip2*
foo2.txt fooDir2/ largefile.tar
ln [4]: tar.close()
In [5]: ls lh
rw r r 1 root root 61M Oct 25 23:15 largeFile.txt
rw r r 1 root root 61M Oct 26 00:39 largefile.tar
rwxr xr x 1 root root 10K Oct 26 01:02 largefilecompressed.tar.bzip2*
Что самое удивительное, алгоритму bzip2 удалось сжать текстовый
файл размером 61 Мбайт в 10 Кбайт, хотя мы и смошенничали, ис
пользуя одни и те же данные снова и снова. Конечно, этот эффект был
получен далеко не бесплатно, потому что в системе на базе двухъядер
ного процессора AMD на это потребовалось несколько минут.
Теперь попробуем двинуться дальше и создать сжатый архив другими
доступными способами, начав с gzip. Синтаксис при этом меняется
весьма незначительно, как показано в примере 6.20.
Пример 6.20. Создание архива TAR, сжатого по алгоритму gzip
In [10]: tar = tarfile.open("largefile.tar.gzip", "w|gz")
In [11]: tar.add("largeFile.txt")
ln [12]: tar.close()

Использование модуля tarfile для проверки содержимого файлов TAR 249
In [13]: ls lh
rw r r 1 root root 61M Oct 26 01:20 largeFile.txt
rw r r 1 root root 61M Oct 26 00:39 largefile.tar
rwxr xr x 1 root root 160K Oct 26 01:24 largefile.tar.gzip*
Архив gzip тоже отличается невероятно маленьким размером, уме
стившись в 160 Кбайт, причем на моей машине сжатый архив TAR
был создан за несколько секунд. В большинстве ситуаций это непло
хой компромисс.
Использование модуля tarfile для проверки
содержимого файлов TAR
Теперь, когда у нас имеется инструмент создания файлов TAR, есть
смысл попробовать проверить содержимое файлов TAR. Создать файл
TAR – это лишь полдела. Если вы проработали системным админист
ратором достаточно продолжительное время, вам, вероятно, случалось
«погореть» с некачественной резервной копией или случалось быть об
виненным в создании некачественной резервной копии.
Чтобы воспроизвести эту ситуацию и подчеркнуть важность проверки
архивов TAR, мы поделимся историей о нашем вымышленном друге,
которую назовем «Проблема пропавшего архива TAR». Имена, назва
ния и факты являются вымышленными. Любые совпадения с действи
тельностью являются случайными.
Наш друг работал в крупной телестудии в качестве системного адми
нистратора и отвечал за поддержку отдела, во главе которого стоял по
настоящему невыдержанный человек. У этого руководителя была ре
путация неправдивого, импульсивного и невыдержанного человека.
Если возникала ситуация, когда этот сумасшедший совершал промах,
например не укладывался в оговоренные с клиентом сроки или выпол
нял свою часть программы не в соответствии с требуемыми характери
стиками, он с большим удовольствием лгал и перекладывал ответст
венность на когонибудь другого. Зачастую этим кемнибудь другим
оказывался наш друг, системный администратор.
К сожалению, наш друг отвечал за содержание резервных копий этого
сумасшедшего. Ему уже стало казаться, что настало время подыски
вать другую работу, но он работал в этой студии уже много лет, у него
было много друзей, и он не хотел потерять все изза этих временных
неурядиц. Ему требовалась система, позволяющая убедиться, что он
охватил резервированием все данные, и поэтому он ввел регистраци
онную систему, которая классифицировала содержимое всех архивов
TAR, которые автоматически создавались для этого сумасшедшего,
так как понимал, что может «погореть», и это лишь вопрос времени,
когда сумасшедший опять не уложится в сроки и ему потребуется при
чина для оправдания.

250 Глава 6. Данные
Однажды нашему другу Вильяму позвонил начальник и сказал:
«Вильям, зайдите ко мне немедленно, у нас неприятности с резервны
ми копиями». Вильям сразу же пошел к начальнику и узнал, что этот
сумасшедший, Алекс, обвинил Вильяма в повреждении архива со
съемкой телешоу, изза чего произошла задержка с передачей про
граммы клиенту. Срыв Алексом сроков сдачи совершенно вывел Боба,
начальника Алекса, из себя.
Начальник сказал Вильяму, что, по словам Алекса, резервная копия
содержала только поврежденные файлы и что изза этого были сорва
ны сроки подготовки шоу. В ответ Вильям сказал боссу, что был уве
рен в том, что рано или поздно его обвинят в порче архива и поэтому
втайне написал на языке Python сценарий, который проверяет содер
жимое всех создаваемых им архивов TAR и записывает расширенные
сведения об атрибутах файлов до и после резервного копирования.
Оказалось, что Алекс так и не приступал к работе над шоу и что в тече
ние нескольких месяцев архивировалась пустая папка.
Когда Алекс был поставлен перед фактами, он быстро пошел на попят
ную и попытался перевести внимание на другую проблему. К несча
стью для Алекса, этот случай стал последней каплей и пару месяцев
спустя он исчез с работы. Возможно, он уехал или был уволен, но это
уже не важно, наш друг успешно решил проблему пропавшего архива
TAR.
Мораль этой истории заключается в том, что, когда приходится иметь
дело с резервными копиями, с ними следует обращаться как с ядер
ным оружием, так как резервные копии могут хранить в себе такие
опасности, о которых вы даже не подозреваете.
Ниже демонстрируется несколько способов проверки содержимого
файла TAR, созданного ранее:
In [1]: import tarfile
In [2]: tar = tarfile.open("temp.tar","r")
In [3]: tar.list()
rw r r ngift/wheel 2 2008 04 04 15:17:14 tmp/file00.txt
rw r r ngift/wheel 2 2008 04 04 15:15:39 tmp/file1.txt
rw r r ngift/wheel 0 2008 04 04 20:50:57 tmp/temp.tar
rw r r ngift/wheel 2 2008 04 04 16:19:07 tmp/dirA/file0.txt
rw r r ngift/wheel 2 2008 04 04 16:19:07 tmp/dirA/file00.txt
rw r r ngift/wheel 2 2008 04 04 16:19:07 tmp/dirA/file1.txt
rw r r ngift/wheel 2 2008 04 04 16:19:52 tmp/dirB/file0.txt
rw r r ngift/wheel 2 2008 04 04 16:19:52 tmp/dirB/file00.txt
rw r r ngift/wheel 2 2008 04 04 16:19:52 tmp/dirB/file1.txt
rw r r ngift/wheel 3 2008 04 04 16:21:50 tmp/dirB/file11.txt
In [4]: tar.name
Out[4]: '/private/tmp/temp.tar'
In [5]: tar.getnames()

Использование модуля tarfile для проверки содержимого файлов TAR 251
Out[5]:
['tmp/file00.txt',
'tmp/file1.txt',
'tmp/temp.tar',
'tmp/dirA/file0.txt',
'tmp/dirA/file00.txt',
'tmp/dirA/file1.txt',
'tmp/dirB/file0.txt',
'tmp/dirB/file00.txt',
'tmp/dirB/file1.txt',
'tmp/dirB/file11.txt']
In [10]: tar.members
Out[10]:
[,
,
,
,
,
,
,
,
,
]
Эти примеры показывают, как получить имена файлов, хранящиеся
в архиве TAR, чтобы впоследствии иметь возможность их проанализи
ровать в сценарии, проверяющем данные. Извлечение файлов из архи
вов выполняется ничуть не сложнее. Если вам потребуется извлечь все
файлы из архива TAR в текущий рабочий каталог, можно воспользо
ваться следующей функцией:
In [60]: tar.extractall()
drwxrwxrwx 7 ngift wheel 238 Apr 4 22:59 tmp/
Если вы чрезвычайно подозрительны, каковыми и должны быть, то
вы могли бы реализовать подсчет контрольных сумм MD5 случайных
файлов при извлечении их из архива и сравнивать их с соответствую
щими контрольными суммами, которые были сохранены до упаковки
файлов в архив. Это очень эффективный способ убедиться в том, что
целостность данных не нарушена.
Ни одно разумное решение не должно основываться на предположе
нии, что архив был создан без ошибок. По крайней мере, хотя бы выбо
рочная проверка архивов должна выполняться автоматически. Но
лучше, если сразу после создания каждый архив будет открываться
и проверяться.

7
SNMP
Введение
Протокол SNMP может изменить вашу жизнь системного администра
тора. Отдача от использования SNMP ощущается не так скоро, как от
нескольких строк программного кода на языке Python, выполняющих
анализ файла журнала, например, но когда инфраструктура SNMP бу
дет настроена, работа с ней начинает удивлять.
В этой главе мы рассмотрим следующие аспекты SNMP: автообнару
жение, опрос/мониторинг, создание агентов, управление устройства
ми и, наконец, интеграцию оборудования средствами SNMP. Безус
ловно, все это можно реализовать на языке Python.
Если вы не знакомы с SNMP или вам требуется освежить свои знания
о SNMP, мы настоятельно рекомендуем прочитать книгу «Essential
SNMP» Дугласа Мауро (Douglas Mauro) и Кевина Шмидта (Kevin
Schmidt) (O’Reilly) или хотя бы держать ее под рукой. Хороший спра
вочник является основой к истинному пониманию возможностей
SNMP. В следующем разделе мы рассмотрим основы SNMP, но глубо
кое изучение этого протокола выходит далеко за рамки этой книги.
В действительности тема использования Python в комплексе с SNMP
настолько обширна, что заслуживает отдельной книги.
Краткое введение в SNMP
Обзор SNMP
С высоты 3000 метров SNMP – это протокол управления устройствами
в IPсетях. Как правило, этот протокол работает с портами UDP 161
и 162, хотя вполне возможно использовать и порты TCP. Практически
все современные устройства в центрах обработки данных поддержива

Краткое введение в SNMP 253
ют работу с протоколом SNMP, а это означает, что имеется возмож
ность управлять не только коммутаторами и маршрутизаторами, но
также серверами, принтерами, блоками бесперебойного питания, на
копителями и другими устройствами.
Работа протокола SNMP основана на передаче хостам пакетов UDP
и ожидании ответов. Таким образом на самом простом уровне произво
дится мониторинг устройств. Тем не менее, протокол SNMP обладает
гораздо более широкими возможностями благодаря управляющим уст
ройствам и возможности создания агентов, отвечающих на запросы.
Наиболее типичными примерами того, что возможно с применением
SNMP, является мониторинг нагрузки на процессор, использования
диска и объема свободной памяти. Этот протокол может также исполь
зоваться для управления сетевыми коммутаторами, с его помощью
вполне возможно даже выполнять загрузку новых параметров на
стройки коммутатора. Мало кому известно, что точно так же можно
осуществлять мониторинг программного обеспечения, такого как веб
приложения и базы данных. Наконец, имеется поддержка RMON MIB
(Remote Monitoring Management Information Base – база управляющей
информации для удаленного мониторинга), которая обеспечивает мо
ниторинг «динамики», тогда как в обычном режиме SNMP применя
ется для мониторинга статических показателей.
Мы уже упомянули аббревиатуру MIB, поэтому сейчас объясним, что
это такое. SNMP – это всего лишь протокол, и он не делает никаких
предположений о данных. На подконтрольных устройствах выполняет
ся агент, snmpd, у которого имеется перечень объектов, подвергаемых
мониторингу. Фактически перечень представляет собой базу управ
ляющей информации, или MIB (Management Information Base). У каж
дого агента имеется, по крайней мере, одна база MIB, структура кото
рой соответствует спецификациям MIBII, определяемым в RFC 1213.
Базу MIB можно представить себе как файл, который используется
для трансляции имен в числа (чемто похоже на DNS), хотя на самом
деле все немного сложнее.
В этом файле находятся описания объектов управления. У каждого
объекта имеется три атрибута: имя, тип и синтаксис и данные для пе
редачи. Из них вам чаще всего придется работать с именами. Имена
часто еще называют идентификаторами объектов, или OID (Object
Identifier). Передавая этот OID агенту, вы тем самым сообщаете, что
именно хотели бы получить. Имена имеют две формы представления:
числовую и «удобочитаемую». Чаще используется удобочитаемая
форма имен, потому что числовые имена имеют большую длину и их
сложно запоминать. Одним из самых часто используемых OID являет
ся sysDescr. Если вы воспользуетесь инструментом командной строки
snmpwalk, чтобы получить значение идентификатора sysDescr, вы мо
жете использовать как удобочитаемую, так и числовую форму пред
ставления:

254 Глава 7. SNMP
[root@rhel][H:4461]# snmpwalk v 2c c public localhost .1.3.6.1.2.1.1.1.0
SNMPv2 MIB::sysDescr.0 = STRING: Linux localhost
2.6.18 8.1.15.el5 #1 SMP Mon Oct 22 08:32:04 EDT 2007 i686
[root@rhel][H:4461]# snmpwalk v 2c c public localhost sysDescr
SNMPv2 MIB::sysDescr.0 = STRING: Linux localhost
2.6.18 8.1.15.el5 #1 SMP Mon Oct 22 08:32:04 EDT 2007 i686
К этому моменту мы нагрузили вас уймой аббревиатур и RFC, но при
зываем вас пересилить в себе желание встать и пойти спать. Мы обе
щаем, что очень скоро исправимся и приступим к разработке про
граммного кода.
Установка и настройка SNMP
Для упрощения дальнейшего повествования мы будем использовать
только пакет NetSNMP и соответствующее расширение Python к не
му. Но это не говорит о его большей ценности в сравнении с другими
библиотеками SNMP для Python, например PySNMP, используемой
в таких продуктах, как TwistwdSNMP и Zenoss. В Zenoss и в Twisted
SNMP библиотека PySNMP используется в асинхронном режиме. Это
очень правильный подход, который заслуживает рассмотрения, но у нас
просто нет места, чтобы описать оба эти продукта в данной главе.
Говоря в терминах NetSNMP, мы будем иметь дело с двумя различны
ми прикладными интерфейсами (API). Первый метод состоит в ис
пользовании модуля subprocess, чтобы «обернуть» инструменты ко
мандной строки из пакета NetSNMP, а второй – в использовании но
вых расширений для Python. Каждый из этих методов имеет свои пре
имущества и недостатки в зависимости от среды, в которой они
применяются.
В заключение мы также познакомимся с продуктом Zenoss, который
представляет собой весьма внушительное решение мониторинга сетей
посредством протокола SNMP, полностью реализованное на языке Py
thon и распространяемое с открытыми исходными текстами. При ис
пользовании Zenoss нам не придется писать средства управления SNMP
с чистого листа и вместо этого мы сможем взаимодействовать с ним по
средством его общедоступного API. Кроме того, проект Zenoss предос
тавляет нам возможность создавать собственные модули для этого про
дукта, вносить исправления и, наконец, расширять его функциональ
ность.
Чтобы добиться чегото полезного от SNMP, и в частности от NetSNMP,
его сначала нужно установить. К счастью, большинство операционных
систем UNIX и Linux устанавливаются вместе с пакетом NetSNMP, по
этому, если вам необходимо реализовать мониторинг устройства, для
этого достаточно будет выполнить необходимые настройки в конфигу
рационном файле snmpd.conf и запустить демон. Если вы предполагае
те разрабатывать на языке Python приложения, использующие пакет

Краткое введение в SNMP 255
NetSNMP, о котором идет речь в этой главе, вам необходимо скомпи
лировать и установить расширения для Python. Если же вы предпола
гаете просто обертывать команды NetSNMP, такие как snmpget, snmp
walk, snmpdf и другие, тогда вам ничего не потребуется делать, если сам
пакет NetSNMP уже установлен.
Как вариант, вы можете загрузить виртуальную машину с исходными
текстами примеров для этой книги с сайта издательства http://www.ore>
illy.com/9780596515829. Вы можете также обращаться на сайт под
держки книги www.py4sa.com, где найдете последнюю информацию
о том, как можно опробовать примеры из этого раздела.
Кроме того, мы настроили эту виртуальную машину и с поддержкой
NetSNMP, и с необходимыми расширениями для Python. Вы можете
просто использовать эту виртуальную машину для запуска всех при
меров. Если мощность вашего компьютера позволяет, вы можете соз
дать несколько копий виртуальной машины и запускать под их управ
лением другие примеры из этой главы, чтобы имитировать взаимодей
ствия с несколькими компьютерами одновременно.
Если вы решите самостоятельно установить расширения для Python,
вам потребуется загрузить с сайта sourceforge.net NetSNMP вер
сии 5.4.x или выше. Расширения в этом пакете не скомпилированы
по умолчанию, поэтому вам придется самостоятельно собрать их, сле
дуя инструкциям в каталоге Python/README. В двух словах заметим,
что вам сначала надо будет скомпилировать эту версию NetSNMP,
а затем запустить сценарий setyp.py в каталоге Python. Мы считаем,
что процедура установки наименее утомительна в дистрибутиве Red
Hat Linux, где имеется пакет RPM с исходными текстами. Если вы ре
шили выполнить компиляцию, возможно, вам следует сначала попро
бовать сделать это в Red Hat, чтобы ознакомиться с самим процессом,
а затем приступать к установке в AIX, Solaris, OS X, HPUX и в других
операционных системах. Наконец, если столкнетесь с неприятностя
ми, то для запуска примеров просто воспользуйтесь виртуальной ма
шиной, а порядок компиляции и установки выясните позже.
И еще одно последнее замечание: обязательно выполните команду set
up.py build и затем setup.py test. Это сразу же позволит вам проверить
возможность работы с NetSNMP из Python. В качестве совета: если вы
столкнетесь с неприятностями во время компиляции, запустите ко
манду ldconfig, как показано ниже:
ldconfig v /usr/local/lib/
Если вам случится устанавливать пакет NetSNMP на стороне клиен
та, который предполагается подвергнуть мониторингу, вам следует
скомпилировать NetSNMP с параметром Host Resources MIB. Для
этого обычно достаточно выполнить следующую команду конфигури
рования процесса сборки:
./configure with mib modules=host

256 Глава 7. SNMP
Обратите внимание, что при запуске сценария configure он попытается
запустить сценарий автоматической настройки. Но вам не обязатель
но делать это. Часто бывает проще вручную создать свой конфигура
ционный файл. В Red Hat настройки обычно сохраняются в файле
/etc/snmp/snmpd.conf и имеют примерно следующий вид:
syslocation "O'Reilly"
syscontact bofh@oreilly.com
rocommunity public
Этого простого файла будет вполне достаточно для опробования приме
ров в оставшейся части главы и запросов не для третьей версии SNMP.
Версия SNMPv3 имеет несколько более сложные настройки и не со
всем вписывается в тему данной главы, хотя при этом мы хотели бы
заметить, что в производстве лучше использовать SNMPv3, так как
версии 2 и 1 не имеют никакой защиты. Это значит, что никогда не
следует использовать SNMPv2 и SNMPv1 для передачи запросов через
Интернет, поскольку этот трафик может быть перехвачен. Известны
случаи высококлассных взломов, которые стали возможны благодаря
использованию этих версий.
IPython и NetSNMP
Если прежде вы никогда не занимались разработкой для SNMP, у вас
может возникнуть ощущение, что это не самая приятная работа. Чест
но говоря, это так и есть. Работа с SNMP чемто сродни головной бо
ли – изза высокой сложности протокола, изза необходимости читать
большое число RFC и изза высоких шансов допустить ошибку. Один
из способов попытаться ослабить эту боль состоит в том, чтобы для ис
следования SNMP и получения навыков обращения с API использо
вать оболочку IPython.
В примере 7.1 представлен очень короткий фрагмент программного
кода для запуска на локальной машине.
Пример 7.1. Использование IPython и Net>SNMP с расширениями Python
In [1]: import netsnmp
In [2]: oid = netsnmp.Varbind('sysDescr')
In [3]: result = netsnmp.snmpwalk(oid,
...: Version = 2,
...: DestHost="localhost",
...: Community="public")
Out[4]: ('Linux localhost 2.6.18 8.1.14.el5 #1 SMP Thu Aug 27 12:51:54 EDT
2008 i686',)
При исследовании библиотеки очень помогает использование функ
ции дополнения по нажатию клавиши табуляции. В этом примере мы
вовсю использовали функцию дополнения в IPython и с ее помощью

IPython и Net;SNMP 257
создали очень простой запрос SNMPv2. В качестве общего примеча
ния: идентификатор sysDescr, о котором мы уже упоминали ранее,
представляет собой очень важный запрос, позволяющий получить ба
зовые характеристики машины. В выводе этого примера можно уви
деть нечто похожее, хотя и не идентичное, тому, что выводит команда
uname
–a.
Как будет показано ниже в этой главе, анализ ответа на запрос sysDe
scr является важной частью исследования центров обработки данных.
К сожалению, как и многие составляющие SNMP, этот ответ не совсем
точен. Некоторые устройства могут не возвращать никакого ответа,
некоторые могут возвращать хоть и полезную, но неполную информа
цию, например: «Fibre Switch» (оптоволоконный коммутатор), неко
торые могут возвращать полную строку идентификации производите
ля. У нас недостаточно места, чтобы углубляться в детали решения
этой проблемы, но заметим, что умение анализировать все эти разли
чия как раз и есть то, на чем большие мальчики зарабатывают деньги.
Как вы уже знаете из главы 2 «IPython», существует возможность соз
дать определение класса или функции в виде отдельного файла прямо
из оболочки IPython, переключившись в редактор Vim, выполнив сле
дующую команду:
ed some_filename.py
После выхода из редактора вы получаете атрибуты созданного модуля
в своем пространстве имен и можете вывести их командой who. Этот
прием очень удобно использовать при работе с SNMP, так как итера
тивный стиль программирования естественным образом вписывается
в эту прикладную область. Давайте двинемся дальше и запишем сле
дующий ниже фрагмент программного кода в файл с именем snmp.py,
выполнив команду:
ed snmp.py
В примере 7.2 приводится простой модуль, представляющий собой
шаблон создания сеанса с помощью NetSNMP.
Пример 7.2. Простой модуль создания сеанса с помощью Net>SNMP
#!/usr/bin/env python
import netsnmp
class Snmp(object):
"""Простой сеанс SNMP"""
def __init__(self,
oid = "sysDescr",
Version = 2,
DestHost = "localhost",
Community = "public"):
self.oid = oid
self.version = Version

258 Глава 7. SNMP
self.destHost = DestHost
self.community = Community
def query(self):
"""Создает запрос SNMP"""
try:
result = netsnmp.snmpwalk(self.oid,
Version = self.version,
DestHost = self.destHost,
Community = self.community)
except Exception, err:
print err
result = None
return result
После того как вы сохраните этот файл и введете команду who, вы полу
чите следующее:
In [2]: who
Snmp netsnmp
Теперь, когда у нас имеется объектноориентированный интерфейс
к SNMP, можно воспользоваться им, чтобы выполнить запрос к ло
кальной машине:
In [3]: s = snmp()
In [4]: s.query()
Out[4]: ('Linux localhost 2.6.18 8.1.14.el5 #1 SMP Thu Sep 27 18:58:54 EDT
2007 i686',)
In [5]: result = s.query()
In [6]: len(result)
Out[6]: 1
Глядя на этот пример, можно сказать, что с помощью этого модуля
легко можно получать результаты, хотя в данном случае мы просто за
пустили сценарий, в котором жестко определили исходные данные;
поэтому теперь попробуем изменить значение объекта OID, чтобы вы
полнить обход всего поддерева системы:
In [7]: s.oid
Out[7]: 'sysDescr'
In [8]: s.oid = ".1.3.6.1.2.1.1"
In [9]: result = s.query()
In [10]: print result
('Linux localhost 2.6.18 8.1.14.el5 #1 SMP Thu Sep 27 18:58:54 EDT 2007 i686',
'.1.3.6.1.4.1.8072.3.2.10', '121219', 'me@localhost.com', 'localhost',
'"My Local Machine"', '0', '.1.3.6.1.6.3.10.3.1.1', '.1.3.6.1.6.3.11.3.1.1',
'.1.3.6.1.6.3.15.2.1.1', '.1.3.6.1.6.3.1',
'.1.3.6.1.2.1.49', '.1.3.6.1.2.1.4', '.1.3.6.1.2.1.50',
'.1.3.6.1.6.3.16.2.2.1', 'The SNMP Management Architecture MIB.',

IPython и Net;SNMP 259
'The MIB for Message Processing and Dispatching.', 'The management information
definitions for the SNMP User based Security Model.',
'The MIB module for SNMPv2 entities', 'The MIB module for managing TCP
implementations', 'The MIB module for managing IP and ICMP implementations',
'The MIB module for managing UDP [snip]',
'View based Access Control Model for SNMP.', '0', '0', '0', '0', '0', '0',
'0', '0')
Такой стиль интерактивного и исследовательского программирования
делает работу с SNMP более приятной. К этому моменту, если вы чув
ствуете в себе уверенность, можете приступить к выполнению запро
сов с другими значениями OID или даже выполнить обход всего дерева
MIB. Однако обход полного дерева MIB может занять некоторое время,
потому что потребуется выполнить запросы для множества значений
OID, поэтому на практике такой подход обычно не используется, так
как при этом будут потребляться ресурсы клиентской машины.
Не забывайте, что MIBII – это всего лишь файл со значениями
OID, который входит в состав большинства систем, обладающих
поддержкой SNMP. Другие MIB, уникальные для каждого про
изводителя, располагаются в отдельных файлах, к которым мо
жет обращаться агент, чтобы вернуть ответ на запрос. Если вы
захотите перейти на следующую ступень мастерства, вам при
дется найти специализированную документацию от производи
теля с описанием того, в какой базе MIB следует запрашивать
тот или иной OID.
Теперь перейдем к использованию особенности оболочки IPython, ко
торая позволяет запускать выполнение заданий в фоновом режиме:
In [11]: bg s.query()
Starting job # 0 in a separate thread.
In [12]: jobs[0].status
Out[12]: 'Completed'
In [16]: jobs[0].result
Out[16]:
('Linux localhost 2.6.18 8.1.14.el5 #1 SMP Thu Sep 27 18:58:54 EDT 2007 i686',
'.1.3.6.1.4.1.8072.3.2.10', '121219', 'me@localhost.com', 'localhost',
'"My Local Machine"',
'0', '.1.3.6.1.6.3.10.3.1.1', '.1.3.6.1.6.3.11.3.1.1',
'.1.3.6.1.6.3.15.2.1.1', '.1.3.6.1.6.3.1',
'.1.3.6.1.2.1.49', '.1.3.6.1.2.1.4', '.1.3.6.1.2.1.50',
'.1.3.6.1.6.3.16.2.2.1',
'The SNMP Management Architecture MIB.', 'The MIB for Message Processing and
Dispatching.',
'The management information definitions for the SNMP User based Security
Model.',
'The MIB module for SNMPv2 entities', 'The MIB module for managing TCP
implementations',
'The MIB module for managing IP and ICMP implementations', 'The MIB module for

260 Глава 7. SNMP
managing UDP implementations',
'View based Access Control Model for SNMP.', '0', '0', '0', '0', '0', '0',
'0', '0')
Прежде чем вы придете в восхищение, разрешите сообщить, что хотя
выполнение действий в фоновом режиме прекрасно реализовано в IPy
thon, тем не менее, этот режим может использоваться только при рабо
те с библиотеками, поддерживающими асинхронную модель выполне
ния. А расширения Python для NetSNMP работают в синхронном ре
жиме. В двух словах отметим, что вы не сможете писать многопоточ
ный программный код, ожидающий ответа, так как в основе лежат
блоки кода на языке C.
К счастью, как будет показано в главе, рассказывающей о процессах
и многозадачности, с помощью модуля обработки легко можно соз
давать дочерние процессы для параллельного выполнения запросов
SNMP. В следующем разделе мы рассмотрим проблему создания сце
нария, который будет автоматически исследовать центр обработки
данных.
Исследование центра обработки данных
Одна из наиболее полезных сторон SNMP заключается в использова
нии этого протокола для исследования центра обработки данных. Про
ще говоря, в ходе исследования составляется опись устройств, под
ключенных к сети, и производится сбор информации об этих устройст
вах. Более детальные виды исследований могут использоваться для
выявления связей между собранными данными, например, выяснение
точного MACадреса, под которым сервер известен коммутатору Cisco,
или схемы распределения памяти для оптоволоконного коммутатора
Brocade.
В этом разделе мы создадим простой сценарий, который будет отби
рать корректные IPадреса, MACадреса, основную информацию, по
ставляемую протоколом SNMP, и помещать ее в записи. Этот сценарий
может использоваться в вашей организации как основа для реализа
ции приложений, выполняющих исследование центра обработки дан
ных. При создании сценария мы будем использовать сведения, кото
рые рассматривались в других главах.
Существует несколько различных алгоритмов исследования, с кото
рыми нам приходилось сталкиваться, но только один из них мы пред
ставим вашему вниманию. Суть алгоритма состоит в следующем: по
слать серию запросов по протоколу ICMP; каждому ответившему уст
ройству послать простой запрос SNMP; проанализировать ответ; про
должить исследование на основе полученных данных. Другой алгоритм
подразумевает посылку серии запросов SNMP и сбор ответов с помо
щью другого процесса, но, как уже говорилось выше, мы сосредото
чимся на реализации первого алгоритма. Взгляните на пример 7.3.

Исследование центра обработки данных 261
Небольшое замечание к программному коду ниже: поскольку
библиотека NetSNMP предусматривает возможность работы
только в синхронном режиме, мы создаем дочерние процессы
вызовом subprocess.call(). Это приводит к возможности появле
ния блокировок. В части использования утилиты ping мы могли
бы просто использовать subprocess.Popen, но чтобы сохранить
единообразие, мы используем один и тот же прием как для вы
полнения запросов SNMP, так и при использовании утилиты
ping.
Пример 7.3. Простой сценарий исследования центра обработки данных
#!/usr/bin/env python
from processing import Process, Queue, Pool
import time
import subprocess
import sys
from snmp import Snmp
q = Queue()
oq = Queue()
#ips = IP("10.0.1.0/24")
ips = ["192.19.101.250", "192.19.101.251", "192.19.101.252",
"192.19.101.253", "192.168.1.1"]
num_workers = 10
class HostRecord(object):
"""Записи с информацией о хостах"""
def __init__(self, ip=None, mac=None, snmp_response=None):
self.ip = ip
self.mac = mac
self.snmp_response = snmp_response
def __repr__(self):
return "[Host Record('%s','%s','%s')]" % (self.ip,
self.mac,
self.snmp_response)
def f(i,q,oq):
while True:
time.sleep(.1)
if q.empty():
sys.exit()
print "Process Number: %s Exit" % i
ip = q.get()
print "Process Number: %s" % i
ret = subprocess.call("ping c 1 %s" % ip,
shell=True,
stdout=open('/dev/null', 'w'),
stderr=subprocess.STDOUT)
if ret == 0:
print "%s: is alive" % ip
oq.put(ip)

262 Глава 7. SNMP
else:
print "Process Number: %s didn’t find a response for %s " % (i, ip)
pass
def snmp_query(i,out):
while True:
time.sleep(.1)
if out.empty():
sys.exit()
print "Process Number: %s" % i
ipaddr = out.get()
s = Snmp()
h = HostRecord()
h.ip = ipaddr
h.snmp_response = s.query()
print h
return h
try:
q.putmany(ips)
finally:
for i in range(num_workers):
p = Process(target=f, args=[i,q,oq])
p.start()
for i in range(num_workers):
pp = Process(target=snmp_query, args=[i,oq])
pp.start()
print "main process joins on queue"
p.join()
#while not oq.empty():
# print "Validated", oq.get()
print "Main Program finished"
Когда мы запустили этот сценарий, то получили следующий результат:
[root@giftcsllc02][H:4849][J:0]> python discover.py
Process Number: 0
192.19.101.250: is alive
Process Number: 1
192.19.101.251: is alive
Process Number: 2
Process Number: 3
Process Number: 4
main process joins on queue
192.19.101.252: is alive
192.19.101.253: is alive
Main Program finished
[Host Record('192.19.101.250','None','('Linux linux.host 2.6.18 8.1.15.el5
#1 SMP Mon Oct 22 08:32:04 EDT 2007 i686',)')]
[Host Record('192.19.101.252','None','('Linux linux.host 2.6.18 8.1.15.el5
#1 SMP Mon Oct 22 08:32:04 EDT 2007 i686',)')]
[Host Record('192.19.101.253','None','('Linux linux.host 2.6.18 8.1.15.el5

Получение множества значений с помощью SNMP 263
#1 SMP Mon Oct 22 08:32:04 EDT 2007 i686',)')]
[Host Record('192.19.101.251','None','('Linux linux.host 2.6.18 8.1.15.el5
#1 SMP Mon Oct 22 08:32:04 EDT 2007 i686',)')]
Process Number: 4 didn't find a response for 192.168.1.1
Полученные результаты показывают, как работает этот интересный
алгоритм исследования центра обработки данных. В этом сценарии
можно было бы коечто исправить: например, добавить запись MAC
адреса в объект HostRecord, переписать программный код в более объ
ектноориентированном стиле, – дополнений хватило бы еще на одну
книгу, и разработок хватило бы на целую компанию. Понимая это, мы
переходим к другому разделу.
Получение множества значений с помощью SNMP
Получение единственного значения не представляет большой сложно
сти, хотя иногда бывает желательно проверить ответы или выполнить
некоторое действие, основанное, например, на типе операционной сис
темы, под управлением которой работает компьютер. Чтобы сделать
чтото более значимое, бывает необходимо получить несколько значе
ний и выполнить какиелибо действия над ними.
Часто возникает задача произвести инвентаризацию центра обработки
данных или отдела и собрать сведения о некоторых параметрах всех
имеющихся машин. Представим такую гипотетическую ситуацию: вы
готовитесь к крупному обновлению программного обеспечения и вам
сказали, что каждая система должна иметь как минимум 1 Гбайт ОЗУ.
Вы помните, что на большинстве компьютеров установлено памяти не
менее 1 Гбайта, но среди тысяч поддерживаемых вами компьютеров
имеется несколько таких, где памяти меньше требуемого объема.
Очевидно, что у вас имеется несколько вариантов действий. Рассмот
рим каждый из них:
Вариант 1
Физически обойти все компьютеры и проверить объем ОЗУ на каж
дом из них, запустив некоторую команду или вскрыв корпус. Дос
таточно очевидно, что это не самый привлекательный вариант.
Вариант 2
Зайти по сети на каждый компьютер и выполнить команду, чтобы
определить объем ОЗУ. Этот подход обладает массой недостатков,
но его хотя бы теоретически можно реализовать в виде сценария,
выполняющего команду средствами ssh. Одна из основных проблем
состоит в том, что сценарий должен учитывать различия между
платформами, так как все операционные системы в чемто немного
отличаются. Другая проблема заключается в необходимости знать,
где все эти компьютеры располагаются.

264 Глава 7. SNMP
Вариант 3
Написать небольшой сценарий, который опросит все устройства,
включенные в сеть, и определит объем памяти на каждом из них
с помощью SNMP.
Вариант 3, основанный на использовании SNMP, позволяет легко соз
дать опись, в которой будут присутствовать только компьютеры, имею
щие менее 1 Гбайта ОЗУ. Точный идентификатор OID, который потре
буется запросить, носит имя «hrMemorySize». Протокол SNMP отно
сится к разряду тех инструментов, которые всегда выгоднее использо
вать в многозадачном режиме, но всетаки подобной оптимизации
лучше избегать, если это не является абсолютно необходимым. Помня
об этом, перейдем непосредственно к деталям.
Чтобы быстро проверить нашу идею, воспользуемся программным ко
дом из предыдущего примера.
Получение объема памяти с помощью SNMP:
In [1]: run snmpinput
In [2]: who
netsnmp Snmp
In [3]: s = Snmp()
In [4]: s.DestHost = "10.0.1.2"
In [5]: s.Community = "public"
In [6]: s.oid = "hrMemorySize"
In [7]: result = int(s.query()[0])
hrMemorySize = None ( None )
In [27]: print result
2026124
Как видите, реализовать подобный сценарий достаточно просто. Ре
зультат возвращается в виде кортежа в строке [6], поэтому мы извле
каем элемент с индексом 0 и преобразуем его в целое число. Теперь мы
имеем целое число, соответствующее объему памяти в килобайтах.
Единственное, что следует иметь в виду, – на разных компьютерах
объем ОЗУ вычисляется поразному. Поэтому в таких случаях лучше
принимать решение на основе приближенных, а не точных значений.
Например, можно было бы искать компьютеры, объем ОЗУ в которых
немного меньше 1 Гбайта – скажем, 990 Мбайт.
В приведенном примере мы можем примерно оценить, что полученное
число примерно соответствует объему ОЗУ 2 Гбайта. Вы можете пола
гаться на эту информацию, отвечая на запрос своего руководителя
о наличии компьютеров с ОЗУ менее 2 Гбайт, если известно, что новое
приложение, которое требуется установить, требует наличия памяти
не менее 2 Гбайт.

Получение множества значений с помощью SNMP 265
Теперь, проверив основную идею, мы можем автоматизировать проце
дуру определения памяти. Если говорить более определенно, следует
опросить все компьютеры, выяснить, в каких из них установлено па
мяти менее 2 Гбайт ОЗУ и затем записать полученную информацию
в файл формата CSV
1, чтобы ее потом проще было импортировать в Ex
cel или OpenOffice Calc.
Далее можно написать инструмент командной строки, который прини
мает диапазон сетевых адресов в качестве входного аргумента и, до
полнительно, значение идентификатора OID, по умолчанию используя
идентификатор «hrMemorySize». При этом в сценарии нам необходимо
будет предусмотреть обход сетевых адресов из указанного диапазона.
Всякий раз, когда системный администратор пишет программный
код, он сталкивается с определенными ограничениями. В состоянии
ли вы потратить несколько часов или даже дней на создание большого
объектноориентированного сценария, который потом можно будет
использовать для решения других задач, или вам нужно быстро полу
чить хотя бы приблизительные результаты? На наш взгляд, в боль
шинстве случаев вполне можно выполнить обе реализации. При ис
пользовании IPython вы можете быстро создавать заготовки сценари
ев и затем доводить их до окончательного состояния. Вообще – это хо
рошая идея писать программный код многократного использования,
поскольку эта привычка, как снежный ком, быстро обретает инерцию
движения.
Надеемся, что теперь вы понимаете, в чем заключается сила SNMP.
Давайте приступим к созданию нашего сценария…
Поиск объема памяти
В этом следующем примере мы реализуем инструмент командной
строки, определяющий объем памяти, установленной в компьютерах,
с помощью SNMP:
#!/usr/bin/env python
#Инструмент командной строки, определяющий общий объем памяти в компьютере
import netsnmp
import optparse
from IPy import IP
class SnmpSession(object):
"""Простой сеанс SNMP"""
def __init__(self,
oid="hrMemorySize",
Version=2,
DestHost="localhost",
1 CSV, или Comma Separated Values, – значения, разделенные запятыми. –
Прим. перев.

266 Глава 7. SNMP
Community="public"):
self.oid = oid
self.Version = Version
self.DestHost = DestHost
self.Community = Community
def query(self):
"""Создает запрос SNMP"""
try:
result = netsnmp.snmpwalk(self.oid,
Version = self.Version,
DestHost = self.DestHost,
Community = self.Community)
except:
#Несмотря на то, что это всего лишь пример,
#тем не менее, выведем, какое исключение возникло
import sys
print sys.exc_info()
result = None
return result
class SnmpController(object):
"""Использует модуль optparse для управления сеансом SnmpSession"""
def run(self):
results = {} #Место сбора и хранения результатов snmp
p = optparse.OptionParser(description="A tool that determines
memory installed",
prog="memorator",
version="memorator 0.1.0a",
usage="%prog [subnet range] [options]")
p.add_option(' community', ' c',help='community string',
default='public')
p.add_option(' oid', ' o', help='object identifier',
default='hrMemorySize')
p.add_option(' verbose', ' v', action=’store_true',
help='increase verbosity')
p.add_option(' quiet', ' q', action=’store_true',help=’
suppresses most messages')
p.add_option(' threshold', ' t', action=’store', type="int",
help='a number to filter queries with')
options, arguments = p.parse_args()
if arguments:
for arg in arguments:
try:
ips = IP(arg) #Преобразовать аргумент в строку
except:
if not options.quiet:
print 'Ignoring %s, not a valid IP address' % arg
continue
for i in ips:
ipAddr = str(i)

Получение множества значений с помощью SNMP 267
if not options.quiet:
print 'Running snmp query for: ', ipAddr
session = SnmpSession(options.oid,
DestHost = ipAddr,
Community = options.community)
if options.oid == "hrMemorySize":
try:
memory = int(session.query()[0])/1024
except:
memory = None
output = memory
else:
#Обработка результатов, не имеющих отношения
#к объему памяти
output = session.query()
if not options.quiet:
print "%s returns %s" % (ipAddr,output)
#Поместить полученные результаты в словарь,
#Но только если был получен корректный ответ
if output != None:
if options.threshold: #если порог обозначен
if output < options.threshold:
results[ipAddr] = output
#если разрешен вывод результатов
if not options.quiet:
print "%s returns %s" % (ipAddr,output)
else:
results[ipAddr] = output
if not options.quiet:
print output
print "Results from SNMP Query %s for %s:\n" % (options.oid,
arguments), results
else:
p.print_help() #при отсутствии аргументов командной строки
#вывести инструкцию об использовании
def _main():
"""
Запускает процесс сбора информации.
"""
start = SnmpController()
start.run()
if __name__ =='__main__':
try:
import IPy
except:
print "Please install the IPy module to use this tool"
_main()

268 Глава 7. SNMP
Теперь пройдемся по этому сценарию и посмотрим, что он делает. Мы
взяли целый класс из предыдущего примера и поместили его в новый
модуль. Затем мы написали классконтроллер, который анализирует
аргументы командной строки с помощью модуля optparse. Модуль IPy,
к которому мы обращаемся снова и снова, используется для автомати
ческой обработки IPадресов. Благодаря этому можно указать несколь
ко IPадресов или диапазон адресов, и наш модуль будет отсылать за
просы SNMP и возвращать результаты в виде словаря, в котором роль
ключей будут играть IPадреса, а роль значений – ответы SNMP.
Единственная сложность, которая здесь реализована, – это логика об
работки пустых ответов и проверки порогового значения. То есть мо
дуль возвращает только значения ниже указанного порога. При ис
пользовании порога мы можем получать значимые для нас результаты
и учесть различия в том, как разные компьютеры вычисляют объем
памяти.
Посмотрим на вывод, полученный в результате работы этого модуля:
[ngift@ng lep lap][H:6518][J:0]> ./memory_tool_netsnmp.py 10.0.1.2 10.0.1.20
Running snmp query for: 10.0.1.2
hrMemorySize = None ( None )
1978
Running snmp query for: 10.0.1.20
hrMemorySize = None ( None )
372
Results from SNMP Query hrMemorySize for ['10.0.1.2', '10.0.1.20']:
{'10.0.1.2': 1978, '10.0.1.20': 372}
Как видите, результаты были получены для компьютеров в подсети
10.0.1.0/24. Теперь воспользуемся флагом threshold (порог), чтобы
сымитировать поиск машин, объем ОЗУ в которых меньше 2 Гбайт.
Как уже упоминалось выше, разные компьютеры поразному вычис
ляют имеющийся объем ОЗУ, поэтому для пущей уверенности возь
мем в качестве порогового значения число 1800, что примерно должно
соответствовать объему ОЗУ 1800 Мбайт. То есть, если в компьютере
объем ОЗУ составляет менее 1800 Мбайт, или примерно 2 Гбайта, ин
формация о нем будет включена в наш отчет.
Ниже приводится результат выполнения такого запроса:
[ngift@ng lep lap][H:6519][J:0]>
./memory_tool_netsnmp.py threshold 1800 10.0.1.2 10.0.1.20
Running snmp query for: 10.0.1.2
hrMemorySize = None ( None )
Running snmp query for: 10.0.1.20
hrMemorySize = None ( None )
10.0.1.20 returns 372
Results from SNMP Query hrMemorySize for ['10.0.1.2', '10.0.1.20']:
{'10.0.1.20': 372}

Получение множества значений с помощью SNMP 269
Наш сценарий прекрасно справился с заданием, однако мы можем
сделать еще коечто, чтобы оптимизировать его. Если вам потребует
ся опросить несколько тысяч машин, то для выполнения работы этому
сценарию может потребоваться целый день или даже больше. Это, мо
жет быть, и не страшно, но если вам требуется получить результаты
очень быстро, вам необходимо будет обеспечить возможность парал
лельного выполнения нескольких запросов одновременно и организо
вать ветвление для каждого запроса, используя для этого библиотеку
стороннего производителя. Еще одно усовершенствование, которое
можно было бы внести, – это автоматическое создание файла отчета
в формате CSV из нашего словаря.
Но прежде чем мы перейдем к реализации этих задач, позвольте обра
тить ваше внимание на один момент, который вы, возможно, не заме
тили. Сценарий написан так, что позволяет запрашивать любой OID,
а не только определять объем памяти. Это очень удобно, потому что
у нас теперь имеется как инструмент определения объемов памяти,
так и универсальный инструмент, позволяющий выполнять любые за
просы SNMP.
Рассмотрим пример, который наглядно демонстрирует, что мы имеем
в виду:
[ngift@ng lep lap][H:6522][J:0]> ./memory_tool_netsnmp.py o sysDescr
10.0.1.2 10.0.1.20
Running snmp query for: 10.0.1.2
sysDescr = None ( None )
10.0.1.2 returns ('Linux cent 2.6.18 8.1.14.el5 #1 SMP
Thu Sep 27 19:05:32 EDT 2007 x86_64',)
('Linux cent 2.6.18 8.1.14.el5 #1 SMP Thu Sep 27 19:05:32 EDT 2007 x86_64',)
Running snmp query for: 10.0.1.20
sysDescr = None ( None )
10.0.1.20 returns ('Linux localhost.localdomain 2.6.18 8.1.14.el5 #1 SMP
Thu Sep 27 19:05:32 EDT 2007 x86_64',)
('Linux localhost.localdomain 2.6.18 8.1.14.el5 #1 SMP
Thu Sep 27 19:05:32 EDT 2007 x86_64',)
Results from SNMP Query sysDescr for ['10.0.1.2', '10.0.1.20']:
{'10.0.1.2': ('Linux cent 2.6.18 8.1.14.el5 #1 SMP
Thu Sep 27 19:05:32 EDT 2007 x86_64',), '10.0.1.20':
('Linux localhost.localdomain 2.6.18 8.1.14.el5 #1 SMP
Thu Sep 27 19:05:32 EDT 2007 x86_64',)}
Совсем не лишне учитывать это, приступая к работе над «одноразо
вым» инструментом для конкретного случая. Почему бы не потратить
дополнительные 30 минут, чтобы придать ему универсальность? В ре
зультате у вас может получиться инструмент, который будет находить
применение снова и снова, и эти 30 минут превратятся в ничто по срав
нению с тем временем, которое вам удастся сэкономить в будущем.

270 Глава 7. SNMP
Создание гибридных инструментов SNMP
Мы уже показали вам несколько отдельных инструментов и хотим от
метить, что использованные нами приемы можно объединить для соз
дания весьма сложных инструментов. Начнем с создания серии про
стых узкоспециализированных инструментов, на основе которых позд
нее мы сможем создавать большие сценарии.
Ниже приводится полезный сценарий с именем snmpstatus, который
получает несколько различных запросов snmp и комбинирует из них
«состояние» опрашиваемого узла:
import subprocess
class Snmpdf(object):
"""Инструмент командной строки snmpstatus"""
def __init__(self,
Version=" v2c",
DestHost="localhost",
Community="public",
verbose=True):
self.Version = Version
self.DestHost = DestHost
self.Community = Community
self.verbose = verbose
def query(self):
"""Создает запрос для snmpstatus"""
Version = self.Version
DestHost = self.DestHost
Community = self.Community
verbose = self.verbose
try:
snmpstatus = "snmpstatus %s c %s %s" % (Version, Community,
DestHost)
if verbose:
print "Running: %s" % snmpstatus
p = subprocess.Popen(snmpstatus,
shell=True,
stdout=subprocess.PIPE)
out = p.stdout.read()
return out
except:
import sys
print >> sys.stderr, "error running %s" % snmpstatus
def _main():
snmpstatus = Snmpdf()
result = snmpstatus.query()
print result

Расширение возможностей Net;SNMP 271
if __name__ == "__main__":
_main()
Мы надеемся, что вы обратили внимание на тот факт, что этот сцена
рий не сильно отличается от команды snmpdf, за исключением некото
рых имен. Это отличный пример, когда было бы желательно перейти
на более высокий уровень абстракции и затем повторно использовать
общие компоненты. Если бы мы создали модуль, вмещающий весь об
щий программный код, наш новый сценарий состоял бы всего из не
скольких строк. Имейте это в виду, мы еще вернемся к этому.
Другой инструмент, имеющий отношение к SNMP, – это ARP, кото
рый использует протокол ARP. С помощью протокола ARP можно по
лучить MACадреса устройств по их IPадресам, при условии, что они
находятся в одной и той же сети. Давайте напишем и этот узкоспециа
лизированный инструмент. Он пригодится нам немного позже.
Оформить действия с протоколом ARP в виде сценария не составит ни
какого труда; можно сразу продемонстрировать работу этого примера,
используя интерактивную оболочку IPython. Итак, запустите IPython
и введите следующее:
import re
import subprocess
#некоторые переменные
ARP = "arp"
IP = "10.0.1.1"
CMD = "%s %s " % (ARP, IP)
macPattern = re.compile(":")
def getMac():
p = subprocess.Popen(CMD, shell=True, stdout=subprocess.PIPE)
out = p.stdout.read()
results = out.split()
for chunk in results:
if re.search(macPattern, chunk):
return chunk
if __name__ == "__main__":
macAddr = getMac()
print macAddr
Этот фрагмент нельзя назвать инструментом многократного использо
вания, но вы легко можете взять эту идею за основу и использовать ее
как часть общей библиотеки получения сведений об устройствах в сети
центра обработки данных.
Расширение возможностей NetSNMP
Как уже говорилось ранее, в большинстве операционных систем *nix
пакет NetSNMP установлен в виде агента. По умолчанию агент может
возвращать определенный перечень информации, однако существует

272 Глава 7. SNMP
возможность расширять этот перечень. Можно было бы указать агенту
на необходимость собирать некоторые сведения и затем возвращать их
по протоколу SNMP.
Файл EXAMPLE.conf, поставляемый в составе NetSNMP, – это один
из лучших источников информации по расширению возможностей
NetSNMP. Нелишним будет обратиться к команде man snmpd.conf, ко
торая выводит более подробную информацию с описанием API. Если
вас интересуют вопросы расширения возможностей «родных» агентов
пакета, оба эти источника справочной информации могут стать для
вас незаменимыми.
С точки зрения программистов на языке Python, возможность расши
рения NetSNMP является одним из самых захватывающих аспектов
работы с SNMP, потому что позволяет разработчикам писать про
граммный код, выполняющий мониторинг всего, что они сочтут необ
ходимым, и дополнительно иметь внутреннего агента, отвечающего
предъявляемым условиям.
Пакет NetSNMP предлагает достаточно много способов расширения
возможностей агента, и для начала мы напишем программу «Hello
World», которая будет выполняться по запросу snmp. Первый шаг за
ключается в создании простого файла snmpd.conf, посредством которо
го будет запускаться наша программа «Hello World», написанная на
языке Python. В примере 7.4 показано, как выглядит этот файл в опе
рационной системе Red Hat.
Пример 7.4. Конфигурационный файл SNMP, предусматривающий вызов
программы «Hello World»
syslocation "O'Reilly"
syscontact bofh@oreilly.com
rocommunity public
exec helloworld /usr/bin/python c "print 'hello world from Python'"
После этого следует сообщить демону snmpd о необходимости перечи
тать конфигурационный файл. Сделать это можно тремя разными спо
собами. В Red Hat можно использовать такую команду:
service snmpd reload
или сначала выполнить такую команду:
ps ef | grep snmpd
root 12345 1 0 Apr14 ?
00:00:30 /usr/sbin/snmpd Lsd Lf /dev/null p /var/run/snmpd.pid –a
а затем послать демону сигнал:
kill HUP 12345
Наконец, можно с помощью команды snmpset присвоить целое число
(1) параметру UCD
–SNMPMIB::versionUpdateConfig.0 и тем самым вынудить
демон snmpd перечитать конфигурационный файл.

Расширение возможностей Net;SNMP 273
Теперь, когда демон snmpd перечитал измененный файл snmpd.conf, мы
можем двинуться дальше и послать нашей машине запрос с помощью
команды snmpwalk или с помощью расширения NetSNMP из оболочки
IPython. Ниже показано, что возвращает команда snmpwalk:
[root@giftcsllc02][H:4904][J:0]> snmpwalk v 1 c public localhost
.1.3.6.1.4.1.2021.8
UCD SNMP MIB::extIndex.1 = INTEGER: 1
UCD SNMP MIB::extNames.1 = STRING: helloworld
UCD SNMP MIB::extCommand.1 = STRING: /usr/bin/python
c "print 'hello world from Python'"
UCD SNMP MIB::extResult.1 = INTEGER: 0
UCD SNMP MIB::extOutput.1 = STRING: hello world from Python
UCD SNMP MIB::extErrFix.1 = INTEGER: noError(0)
UCD SNMP MIB::extErrFixCmd.1 = STRING:
Этот запрос требует некоторых пояснений, так как наблюдательный чи
татель может задаться вопросом, откуда взялся OID 1.3.6.1.4.1.2021.8.
Этот OID соответствует идентификатору ucdavis.extTable. Когда созда
ется расширение в snmpd.conf, оно присваивается этому OID. Дело не
сколько осложняется, когда возникает потребность создать свой OID.
Для этого необходимо обратиться в организацию iana.org и получить
уникальный номер для своего предприятия. После этого можно будет
использовать полученный номер для создания специализированных
запросов агенту. Основная причина таких сложностей состоит в необ
ходимости сохранить однородность пространства имен и избежать
конфликтов с числами, которые, возможно, получат поставщики обо
рудования в будущем.
Истинная сила Python заключается вовсе не в том, чтобы получить вы
вод от единственной команды – это было бы слишком просто. Ниже
приводится пример сценария, который определяет общее число обра
щений к вебсерверу Apache из броузера Firefox, анализируя файл жур
нала, и возвращает результат под нестандартным OID предприятия.
Начнем рассмотрение с конца и сначала посмотрим на полученные ре
зультаты:
snmpwalk v 2c c public localhost .1.3.6.1.4.1.2021.28664.100
UCD SNMP MIB::ucdavis.28664.100.1.1 = INTEGER: 1
UCD SNMP MIB::ucdavis.28664.100.2.1 = STRING: "FirefoxHits"
UCD SNMP MIB::ucdavis.28664.100.3.1 = STRING:
"/usr/bin/python /opt/local/snmp_scripts/agent_ext_logs.py"
UCD SNMP MIB::ucdavis.28664.100.100.1 = INTEGER: 0
UCD SNMP MIB::ucdavis.28664.100.101.1 = STRING:
"Total number of Firefox Browser Hits: 15702"
UCD SNMP MIB::ucdavis.28664.100.102.1 = INTEGER: 0
UCD SNMP MIB::ucdavis.28664.100.103.1 = ""
Если отыскать строку со значением 100.101.1, можно увидеть вывод,
полученный от сценария, который анализирует файл журнала вебсер
вера Apache и отыскивает записи, свидетельствующие об обращениях

274 Глава 7. SNMP
с помощью броузера Firefox. Затем сценарий суммирует их и возвраща
ет по протоколу SNMP. В примере 7.5 приводится исходный текст сце
нария, который запускается при выполнении запроса к данному OID.
Пример 7.5. Сценарий поиска числа обращений к веб>серверу Apache
из броузера Firefox
import re
"""Возвращает число обращений из броузера Firefox"""
def grep(lines,pattern="Firefox"):
pat = re.compile(pattern)
for line in lines:
if pat.search(line): yield line
def increment(lines):
num = 0
for line in lines:
num += 1
return num
wwwlog = open("/home/noahgift/logs/noahgift.com combined log")
column = (line.rsplit(None,1)[1] for line in wwwlog)
match = grep(column)
count = increment(match)
print "Total Number of Firefox Hits: %s" % count
Чтобы заставить этот запрос работать, мы сначала должны добавить
вфайл snmpd.conf информацию об этом сценарии, как показано ниже:
syslocation “O Reilly”
syscontact bofh@oreilly.com
rocommunity public
exec helloworld /usr/bin/python c "print 'hello world from Python'"
exec .1.3.6.1.4.1.2021.28664.100 FirefoxHits /usr/bin/python
/opt/local/snmp_scripts/agent_ext_logs.py
Самая магическая часть здесь – последняя строка с идентификатором
.1.3.6.1.4.1.2021, где 28664 является числом нашего предприятия,
а число 100 – просто некоторое число, которое мы решили использо
вать для примера. Это очень важно – следовать общепринятым прави
лам и использовать свое число предприятия, если вы планируете зани
маться расширением возможностей SNMP. Благодаря этому вы сумее
те избежать конфликтов при использовании в команде snmpset чисел,
уже занятых кемто другим.
Мы склонны считать, что тема использования SNMP является одной
из самых захватывающих тем в книге и что при этом SNMP попреж
нему остается малоизведанной областью. Можно привести массу при
меров, когда расширение возможностей NetSNMP может быть полез
ным, а при аккуратном использовании SNMPv3 вы сможете делать
удивительные вещи, реализовать которые с помощью протокола SNMP

Управление устройствами через SNMP 275
совсем несложно и для которых применение ssh и сокетов могло бы по
казаться естественным выбором.
Управление устройствами через SNMP
Одним из самых интересных аспектов применения SNMP является
возможность управления устройствами по этому протоколу. Очевид
но, что такой способ управления маршрутизатором обладает сущест
венными преимуществами перед использованием, например, модуля
Pexpect (http://sourceforge.net/projects/pexpect/), потому что реализу
ется намного проще.
Для краткости мы в примере будем рассматривать только использова
ние SNMPv1, но, если вам предстоит взаимодействовать с устройства
ми через незащищенную сеть, вам следует использовать SNMPv3. Пе
ред прочтением этого раздела было бы неплохо ознакомиться с книга
ми «Essential SNMP» и «Cisco IOS Cookbook» Кевина Дули (Kevin
Dooley) и Яна Дж. Брауна ( Ian. J. Brown) (O’Reilly), если они у вас име
ются или у вас имеется учетная запись для доступа к службе Safari.
Они содержат обширую информацию как об основах настройки, так
и о способах взаимодействия с устройствами Cisco по протоколу SNMP.
Поскольку перезагрузка параметров настройки в устройствах Cisco
красиво реализуется через протокол SNMP, мы выбрали эту тему для
разговора об управлении устройствами. Для опробования этого приме
ра вам потребуется работающий сервер TFTP, откуда маршрутизатор
будет забирать файл IOS, и маршрутизатор с разрешенным доступом
для чтения/записи по протоколу SNMP. В примере 7.6 приводится
сценарий на языке Python.
Пример 7.6. Выгрузка новой конфигурации в маршрутизатор Cisco
import netsnmp
vars = netsnmp.Varbind(netsnmp.VarList(netsnmp.Varbind(
".1.2.6.1.4.1.9.2.10.6.0", "1"),
(netsnmp.Varbind("cisco.example.com.1.3.6.1.4.1.9.2.10.12.172.25.1.1",
"iso config.bin")
result = netsnmp.snmpset(vars,
Version = 1,
DestHost='cisco.example.com',
Community='readWrite')
В этом примере мы использовали метод VarList из модуля netsnmp, что
бы сначала выполнить инструкцию, которая стирает информацию во
флешпамяти коммутатора, а затем загрузить новый образ файла IOS.
Этот программный код мог бы послужить основой сценария, выпол
няющего обновление настроек всех коммутаторов в вычислительном
центре. Как и любой другой программный код в этой книге, он должен

276 Глава 7. SNMP
быть опробован на оборудовании, не включенном в работу, и вы не ока
жетесь перед фактом, что чтото натворили,.
И последнее замечание: протокол SNMP редко рассматривается как
способ управления устройствами и, тем не менее, он предоставляет
широкие возможности по управлению устройствами в вычислитель
ном центре, поскольку является универсальной спецификацией для
устройств, выпускавшихся начиная с 1988 года. В будущем возможно
очень интересное развитие протокола SNMP v3.
Интеграция SNMP в сеть предприятия
с помощью Zenoss
Zenoss представляет собой замечательную систему управления ло
кальными сетями уровня предприятия. Мало того, что Zenoss являет
ся приложением, распространяемым с открытыми исходными текста
ми, но оно еще целиком написано на языке Python. Система Zenoss яв
ляется представителем нового поколения приложений уровня пред
приятия, обладающих большими возможностями и допускающих
расширение с использованием интерфейса XMLRPC или ReST. За до
полнительной информацией о ReST обращайтесь к книге Леонарда
Ричардсона (Leonard Richardson) и Сэма Руби (Sam Ruby) «RESTful
Web Services» (O’Reilly).
Наконец, если у вас появится желание участвовать в разработке Ze
noss, вы можете предлагать свои исправления.
Прикладной интерфейс Zenoss
За последней информацией о прикладном интерфейсе Zenoss обра
щайтесь на сайт http://www.zenoss.com/community/docs/howtos/send>
events/.
Использование Zendmd
Система Zendoss не только поставляется в комплекте с системой мони
торинга и исследования SNMP, но и включает в себя прикладной ин
терфейс высокого уровня с именем zendmd. Вы можете открыть на
строенную командную оболочку Python и выполнять команды Zenoss
непосредственно.
Пример использования zendmd:
>>> d = find('build.zenoss.loc')
>>> d.os.interfaces.objectIds()
['eth0', 'eth1', 'lo', 'sit0', 'vmnet1', 'vmnet8']
>>> for d in dmd.Devices.getSubDevices():
>>> print d.id, d.getManageIp()

Интеграция SNMP в сеть предприятия с помощью Zenoss 277
Прикладной интерфейс доступа к устройствам
С системой Zenoss можно также взаимодействовать через интерфейс
XMLRPC и добавлять или удалять устройства. Ниже приводятся два
примера:
С использованием ReST:
[zenos@zenoss $]
wget 'http://admin:zenoss@MYHOST:8080/zport/dmd
/ZenEventManager/manage_addEvent?device=MYDEVICE&component=
"
MYCOMPONENT&summary= "
MYSUMMARY&severity=4&eclass=EVENTCLASS&eventClassKey=EVENTCLASSKEY
C использованием XMLRPC:
>>> from xmlrpclib import ServerProxy
>>> serv = ServerProxy(
'http://admin:zenoss@MYHOST:8080/zport/dmd/ZenEventManager')
>>> evt = {'device':'mydevice', 'component':'eth0',
'summary':'eth0 is down','severity':4, 'eventClass':'/Net'}
>>> serv.sendEvent(evt)

8
Окрошка из операционных систем
Введение
Быть системным администратором зачастую означает быть брошен
ным на съедение волкам. Правила, предварительное планирование
и даже выбор операционной системы часто находятся вне сферы ваше
го влияния. В настоящее время чтобы быть хотя бы маломальски эф
фективным системным администратором, вам необходимо знать их
все, мы имеем в виду все операционные системы. От Linux до Solaris,
Mac OS X и FreeBSD – все эти системы должны быть вам знакомы.
Только время покажет, продолжат ли свое существование такие па
тентованные операционные системы, как AIX или HPUX, но они все
еще необходимы многим людям.
К счастью, здесь нам на помощь опять приходит язык Python – мы на
деемся, что вы обратили внимание, что в состав языка входит полно
масштабная стандартная библиотека, которая способна удовлетворить
практически все потребности администраторов самых разнообразных
операционных систем. В составе стандартной библиотеки имеются мо
дули, которые позволят системному администратору реализовать все,
что ему необходимо, от архивирования каталогов и сравнения файлов
и каталогов до анализа конфигурационных файлов. Зрелость языка
Python вместе с его элегантностью и удобочитаемостью – причина то
го, что он не имеет себе равных в системном администрировании.
Во многих сложнейших областях человеческой деятельности, где тре
буются услуги системного администратора, таких как производство
мультипликационных фильмов или в вычислительных центрах, про
исходит отказ от применения языка Perl в пользу языка Python, пото
му что последний позволяет писать более элегантный и удобочитае
мый программный код. Язык Ruby – это достаточно интересный язык
программирования, в котором используются положительные особен

Кросс;платформенное программирование на языке Python в UNIX 279
ности языка Python, но, тем не менее, мощность стандартной библио
теки и широта возможностей языка Python дает ему преимущества пе
ред языком Ruby при использовании в качестве языка системного ад
министрирования.
В этой главе будут рассматриваться несколько операционных систем,
поэтому у нас не будет времени исследовать какуюлибо из них доста
точно глубоко, но мы углубимся настолько, чтобы показать, что Python
может играть роль как универсального кроссплатформенного языка
сценариев, так и уникального средства администрирования каждой из
операционных систем. Кроме того, на горизонте замаячила «новая
операционная система», которая обретает форму центра обработки
данных. Эта новая платформа получила название «(за)облачная» обра
ботка данных (Cloud Computing), и мы поговорим о том, что предлага
ют компании Amazon и Google.
Но довольно бездельничать и балагурить. С кухни потянуло чемто
восхитительным… это что, окрошка из операционных систем?
Кроссплатформенное программирование
на языке Python в UNIX
Хотя между разными UNIXподобными операционными системами
существуют некоторые значимые различия, но общего в них намного
больше. Один из способов примирить различные версии *nix состоит
в том, чтобы создавать кроссплатформенные инструменты и библио
теки, которые скрывают различия между операционными системами.
Основной способ добиться этого – использовать условные инструкции,
которые проверяют тип и версию операционной системы.
Язык Python неизменно следует философии «батарейки входят в ком
плект поставки» и предоставляет инструменты для решения практи
чески любой проблемы, с которой вы можете столкнуться. Для опреде
ления типа платформы, на которой выполняется ваш программный
код, существует модуль platform. Давайте поближе познакомимся с ос
новами использования этого модуля.
Самый простой способ познакомиться с возможностями модуля plat
form – написать сценарий, который будет выводить всю доступную ин
формацию о системе, как показано в примере 8.1.
Пример 8.1. Использование модуля platform для получения информации
о системе
#!/usr/bin/env python
import platform
profile = [
platform.architecture(),
platform.dist(),

280 Глава 8. Окрошка из операционных систем
platform.libc_ver(),
platform.mac_ver(),
platform.machine(),
platform.node(),
platform.platform(),
platform.processor(),
platform.python_build(),
platform.python_compiler(),
platform.python_version(),
platform.system(),
platform.uname(),
platform.version(),
]
for item in profile:
print item
Ниже приводится результат работы этого сценария в операционной
системе OS X Leopard 10.5.2:
[ngift@Macintosh 6][H:10879][J:0]% python cross_platform.py
('32bit', '')
('', '', '')
('', '')
('10.5.2', ('', '', ''), 'i386')
i386
Macintosh 6.local
Darwin 9.2.0 i386 32bit
i386
('r251:54863', 'Jan 17 2008 19:35:17')
GCC 4.0.1 (Apple Inc. build 5465)
2.5.1
Darwin
('Darwin', 'Macintosh 6.local', '9.2.0', 'Darwin Kernel Version 9.2.0:
Tue Feb 5 16:13:22 PST 2008; root:xnu 1228.3.13~1/
RELEASE_I386','i386','i386')
Darwin Kernel Version 9.2.0: Tue Feb 5 16:13:22 PST 2008;
root:xnu 1228.3.13~1/RELEASE_I386
Этот пример позволяет получить представление о том, какого рода ин
формация об операционной системе нам доступна. Следующий шаг на
пути к созданию кроссплатформенного программного кода состоит
в необходимости создать модуль fingerprint, который будет «брать от
печатки пальцев», определяя, на какой платформе, с каким номером
версии он выполняется. В следующем примере мы «взяли отпечатки
пальцев» у следующих операционных систем: Mac OS X, Ubuntu, Red
Hat/CentOS, FreeBSD и SunOS. Взгляните на пример 8.2.
Пример 8.2. Определение типа операционной системы
#!/usr/bin/env python
import platform

Кросс;платформенное программирование на языке Python в UNIX 281
"""
Определение принадлежности к одной из следующих операционных систем:
* Mac OS X
* Ubuntu
* Red Hat/Cent OS
* FreeBSD
* SunOS
"""
class OpSysType(object):
"""Определяет тип ОС с помощью модуля platform"""
def __getattr__(self, attr):
if attr == "osx":
return "osx"
elif attr == "rhel":
return "redhat"
elif attr == "ubu":
return "ubuntu"
elif attr == "fbsd":
return "FreeBSD"
elif attr == "sun":
return "SunOS"
elif attr == "unknown_linux":
return "unknown_linux"
elif attr == "unknown":
return "unknown"
else:
raise AttributeError, attr
def linuxType(self):
"""Определяет разновидность Linux с помощью различных методов"""
if platform.dist()[0] == self.rhel:
return self.rhel
elif platform.uname()[1] == self.ubu:
return self.ubu
else:
return self.unknown_linux
def queryOS(self):
if platform.system() == "Darwin":
return self.osx
elif platform.system() == "Linux":
return self.linuxType()
elif platform.system() == self.sun:
return self.sun
elif platform.system() == self.fbsd:
return self.fbsd
def fingerprint():
type = OpSysType()
print type.queryOS()
if __name__ == "__main__":
fingerprint()

282 Глава 8. Окрошка из операционных систем
Теперь посмотрим, что выводит этот модуль при запуске на различных
платформах.
Red Hat:
[root@localhost]/# python fingerprint.py
redhat
Ubuntu:
root@ubuntu:/# python fingerprint.py
ubuntu
Solaris 10 или SunOS:
bash 3.00# python fingerprint.py
SunOS
FreeBSD:
# python fingerprint.py
FreeBSD
Хотя в выводе этой команды не содержится ничего особенно интерес
ного, но в действительности она предоставляет нам очень мощный ин
струмент. Этот простой модуль позволит нам писать кроссплатфор
менный программный код, так как мы, например, можем определить
словарь с типами этих операционных систем и при нахождении соот
ветствия выполнять соответствующий платформозависимый про
граммный код. Одним из примеров получения самой ощутимой выго
ды от использования приемов кроссплатформенного программирова
ния могут служить сценарии, используемые для администрирования
сети посредством применения ssh с ключами. В этом случае программ
ный код может работать на многих платформах и давать непротиворе
чивые результаты.
Использование SSH с ключами, каталога NFS
и кроссплатформенных сценариев Python
для управления системами
Один из способов управления инфраструктурой из компьютеров, рабо
тающих под управлением разнотипных систем *nix, заключается в ис
пользовании ssh с ключами, общего каталога, монтируемого как том
NFS, и кроссплатформенного программного кода на языке Python.
Разобьем этот процесс на несколько шагов, чтобы было понятнее:
Шаг 1: создать открытый ключ ssh в системе, откуда будет выполнять
ся администрирование. Обратите внимание: для разных платформ эта
процедура может существенно отличаться. За подробностями обра
щайтесь к документации по операционной системе и к справочному
руководству по команде ssh. Создание ключа демонстрируется в при
мере 8.3.

Кросс;платформенное программирование на языке Python в UNIX 283
Примечание к примеру ниже: с целью демонстрации мы созда
ли ключ для пользователя root, однако для обеспечения более
высокого уровня безопасности было бы лучше создать учетную
запись пользователя, для которого определить привилегии sudo
на запуск только этого сценария.
Пример 8.3. Создание открытого ключа ssh
[ngift@Macintosh 6][H:11026][J:0]% ssh keygen t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
6c:2f:6e:f6:b7:b8:4d:17:05:99:67:26:1c:b9:74:11 root@localhost.localdomain
[ngift@Macintosh 6][H:11026][J:0]%
Шаг 2: Скопировать открытый ключ на администрируемые машины
исоздать файл authorized_keys, как показано в примере 8.4.
Пример 8.4. Копирование открытого ключа ssh
[ngift@Macintosh 6][H:11026][J:0]% scp id_leop_lap.pub root@10.0.1.51:~/
.ssh/
root@10.0.1.51’s password:
id_leop_lap.pub
100% 403 0.4KB/s 00:00
[ngift@Macintosh 6][H:11027][J:0]% ssh root@10.0.1.51
root@10.0.1.51’s password:
Last login: Sun Mar 2 06:26:10 2008
[root@localhost]~# cd .ssh
[root@localhost]~/.ssh# ll
total 8
rw r r 1 root root 403 Mar 2 06:32 id_leop_lap.pub
rw r r 1 root root 2044 Feb 14 05:33 known_hosts
[root@localhost]~/.ssh# cat id_leop_lap.pub > authorized_keys
[root@localhost]~/.ssh# exit
Connection to 10.0.1.51 closed.
[ngift@Macintosh 6][H:11028][J:0]% ssh root@10.0.1.51
Last login: Sun Mar 2 06:32:22 2008 from 10.0.1.3
[root@localhost]~#
Шаг 3: смонтировать общий каталог NFS, содержащий модули, кото
рые потребуется запускать на стороне клиентов. Часто самый простой
способ добиться этого заключается в использовании функции autofs
и в последующем создании символической ссылки. Однако то же са
мое можно реализовать на основе системы управления версиями, с по
мощью которой через ssh обновлять локальные репозитарии SVN
с программным кодом на администрируемых машинах. После таких

284 Глава 8. Окрошка из операционных систем
обновлений сценарии будут использовать самые свежие версии моду
лей. Например, в системе на базе дистрибутива Red Hat можно было
бы выполнить, например, такую команду:
ln s /net/nas/python/src /src
Шаг 4: написать сценарий, который будет запускать программный код
на удаленных машинах. Теперь, когда у нас имеются ключи ssh и смон
тированный каталог NFS (или каталог, находящийся под контролем
системы управления версиями), это достаточно простая задача. Как
обычно, начнем с примера наиболее простого сценария, выполняюще
го администрирование удаленных систем через ssh. Если ранее вам ни
когда не приходилось делать ничего подобного, вас наверняка удивит,
насколько просто можно выполнять достаточно сложные действия.
В примере 8.5 реализован запуск простой команды uname.
Пример 8.5. Простой управляющий сценарий
#!/usr/bin/env python
import subprocess
"""
Система управления на основе ssh
"""
machines = ["10.0.1.40",
"10.0.1.50",
"10.0.1.51",
"10.0.1.60",
"10.0.1.80"]
cmd = "uname"
for machine in machines:
subprocess.call("ssh root@%s %s" % (machine, cmd), shell=True)
Выполнив этот сценарий на пяти машинах с указанными IPадресами,
которые работают под управлением CentOS 5, FreeBSD 7, Ubuntu 7.1
и Solaris 10, мы получили следующие результаты:
[ngift@Macintosh 6][H:11088][J:0]% python dispatch.py
Linux
Linux
Linux
SunOS
FreeBSD
Однако у нас имеется модуль, более точно определяющий тип опера
ционной системы, поэтому используем его для получения более точно
го описания машин, которым посылаются команды, для чего создадим
временный каталог src на каждой удаленной машине и скопируем ту
да наш программный код. Конечно, после создания управляющего
сценария становится очевидной потребность в устойчивом интерфейсе
командной строки к нему, так как в противном случае нам придется

Кросс;платформенное программирование на языке Python в UNIX 285
изменять сам сценарий, чтобы выполнить какуюнибудь другую ко
манду, как показано ниже:
cmd = "mkdir /src"
или:
cmd = "python /src/fingerprint.py"
или даже:
subprocess.call("scp fingerprint.py root@%s:/src/" % machine, shell=True)
Мы сделаем это, как только запустим наш сценарий fingerprint.py, но
сначала посмотрим на новую команду:
#!/usr/bin/env python
import subprocess
"""
Система управления на основе ssh
"""
machines = ["10.0.1.40",
"10.0.1.50",
"10.0.1.51",
"10.0.1.60",
"10.0.1.80"]
cmd = "python /src/fingerprint.py"
for machine in machines:
subprocess.call("ssh root@%s %s" % (machine, cmd), shell=True)
А теперь посмотрим, что получилось:
[ngift@Macintosh 6][H:11107][J:0]# python dispatch.py
redhat
ubuntu
redhat
SunOS
FreeBSD
Благодаря модулю fingerprint.py результаты выглядят намного луч
ше. Безусловно, несколько строк в нашем управляющем сценарии тре
буют кардинальной перестройки, потому что в противном случае нам
всякий раз будет необходимо редактировать его. Нам требуется более
удобный инструмент, поэтому давайте создадим его.
Создание кроссплатформенного
инструмента управления
Решение об использовании ключей ssh в соединении с простой систе
мой управления оказалось недостаточно удобным, потому что его
сложно расширять или повторно использовать. Попробуем определить
перечень проблем, характерных для предыдущего инструмента, а затем

286 Глава 8. Окрошка из операционных систем
составим список требований, устраняющих эти проблемы. Проблемы:
список администрируемых машин определяется жестко, в самом сце
нарии; выполняемая команда жестко задана в сценарии; допускается
запуск только одной команды за раз; один и тот же набор команд вы
полняется на всех машинах, мы лишены возможности выбора; наш
управляющий сценарий ожидает, пока не будет получен ответ на каж
дую команду. Требования: нам необходим инструмент командной
строки, который будет получать IPадреса и команды, которые надле
жит выполнить, из конфигурационного файла; нам необходим интер
фейс командной строки с параметрами, чтобы можно было передавать
команды машинам; нам необходим инструмент управления, который
будет запускать команды в отдельных потоках выполнения, чтобы не
блокировать процесс.
Похоже, что нам необходимо выработать элементарный синтаксис
конфигурационного файла с разделом для машин и с разделом для ко
манд. Взгляните на пример 8.6.
Пример 8.6. Конфигурационный файл для управляющего сценария
[MACHINES]
CENTOS: 10.0.1.40
UBUNTU: 10.0.1.50
REDHAT: 10.0.1.51
SUN: 10.0.1.60
FREEBSD: 10.0.1.80
[COMMANDS]
FINGERPRINT : python /src/fingerprint.py
Теперь нам необходимо написать функцию, которая будет читать со
держимое конфигурационного файла и выделять разделы MACHINES
и COMMANDS, чтобы можно было выполнять обход этих разделов по
очереди, как показано в примере 8.7.
Следует заметить, что команды из конфигурационного файла
будут импортироваться в случайном порядке. В большинстве
случаев это может оказаться неприемлемым и, возможно, было
бы лучше просто написать модуль на языке Python, который бу
дет играть роль конфигурационного файла.
Пример 8.7. Улучшенный сценарий управления
#!/usr/bin/env python
import subprocess
import ConfigParser
"""
Система управления на основе ssh
"""
def readConfig(file="config.ini"):
"""
Извлекает IP адреса и команды из конфигурационного файла

Кросс;платформенное программирование на языке Python в UNIX 287
и возвращает кортеж
"""
ips = []
cmds = []
Config = ConfigParser.ConfigParser()
Config.read(file)
machines = Config.items("MACHINES")
commands = Config.items("COMMANDS")
for ip in machines:
ips.append(ip[1])
for cmd in commands:
cmds.append(cmd[1])
return ips, cmds
ips, cmds = readConfig()
#Выполнить все команды для каждого IP адреса
for ip in ips:
for cmd in cmds:
subprocess.call("ssh root@%s %s" % (ip, cmd), shell=True)
Эти несложные изменения повысили удобство использования. Мы мо
жем произвольно изменять список команд и машин и выполнять сразу
все команды. Если теперь взглянуть на вывод сценария, можно убе
диться, что он не изменился:
[ngift@Macintosh 6][H:11285][J:0]# python advanced_dispatch1.py
redhat
redhat
ubuntu
SunOS
FreeBSD
Хотя это весьма усовершенствованный инструмент, у нас попрежне
му отсутствует механизм выполнения команд в отдельных потоках,
наличие которого определено в нашей спецификации. К счастью, мы
можем воспользоваться некоторыми приемами, описанными в главе,
посвященной процессам, и легко реализовать многопоточный режим
выполнения. В примере 8.8 показано, что для этого можно сделать.
Пример 8.8. Многопоточный инструмент управления командами
#!/usr/bin/env python
import subprocess
import ConfigParser
from threading import Thread
from Queue import Queue
import time
"""
Многопоточная система управления на основе ssh
"""
start = time.time()
queue = Queue()

288 Глава 8. Окрошка из операционных систем
def readConfig(file="config.ini"):
"""
Извлекает IP адреса и команды из конфигурационного файла
и возвращает кортеж
"""
ips = []
cmds = []
Config = ConfigParser.ConfigParser()
Config.read(file)
machines = Config.items("MACHINES")
commands = Config.items("COMMANDS")
for ip in machines:
ips.append(ip[1])
for cmd in commands:
cmds.append(cmd[1])
return ips, cmds
def launcher(i,q, cmd):
"""Запускает команды в потоке выполнения, отдельном для каждого IP"""
while True:
#Получить ip, cmd из очереди
ip = q.get()
print "Thread %s: Running %s to %s" % (i, cmd, ip)
subprocess.call("ssh root@%s %s" % (ip, cmd), shell=True)
q.task_done()
#Получить IP адреса и команды из конфигурационного файла
ips, cmds = readConfig()
#Определить количество используемых потоков, но не более 25
if len(ips) < 25:
num_threads = len(ips)
else:
num_threads = 25
#Запустить потоки
for i in range(num_threads):
for cmd in cmds:
worker = Thread(target=launcher, args=(i, queue,cmd))
worker.setDaemon(True)
worker.start()
print "Main Thread Waiting"
for ip in ips:
queue.put(ip)
queue.join()
end = time.time()
print "Dispatch Completed in %s seconds" % end – start
Если теперь взглянуть на вывод нового многопоточного механизма
управления, можно заметить, что на выполнение всех команд потребо
валось около 1,2 секунды. Чтобы увидеть различия в скорости выпол

Кросс;платформенное программирование на языке Python в UNIX 289
нения, нам следует добавить измерение времени в оригинальный
управляющий сценарий и сравнить полученные результаты:
[ngift@Macintosh 6][H:11296][J:0]# python threaded_dispatch.py
Main Thread Waiting
Thread 1: Running python /src/fingerprint.py to 10.0.1.51
Thread 2: Running python /src/fingerprint.py to 10.0.1.40
Thread 0: Running python /src/fingerprint.py to 10.0.1.50
Thread 4: Running python /src/fingerprint.py to 10.0.1.60
Thread 3: Running python /src/fingerprint.py to 10.0.1.80
redhat
redhat
ubuntu
SunOS
FreeBSD
Dispatch Completed in 1 seconds
После добавления в оригинальный управляющий сценарий простого
программного кода, выполняющего замер времени, мы получили сле
дующее:
[ngift@Macintosh 6][H:11305][J:0]# python advanced_dispatch1.py
redhat
redhat
ubuntu
SunOS
FreeBSD
Dispatch Completed in 3 seconds
Исходя из этих результатов, можно сказать, что наша многопоточная
версия оказалась примерно в три раза быстрее. Если бы мы использова
ли этот инструмент для опроса сети, скажем, из 500 машин, а не из 5,
разница в производительности могла бы оказаться еще более сущест
венной. Пока разработка нашего кроссплатформенного инструмента
управления продвигается достаточно успешно, поэтому сделаем сле
дующий шаг и создадим кроссплатформенный инструмент сборки.
Следует заметить, что для реализации этого сценария, возмож
но, более удачным решением было бы использование многоза
дачной версии IPython. За подробностями обращайтесь по адре
су: http://ipython.scipy.org/moin/Parallel_Computing.
Создание кроссплатформенного инструмента сборки
Мы уже знаем, как распределять работу между несколькими машина
ми, как определять тип операционной системы, под управлением ко
торой выполняется сценарий, и, наконец, создавать универсальную
декларацию с помощью менеджера пакетов EPM, который способен
создавать специализированные пакеты в зависимости от типа опера
ционной системы. Почему бы нам не объединить все это вместе? Мы

290 Глава 8. Окрошка из операционных систем
можем использовать эти три приема, чтобы создать кроссплатфор
менный инструмент сборки.
С появлением технологии виртуальных машин стало весьма просто
создавать виртуальные машины для любых свободно распространяе
мых UNIXподобных операционных систем, таких как Debian/Ubuntu,
Red Hat/CentOS, FreeBSD и Solaris. Теперь, создав инструмент, кото
рый вы хотите сделать доступным всему миру или просто коллегам
в вашей компании, вы легко и просто можете создать «сборочный цех»,
возможно даже на своем ноутбуке, где ведется разработка сценария,
и создавать специализированные пакеты сразу же с этим сценарием.
Как это будет работать? Самый простой способ достичь этого – создать
общее дерево сборки пакета на разделе NFS и предоставить доступ
к этой точке монтирования всем серверам сборки. После этого, исполь
зуя инструменты, созданные нами ранее, настроить серверы на сборку
пакетов в каталоге NFS. Менеджер пакетов EPM позволяет создавать
простые декларации, или «списки» файлов, кроме того, у нас имеется
модуль fingerprint, поэтому самое сложное уже позади. Осталось на
писать программный код, который делает только то, что осталось.
Ниже показано, как может выглядеть сценарий сборки:
#!/usr/bin/env python
from fingerprint import fingerprint
from subprocess import call
os = fingerprint()
#Получить корректный ключ для EPM
epm_keyword = {"ubuntu":"dpkg", "redhat":"rpm", "SunOS":"pkg", "osx":"osx"}
try:
epm_keyword[os]
except Exception, err:
print err
subprocess.call("epm f %s helloEPM hello_epm.list" %
platform_cmd,shell=True)
Теперь можно отредактировать конфигурационный файл config.ini, пе
реориентировав его на запуск нового сценария.
[MACHINES]
CENTOS: 10.0.1.40
UBUNTU: 10.0.1.50
REDHAT: 10.0.1.51
SUN: 10.0.1.60
FREEBSD: 10.0.1.80
[COMMANDS]
FINGERPRINT = python /src/create_package.py
Теперь осталось только запустить многопоточную версию инструмента
сборки и – эврика! – нам удалось создать пакеты для CentOS, Ubuntu,

PyInotify 291
Red Hat, FreeBSD и Solaris за считанные секунды. Этот сценарий еще
нельзя рассматривать как окончательную рабочую версию программ
ного кода, так как в нем отсутствует обработка ошибок, но он прекрас
но демонстрирует, что позволяет сделать язык Python на скорую руку,
всего за несколько минут или часов.
PyInotify
Если вам выпадет честь работать с платформами GNU/Linux, вы по
достоинству оцените возможности PyInotify. Согласно документации
это: «модуль Python для обнаружения изменений в файловой систе
ме». Официальная страница проекта находится по адресу http://pyino>
tify.sourceforge.net.
В примере 8.9 показано, как работать с этим модулем.
Пример 8.9. Сценарий слежения за событиями с помощью модуля Pyinotify
import os
import sys
from pyinotify import WatchManager, Notifier, ProcessEvent, EventsCodes
class PClose(ProcessEvent):
"""
Обработка события закрытия
"""
def __init__(self, path):
self.path = path
self.file = file
def process_IN_CLOSE(self, event):
"""
Обработка событий 'IN_CLOSE_*'
может принимать функцию обработчик
"""
path = self.path
if event.name:
self.file = "%s" % os.path.join(event.path, event.name)
else:
self.file = "%s" % event.path
print "%s Closed" % self.file
print "Performing pretend action on %s...." % self.file
import time
time.sleep(2)
print "%s has been processed" % self.file
class Controller(object):
def __init__(self, path='/tmp'):
self.path = path
def run(self):
self.pclose = PClose(self.path)

292 Глава 8. Окрошка из операционных систем
PC = self.pclose
# следить только за этими событиями
mask = EventsCodes.IN_CLOSE_WRITE | EventsCodes.IN_CLOSE_NOWRITE
# экземпляр менеджера слежения за событиями
wm = WatchManager()
notifier = Notifier(wm, PC)
print 'monitoring of %s started' % self.path
added_flag = False
# читать и обрабатывать события
while True:
try:
if not added_flag:
# на первой итерации добавить слежение за каталогом:
# обрабатываемые события определяются маской.
wm.add_watch(self.path, mask)
added_flag = True
notifier.process_events()
if notifier.check_events():
notifier.read_events()
except KeyboardInterrupt:
# ...пока не будет нажата комбинация Ctrl C
print 'stop monitoring...'
# прекратить слежение за событиями
notifier.stop()
break
except Exception, err:
# продолжить слежение
print err
def main():
monitor = Controller()
monitor.run()
if __name__ == '__main__':
main()
Если запустить этот сценарий, он начнет выполнять «требуемые» дей
ствия при помещении чего бы то ни было в каталог /tmp. Этот пример
должен дать вам некоторое представление о том, как фактически сде
лать чтонибудь полезное, например, добавить функцию обратного вы
зова для выполнения требуемых действий. Здесь же можно было ис
пользовать часть программного кода из главы «Данные», например,
для автоматического поиска и удаления дубликатов или для архиви
рования файлов, если их имена соответствуют определяемому вами
критерию в функции fnmatch(). В общем, это интересный и полезный
модуль Python, который работает только в Linux.

OS X 293
OS X
OS X является довольно экзотической операционной системой, если не
сказать больше. С одной стороны, она обладает, пожалуй, самым луч
шим пользовательским интерфейсом Cocoa, а с другой, в версии Leo
pard, она является полностью POSIXсовместимой операционной сис
темой UNIX. Системе OS X удалось добиться того, чего не удалось ни
одному производителю операционных систем UNIX: она вывела UNIX
на уровень массового потребления. Версия OS X Leopard включает в се
бя Python 2.5.1, Twisted и многие другие замечательные программные
компоненты на языке Python.
Разработчики системы OS X следуют несколько странной парадигме,
предлагая операционную систему и в серверном, и в обычном исполне
нии. Хотя, безусловно, компания Apple имеет на это полное право,
возможно, ей стоит отказаться от такой архаичной идеи; мы же здесь
не будем обсуждать плюсы и минусы концепции единой ОС по единой
цене. Серверная версия операционной системы предлагает более пол
ный комплект инструментов командной строки для администрирова
ния, а также ряд компонентов, характерных для Apple, таких как воз
можность загрузки по сети, возможность работы с серверами катало
гов LDAP, и многие другие особенности.
Взаимодействие с DSCL, утилитой службы каталогов
Название DSCL происходит от Directory Services Command Line (ко
мандная строка службы каталогов) и представляет собой удобный спо
соб доступа к прикладному интерфейсу службы каталогов в OS X.
DSCL позволяет читать, создавать и удалять записи, что язык Python
позволяет делать естественным образом. В примере 8.10 демонстриру
ется взаимодействие с DSCL в оболочке IPython для чтения атрибутов
службы Open Directory и их значений.
Обратите внимание, что в этом примере мы только читаем зна
чения атрибутов, но точно так же, используя тот же прием,
можно было бы организовать и их изменение.
Пример 8.10. Получение записи пользователя в интерактивной оболочке
IPython с помощью DSCL
In [40]: import subprocess
In [41]: p = subprocess.Popen("dscl . read /Users/ngift",
shell=True,stdout=subprocess.PIPE)
In [42]: out = p.stdout.readlines()
In [43]: for line in out:
line.strip().split()
Out[46]: ['NFSHomeDirectory:', '/Users/ngift']

294 Глава 8. Окрошка из операционных систем
Out[46]: ['Password:', '********']
Out[46]: ['Picture:']
Out[46]: ['/Library/User', 'Pictures/Flowers/Sunflower.tif']
Out[46]: ['PrimaryGroupID:', '20']
Out[46]: ['RealName:', 'ngift']
Out[46]: ['RecordName:', 'ngift']
Out[46]: ['RecordType:', 'dsRecTypeStandard:Users']
Out[46]: ['UniqueID:', '501']
Out[46]: ['UserShell:', '/bin/zsh']
Это замечательно, что в Apple организовали централизованное управле
ние локальными учетными записями и учетными записями LDAP/Ac
tive Directory с помощью команды dscl. Утилита dscl – это как глоток
свежего воздуха по сравнению с другими средствами управления LDAP,
даже если вынести использование Python за скобки. У нас недостаточно
места, чтобы углубляться в подробности. Тем не менее, заметим, что
с помощью языка Python и утилиты dscl можно очень легко организо
вать программное управление учетными записями как в локальной ба
зе данных, так и в базе данных LDAP, такой как Open Directory, а пре
дыдущий пример должен показать вам, с чего следует начинать.
Взаимодействие с прикладным интерфейсом OS X
Часто администратору OS X бывает необходимо знать, как организо
вать взаимодействие с фактическим интерфейсом пользователя. В OS X
Leopard для языков Python и Ruby предоставляется доступ к механиз
му Scripting Bridge. За дополнительной информацией по этому меха
низму обращайтесь по адресу http://developer.apple.com/documentation/
Cocoa/Conceptual/RubyPythonCocoa/Introduction/Introduction.html.
Как вариант, для доступа к OSA, или Open Scripting Architecture (от
крытая архитектура сценариев), можно использовать модуль py
–app
script со страницей проекта по адресу http://sourceforge.net/projects/
appscript.
Работать с модулем py
–appscript – одно удовольствие, так как он дает
возможность из языка Python взаимодействовать с очень богатой воз
можностями архитектурой OSA. Но прежде чем познакомится с ним
поближе, мы сначала воспользуемся простым инструментом команд
ной строки osascript, на примере которого продемонстрируем, как
можно организовать взаимодействие с прикладным интерфейсом сце
нариев. В OS X Leopard теперь имеется возможность писать инстру
менты командной строки, работающие под управлением osascript,
и выполнять их как обычные сценарии Bash или Python. Давайте на
пишем сценарий с именем bofh.osa, как показано ниже, и затем запус
тим его. Текст сценария приводится в примере 8.11.
Пример 8.11. Сценарий «Hello, Bastard Operator From Hell»
#!/usr/bin/osascript
say "Hello, Bastard Operator From Hell" using "Zarvox"

OS X 295
Если запустить этот сценарий из командной строки, механический го
лос поприветствует нас. Это немножко глупо, но это же Mac OS X; она
вполне допускает такие вещи.
А теперь погрузимся в использование модуля appscript для доступа
к тому же самому API из сценариев на языке Python, но сделаем это
в интерактивном режиме, в оболочке IPython. Ниже представлена инте
рактивная версия примера, включенного в исходные тексты appscript,
который выводит список всех запущенных процессов в алфавитном
порядке:
In [4]: from appscript import app
In [5]: sysevents = app('System Events')
In [6]: processnames = sysevents.application_processes.name.get()
In [7]: processnames.sort(lambda x, y: cmp(x.lower(), y.lower()))
In [8]: print '\n'.join(processnames)
Activity Monitor
AirPort Base Station Agent
AppleSpell
Camino
DashboardClient
DashboardClient
Dock
Finder
Folder Actions Dispatcher
GrowlHelperApp
GrowlMenu
iCal
iTunesHelper
JavaApplicationStub
loginwindow
mdworker
PandoraBoy
Python
quicklookd
Safari
Spotlight
System Events
SystemUIServer
Terminal
TextEdit
TextMate
Если вам придется решать задачи автоматизации с применением при
ложений OS X, модуль appscript окажется для вас удачной находкой,
так как с его помощью в языке Python можно реализовать такие дейст
вия, которые ранее были возможны только в языке Applescript. Ноа
Гифт (Noah Gift) написал статью, в которой немного рассказывается

296 Глава 8. Окрошка из операционных систем
об этом: http://www.macdevcenter.com/pub/a/mac/2007/05/08/using>
python>and>applescript>to>get>the>most>out>of>your>mac.html.
Коечто системный администратор может выполнять с помощью Final
Cut Pro, создавая пакеты операций, взаимодействующих, например,
с Adobe After Effects. Кроме того, в OS X с помощью Applescript Studio
можно быстро создать графический интерфейс и вызывать из него сце
нарий на языке Python командой do shell script. Мало кому известно,
что оригинальная версия Carbon Copy Cloner была написана в App
lescript Studio. Если у вас есть свободное время, вам стоит познако
миться с этой средой поближе.
Автоматическое восстановление системы
ASR – это еще один революционный, опередивший время инструмент
командной строки, разработанный для OS X. Этот инструмент являет
ся ключевым компонентом очень популярной утилиты с именем Car
bon Copy Cloner и служит для автоматизации многих ситуаций. Ноа
(Noah) использовал утилиту asr в паре с Netboot для автоматического
восстановления – фактически он ввел полную автоматизацию этого
процесса в одном из мест, где он работал. Пользователю достаточно
было просто перезагрузить свою машину и удерживать клавишу «N»,
чтобы перейти в режим загрузки по сети, и в результате либо наступал
«конец игры», либо машина сама исправляла повреждения.
Пожалуйста, не рассказывайте об этом никому, потому что многие до
сих пор думают, что он все еще работает там. Ниже, в примере 8.12
приводится упрощенная версия сценария для автоматического восста
новления системы, который может быть запущен при загрузке по сети
или со второго раздела жесткого диска. С точки зрения настроек, ката
лог /Users, как и любой другой жизненно важный каталог, должен
быть символической ссылкой, ведущей в другой раздел, или должен
находиться в сети, что еще лучше. Смотрите пример 8.12.
Пример 8.12. Сценарий автоматического восстановления раздела жесткого
диска в OS X, демонстрирующий ход выполнения с помощью
виджета из библиотеки WXPython
#!/usr/bin/env pythonw
#автоматически восстанавливает раздел жесткого диска
import subprocess
import os
import sys
import time
from wx import PySimpleApp, ProgressDialog, PD_APP_MODAL, PD_ELAPSED_TIME
#команда пересоздания главного раздела с помощью утилиты asr
asr = '/usr/sbin/asr source '
#переменные, содержащие различные пути
os_path = '/Volumes/main’

OS X 297
ipath = '/net/server/image.dmg '
dpath = ' target /Volumes/main erase noprompt noverify &'
reimage_cmd = "%s%s%s" % (asr,ipath, dpath)
#Команды перезагрузки
reboot = 'reboot'
bless = '/usr/sbin/bless folder /Volumes/main/System/Library/CoreServices
setOF'
#часть использования wxpython
application = PySimpleApp()
dialog = ProgressDialog ('Progress', 'Attempting Rebuild of Main Partition',
maximum = 100, style = PD_APP_MODAL | PD_ELAPSED_TIME)
def boot2main():
"""Делает новый раздел загружаемым и выполняет перезагрузку"""
subprocess.call(bless, shell=True)
subprocess.call(reboot, shell=True)
def rebuild():
"""Пересоздает раздел"""
try:
time.sleep(5) #Дать диалогу время на запуск
subprocess.call(reimage_cmd)
except OSError:
print "CMD: %s [ERROR: invalid path]" % reimage_cmd
sys.exit(1)
time.sleep(30)
while True:
if os.path.exists(os_path):
x = 0
wxSleep(1)
dialog.Update (x + 1,
"Rebuild is complete...\n rebooting to main partition\n
...in 5 seconds..")
wxSleep(5)
print "repaired volume.." + os_path
boot2main() #вызывает функции reboot/bless
break
else:
x = 0
wxSleep(1)
dialog.Update ( x + 1, 'Reimaging.... ')
def main():
if os.path.exists(os_path):
rebuild()
else:
print "Could not find valid path...FAILED.."
sys.exit(1)
if __name__ == "__main__":
main()

298 Глава 8. Окрошка из операционных систем
Этот сценарий пытается пересоздать раздел и выводит средствами биб
лиотеки WXPython индикатор хода выполнения. Если путь указан
корректно и не обнаружено ошибок, выполняется пересоздание разде
ла жесткого диска с помощью команды asr, в процессе выполнения ко
торой выводится индикатор, показывающий ход выполнения опера
ции, затем новый раздел назначается загружаемым с помощью коман
ды bless, после чего выполняется перезагрузка машины.
Этот сценарий легко можно превратить в основу системы управления
и распространения дистрибутива системы уровня предприятия, по
скольку достаточно легко организовать установку различных образов,
основываясь на данных об аппаратной комплектации или даже считы
вая «старую» метку жесткого диска. После этого можно, например,
организовать программную установку пакетов программного обеспе
чения с помощью системы управления пакетами в OS X или с помо
щью свободно распространяемого инструмента radmind. Ноа (Noah)
реализовал один интересный сценарий, в котором сначала в автомати
ческом режиме развертывал базовую систему OS X, а затем завершал
установку остальных пакетов с помощью radmind.
Если вы всерьез собираетесь заниматься администрированием систем
OS X, то вам определенно стоило бы поближе познакомиться с radmind.
Radmind – это своего рода система автоматического обновления, кото
рая обнаруживает изменения в файловой системе и обеспечивает воз
можность восстановления машин на основе этих изменений. Дополни
тельную информацию о radmind вы найдете на странице http://
rsug.itd.umich.edu/software/radmind/. Несмотря на то, что программа
radmind написана не на языке Python, ее легко можно было бы пере
писать на этом языке.
Управление файлами Plist из сценариев на языке Python
В главе 3 мы выполняли анализ потока информации в формате XML,
генерируемого утилитой system_profiler, используя для этого библио
теку ElementTree. Но в OS X в Python встроена поддержка библиотеки
plistlib, которая позволяет анализировать и создавать файлы Plist.
Сам модуль тоже называется plistlib. У нас нет возможности проде
монстрировать этот модуль на примерах, но вам стоит познакомиться
с ним поближе самостоятельно.
Администрирование систем Red Hat Linux
В Red Hat язык Python используется очень широко – и в компании,
и в операционной системе. Некоторые из наиболее интересных новых
способов использования Python родились в группе Emerging Technolo
gies: http://et.redhat.com/page/Main_Page. Ниже приводится список
некоторых проектов, использующих язык Python:
•Libvert – API виртуализации менеджера виртуальных машин

Администрирование Ubuntu 299
•VirtInst – приложение управления виртуальными машинами на ба
зе библиотеки libvirt 1, написанное на языке Python + PyGTK
•Библиотека Python, упрощающая инициализацию гостевых вирту
альных машин на основе libvirt
•Cobbler – продукт, позволяющий создавать полностью автоматизи
рованные серверы загрузки для нужд PXE и виртуализации
•VirtFactory: сетевая среда управления виртуальными приложе
ниями
•FUNC (Fedora Unified Network Controller)
Администрирование Ubuntu
Можно сказать, что из всех основных дистрибутивов Linux Ubuntu яв
ляется одним из самых влюбленных в Python. Отчасти потому, что
Марк Шаттлворт (Mark Shuttleworth), создатель дистрибутива, долгое
время – с начала 90 годов – работал с языком Python. Одним из заме
чательных источников пакетов на языке Python для Ubuntu является
Launchpad: http://launchpad.net.
Администрирование систем Solaris
С конца 90х до начала 2000х годов операционная система Solaris за
нимала практически непоколебимое место в мире UNIX. В начале
2000х годов Linux подобно метеориту врезался в Solaris, в связи с чем
компании Sun пришлось испытать вполне реальные неприятности. Од
нако с недавнего времени все больше системных администраторов, раз
работчиков и предпринимателей снова начинают говорить о Solaris.
Из наиболее интересных нововведений, которые предполагает внести
Sun, можно назвать 6месячный цикл выпуска новых версий системы,
так же, как и в Ubuntu, с 18месячным периодом технической под
держки; отказ от создания объемного дистрибутива на DVDдиске
в пользу единственного CD, как в Ubuntu. Наконец, были заимствова
ны некоторые идеи из Red Hat и Fedora по созданию версии Solaris,
разрабатываемой сообществом. Загрузить или заказать загрузочный
CD можно по адресу: http://www.opensolaris.com.
Что все это означает для системного администратора, использующего
язык Python? Интерес к Sun быстро растет, и у нее имеется большое
количество весьма интересных технологий, начиная от ZFS и заканчи
вая контейнерами и LDOM, которые в некотором смысле можно срав
нить с виртуальными машинами VMware. Имеется даже связь с этой
книгой. Интерпретатор Python прекрасно работает в операционной
1 libvert и libvirt – это разные библиотеки! – Прим. перев.

300 Глава 8. Окрошка из операционных систем
системе Solaris и даже широко используется в разработке системы
управления пакетами для нее.
Виртуализация
14 августа 2007 года состоялось первое открытое размещение акций
компании VMware, которое принесло ей миллионы долларов и укре
пило позиции виртуализации как крупного направления в развитии
информационных технологий. Предсказание будущего всегда было
рискованным делом, однако все чаще в крупных компаниях слышны
слова «операционная система центра обработки данных», и все, от Mi
crosoft до Red Hat и Oracle, стремятся не опоздать сесть в поезд виртуа
лизации. Можно смело сказать, что со временем виртуализация пол
ностью изменит центры обработки данных и работу системных адми
нистраторов. Виртуализация – это элементарный пример действия
слишком частого использования фразы «прорывная технология».
Виртуализация – это обоюдоострое оружие для системных админист
раторов, так как, с одной стороны, позволяет легко тестировать систе
мы и приложения, но, с другой стороны, чрезвычайно увеличивает
сложность администрирования. Теперь на одной машине одновремен
но может работать сразу несколько операционных систем, здесь могут
находиться приложения для малого бизнеса или крупная часть вычис
лительного центра. За эффективность приходится платить, а обеспече
ние эффективности – прямая обязанность среднего системного адми
нистратора.
Возможно, прямо сейчас, читая эти строки, вы могли бы подумать: ка
кое отношение все это имеет к языку Python? Самое непосредственное.
В компании Racemi, где недавно работал Ноа (Noah), на языке Python
было написано полноценное приложение управления центром обра
ботки данных, которое имеет дело с виртуализацией. Python может
и действительно очень тесно взаимодействует с механизмами виртуа
лизации, начиная от управления виртуальными машинами и заканчи
вая перемещением систем с физических машин на виртуальные, ис
пользуя для этого Python API. В этом виртуализованном мире Python
чувствует себя как дома и можно смело утверждать, что он будет иг
рать далеко не последнюю роль в будущей операционной системе цен
тра обработки данных.
VMware
Как уже говорилось выше, компания VMware является лидером по
разработке технологий виртуализации. Наличие полного программно
го контроля над виртуальной машиной – это своего рода Чаша Грааля.
К счастью, существует несколько API на выбор: Perl, XMLRPC, Py
thon и C. К моменту написания этих строк некоторые реализации Py
thon имели определенные ограничения, но такое положение дел могло

Облачная обработка данных 301
измениться. Похоже, что в VMware выбрали новое направление –
XMLRPC API.
Компания VMware выпускает несколько различных продуктов с раз
личными API. Из продуктов, с которыми вам может потребоваться
взаимодействовать, можно назвать VMware Site Recovery Manager,
VMware ESX Server, VMware Server и VMware Fusion.
У нас недостаточно места, чтобы охватить принципы взаимодействия
с этими технологиями, поскольку эта тема выходит далеко за рамки
данной книги, но они стоят того, чтобы следить за их развитием и за
тем, какую роль будет играть Python.
Облачная обработка данных
Толькотолько утихла шумиха вокруг виртуализации, как вдруг воз
ник шум об «облачной» обработке данных (cloud computing). Термин
«облачная обработка данных» обозначает технологию выделения вы
числительных ресурсов по требованию, в зависимости от величины ра
бочей нагрузки. В сфере развития технологии «облачной» обработки
данных присутствуют два крупных игрока – Amazon и Google. Бук
вально за несколько недель до передачи этой книги издателю компа
ния Google взорвала настоящую бомбу. Компания предложила инте
реснейшую «фишку», которая пока поддерживается только языком
Python. Поскольку эта книга посвящена языку Python, мы думаем,
что такое ограничение не слишком огорчит вас. В некотором смысле
такое предпочтение, отданное языку Python, напоминает нам рекламу
American Express.
В этом разделе мы пройдемся по некоторым имеющимся API, с кото
рыми вам придется столкнуться при работе с Amazon и с Google App
Engine. В заключение мы поговорим о том, как это может касаться
системных администраторов.
Вебслужбы Amazon на основе Boto
Отличную возможность для работы с инфраструктурой «облачной» об
работки данных Amazon предоставляет интерфейс Boto. Посредством
Boto обеспечивается доступ к таким службам, как Simple Storage Ser
vice, Simple Queue Service, Elastic Compute Cloud, Mechanical Turk,
SimpleDB. Это совершенно новый и очень мощный API, поэтому мы
рекомендуем заглянуть на домашнюю страницу проекта http://code.
google.com/p/boto/. Здесь вы сможете почерпнуть самую свежую ин
формацию, что лучше, чем приведение нами сведений, доступных на
данный момент.
Ниже приводится короткий пример взаимодействия со службой Sim
pleDB.
Соединение со службой:

302 Глава 8. Окрошка из операционных систем
In [1]: import boto
In [2]: sdb = boto.connect_sdb()
Создание нового домена:
In [3]: domain = sdb.create_domain('my_domain')
Добавление нового элемента:
In [4]: item = domain.new_item('item')
Примерно так выглядит API в настоящее время, но, чтобы получить
полное представление, вам необходимо взглянуть на примеры в репо
зитарии svn: http://code.google.com/p/boto/source/browse. Заметим,
что изучение примеров – это один из лучших способов понять, как ра
ботает новая библиотека.
Google App Engine
Служба Google App Engine выпущена в состоянии бетаверсии