Python
Python
Введение 1.1
О книге 1.1.1
Изменения в книге 1.1.2
Ресурсы для обучения по книге 1.1.3
Как учиться по этой книге 1.1.4
Пример плана обучения 1.1.5
FAQ 1.1.6
Благодарности 1.1.7
I. Основы Python 1.2
1. Подготовка к работе 1.2.1
ОС и редактор 1.2.1.1
Система управления пакетами pip 1.2.1.2
Виртуальные окружения 1.2.1.3
Интерпретатор Python 1.2.1.4
Дополнительные материалы 1.2.1.5
Задания 1.2.1.6
2. Использование Git и GitHub 1.2.2
Основы Git 1.2.2.1
Отображение статуса репозитория в приглашении 1.2.2.1.1
Работа с Git 1.2.2.1.2
Дополнительные возможности 1.2.2.1.3
Аутентификация на GitHub 1.2.2.2
Работа со своим репозиторием 1.2.2.3
Работа с репозиторием заданий и примеров 1.2.2.4
Дополнительные материалы 1.2.2.5
Задания 1.2.2.6
3. Начало работы с Python 1.2.3
Синтаксис Python 1.2.3.1
Интерпретатор Python. IPython 1.2.3.2
Магия IPython 1.2.3.2.1
2
Переменные 1.2.3.3
Задания 1.2.3.4
4. Типы данных в Python 1.2.4
Числа 1.2.4.1
Строки (Strings) 1.2.4.2
Полезные методы для работы со строками 1.2.4.2.1
Форматирование строк 1.2.4.2.2
Объединение литералов строк 1.2.4.2.3
Список (List) 1.2.4.3
Полезные методы для работы со списками 1.2.4.3.1
Варианты создания списка 1.2.4.3.2
Словарь (Dictionary) 1.2.4.4
Полезные методы для работы со словарями 1.2.4.4.1
Варианты создания словаря 1.2.4.4.2
Кортеж (Tuple) 1.2.4.5
Множество (Set) 1.2.4.6
Полезные методы для работы с множествами 1.2.4.6.1
Операции с множествами 1.2.4.6.2
Варианты создания множества 1.2.4.6.3
Преобразование типов 1.2.4.7
Проверка типов 1.2.4.8
Дополнительные материалы 1.2.4.9
Задания 1.2.4.10
5. Создание базовых скриптов 1.2.5
Передача аргументов скрипту 1.2.5.1
Ввод информации пользователем 1.2.5.2
Задания 1.2.5.3
6. Контроль хода программы 1.2.6
if/elif/else 1.2.6.1
for 1.2.6.2
Вложенные for 1.2.6.2.1
Совмещение for и if 1.2.6.2.2
while 1.2.6.3
break, continue, pass 1.2.6.4
3
for/else, while/else 1.2.6.5
Работа с исключениями try/except/else/finally 1.2.6.6
Дополнительные материалы 1.2.6.7
Задания 1.2.6.8
7. Работа с файлами 1.2.7
Открытие файлов 1.2.7.1
Чтение файлов 1.2.7.2
Запись файлов 1.2.7.3
Закрытие файлов 1.2.7.4
Конструкция with 1.2.7.5
Дополнительные материалы 1.2.7.6
Задания 1.2.7.7
8. Примеры использования основ 1.2.8
Распаковка переменных 1.2.8.1
List, dict, set comprehensions 1.2.8.2
Работа со словарями 1.2.8.3
Дополнительные материалы 1.2.8.4
II. Повторное использование кода 1.3
9. Функции 1.3.1
Создание функций 1.3.1.1
Пространства имен. Области видимости 1.3.1.2
Параметры и аргументы функций 1.3.1.3
Типы параметров 1.3.1.3.1
Типы аргументов 1.3.1.3.2
Аргументы переменной длины 1.3.1.3.3
Распаковка аргументов 1.3.1.3.4
Пример использования 1.3.1.3.5
Дополнительные материалы 1.3.1.4
Задания 1.3.1.5
10. Полезные функции 1.3.2
Функция print 1.3.2.1
Функция range 1.3.2.2
Функция sorted 1.3.2.3
4
Функция enumerate 1.3.2.4
Функция zip 1.3.2.5
Функции any и all 1.3.2.6
11. Модули 1.3.3
Импорт модуля 1.3.3.1
Создание своих модулей 1.3.3.2
if name == "main" 1.3.3.3
Задания 1.3.3.4
12. Полезные модули 1.3.4
Модуль subprocess 1.3.4.1
Модуль os 1.3.4.2
Модуль ipaddress 1.3.4.3
Модуль argparse 1.3.4.4
Модуль tabulate 1.3.4.5
Модуль pprint 1.3.4.6
Дополнительные материалы 1.3.4.7
Задания 1.3.4.8
13. Итераторы, итерируемые объекты и генераторы 1.3.5
Итерируемый объект 1.3.5.1
Итератор 1.3.5.2
Generator 1.3.5.3
Дополнительные материалы 1.3.5.4
III. Регулярные выражения 1.4
14. Синтаксис регулярных выражений 1.4.1
Наборы символов 1.4.1.1
Символы повторения 1.4.1.2
Специальные символы 1.4.1.3
Жадность символов повторения 1.4.1.4
Группировка выражений 1.4.1.5
Пример использования именованных групп 1.4.1.5.1
Группа без захвата 1.4.1.6
Повторение захваченного результата 1.4.1.7
15. Модуль re 1.4.2
Объект Match 1.4.2.1
5
re.search 1.4.2.2
re.match 1.4.2.3
re.finditer 1.4.2.4
re.findall 1.4.2.5
re.compile 1.4.2.6
Флаги 1.4.2.7
re.split 1.4.2.8
re.sub 1.4.2.9
Дополнительные материалы 1.4.2.10
Задания 1.4.2.11
IV. Запись и передача данных 1.5
16. Unicode 1.5.1
Стандарт Unicode 1.5.1.1
Unicode в Python 3 1.5.1.2
Конвертация между байтами и строками 1.5.1.3
Примеры конвертации 1.5.1.4
Ошибки при конвертации 1.5.1.5
Дополнительные материалы 1.5.1.6
17. Работа с файлами в формате CSV, JSON, YAML 1.5.2
CSV 1.5.2.1
JSON 1.5.2.2
YAML 1.5.2.3
Дополнительные материалы 1.5.2.4
Задания 1.5.2.5
18. Работа с базами данных 1.5.3
SQL 1.5.3.1
SQLite 1.5.3.2
Основы SQL (в sqlite3 CLI) 1.5.3.3
CREATE 1.5.3.3.1
DROP 1.5.3.3.2
INSERT 1.5.3.3.3
SELECT 1.5.3.3.4
WHERE 1.5.3.3.5
6
ALTER 1.5.3.3.6
UPDATE 1.5.3.3.7
REPLACE 1.5.3.3.8
DELETE 1.5.3.3.9
ORDER BY 1.5.3.3.10
AND, OR, NOT, IN 1.5.3.3.11
Модуль sqlite3 1.5.3.4
Выполнение команд SQL 1.5.3.4.1
Получение результатов запроса 1.5.3.4.2
Cursor как итератор 1.5.3.4.3
Использование модуля sqlite3 без явного создания курсора 1.5.3.4.4
Обработка исключений 1.5.3.4.5
Connection как менеджер контекста 1.5.3.4.6
Пример использования SQLite 1.5.3.4.7
Дополнительные материалы 1.5.3.5
Задания 1.5.3.6
V. Работа с сетевым оборудованием 1.6
19. Подключение к оборудованию 1.6.1
Ввод пароля 1.6.1.1
Pexpect 1.6.1.2
Пример использования pexpect 1.6.1.2.1
Telnetlib 1.6.1.3
Пример использования telnetlib 1.6.1.3.1
Paramiko 1.6.1.4
Netmiko 1.6.1.5
Возможности netmiko 1.6.1.5.1
Дополнительные материалы 1.6.1.6
Задания 1.6.1.7
20. Одновременное подключение к нескольким устройствам 1.6.2
Измерение времени выполнения скрипта 1.6.2.1
Процессы и потоки в CPython 1.6.2.2
Модуль concurrent.futures 1.6.2.3
Метод map 1.6.2.3.1
Метод submit 1.6.2.3.2
7
Дополнительные материалы 1.6.2.4
Задания 1.6.2.5
21. Шаблоны конфигураций с Jinja2 1.6.3
Пример использования Jinja2 1.6.3.1
Программный интерфейс Jinja2 1.6.3.2
Синтаксис шаблонов Jinja2 1.6.3.3
Контроль символов whitespace 1.6.3.3.1
Переменные 1.6.3.3.2
for 1.6.3.3.3
if/elif/else 1.6.3.3.4
Фильтры 1.6.3.3.5
Тесты 1.6.3.3.6
Присваивание (set) 1.6.3.3.7
Include 1.6.3.3.8
Наследование шаблонов 1.6.3.4
Дополнительные материалы 1.6.3.5
Задания 1.6.3.6
22. Обработка вывода команд TextFSM 1.6.4
Синтаксис шаблонов TextFSM 1.6.4.1
Примеры использования TextFSM 1.6.4.2
CLI Table 1.6.4.3
Дополнительные материалы 1.6.4.4
Задания 1.6.4.5
VI. Ansible 1.7
23. Основы Ansible 1.7.1
Инвентарный файл 1.7.1.1
Ad-Hoc команды 1.7.1.2
Конфигурационный файл 1.7.1.3
Модули 1.7.1.4
Основы playbook 1.7.1.5
Переменные 1.7.1.5.1
Результат выполнения модуля 1.7.1.5.2
24. Сетевые модули 1.7.2
8
ios_command 1.7.2.1
ios_facts 1.7.2.2
ios_config 1.7.2.3
lines (commands) 1.7.2.3.1
parents 1.7.2.3.2
Отображение обновлений 1.7.2.3.3
save_when 1.7.2.3.4
backup 1.7.2.3.5
defaults 1.7.2.3.6
after 1.7.2.3.7
before 1.7.2.3.8
match 1.7.2.3.9
replace 1.7.2.3.10
src 1.7.2.3.11
ntc_ansible 1.7.2.4
Подробнее об Ansible 1.7.2.5
Дополнительные материалы 1.7.2.6
Задания 1.7.2.7
Дополнительная информация 1.8
Соглашение об именах 1.8.1
Подчеркивание в именах 1.8.1.1
Полезные функции 1.8.2
Функция lambda 1.8.2.1
Функция map 1.8.2.2
Функция filter 1.8.2.3
Основы threading и multiprocessing 1.8.3
Модуль threading 1.8.3.1
Модуль multiprocessing 1.8.3.2
Дополнительные материалы 1.8.3.3
Отличия Python 2.7 и Python 3 1.8.4
Продолжение обучения 1.9
Отзывы 1.10
9
10
Введение
Введение
Обсуждение
Для обсуждения книги, заданий, а также связанных вопросов используется Slack.
Обсуждения на GitBook закрыты. Все вопросы, предложения и замечания по книге
также пишите в Slack.
О книге
Если "в двух словах", то это такой CCNA по Python. С одной стороны, книга достаточно
базовая, чтобы её мог одолеть любой желающий, а с другой стороны, в книге
рассматриваются все основные темы, которые позволят дальше расти
самостоятельно. Книга не ставит своей целью глубокое рассмотрение Python. Задача
книги – объяснить понятным языком основы Python и дать понимание необходимых
инструментов для его практического использования. Всё, что рассматривается в книге,
ориентировано на сетевое оборудование и работу с ним. Это даёт возможность сразу
использовать в работе сетевого инженера то, что было изучено на курсе. Все примеры
показываются на примере оборудования Cisco, но, конечно же, они применимы и для
любого другого оборудования.
11
Введение
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International
License.
12
О книге
О книге
Книга разделена на шесть частей.
Первая часть книги посвящена основам языка, которые являются фундаментом для
дальнейшего его изучения. В главах 1 – 3, посвящённых подготовке к работе с Python,
рассматриваются выбор ОС и установка её пакетов, выбор редактора, использование
Git и GitHub. Кроме того, в них рассматриваются система установки пакетов Python и
способ изоляции разных версий и пакетов Python, а также готовые виртуальные
машины для выполнения заданий. В главах 4 – 7 изложены основы Python. Вы
узнаете, какие базовые типы данных поддерживает Python, как с ними работать, какие
возможности и ограничения есть у них. Создание базовых скриптов, получение
информации от пользователя и передача аргументов созданному скрипту,
рассматриваются в главе 5. Глава 6 описывает механизмы контроля хода программы:
условия (if, else), циклы (for, while) и работу с исключениями. В главе 7 завершается
знакомство с основами Python описанием принципов работы с файлами, и, в
завершение первой части, приводятся примеры использования пройденных тем для
решения задач в главе 8.
13
О книге
как формат передачи данных разных API. В главе 18 рассматривается работа с базами
данных на примере SQLite, рассматриваются и основы языка SQL, и как работать с
базами данных из Python.
Пятая часть рассказывает про работу с сетевым оборудованием через Python. В главе
19 рассказ посвящён подключению к сетевому оборудованию через Telnet и SSH,
рассматривается использование нескольких полезных модулей – каждый из них
использует несколько отличный от другого модуля подход. Глава 20 объясняет, как
подключаться к оборудованию параллельно, используя потоки и процессы, а в главе
21 рассматривается язык шаблонов Jinja2. Он позволит создавать шаблоны
конфигурации с нуля, и таким образом, вместо замены параметров в текстовом файле,
Вы легко сможете сгенерировать нужные команды с помощью Python. В главе 22
разговор посвящён модулю TextFSM, задача которого является обратной модулю
Jinja2. Это библиотека от Google, которая позволяет обрабатывать вывод команд show
(и любых других аналогичных), и получать из него значения в виде переменных, то
есть проводить парсинг вывода. Для обработки команды создается отдельный шаблон,
который описан с помощью регулярных выражений.
Шестая часть посвящена основам работы с Ansible, а также модулям для работы с
оборудованием Cisco. Этой информации будет достаточно, чтобы начать использовать
Ansible, но, так как это лишь небольшая часть его возможностей, то остальное
вынесено в отдельную книгу.
Книга всегда будет оставаться бесплатной, поэтому Вам не нужно переживать, что она
будет удалена.
Примеры
Все примеры, которые используются в книге, располагаются в репозитории. Примеры,
которые рассматриваются в разделах книги, являются обучающими. Это значит, что
они не обязательно показывают лучший вариант решения задачи, так как они
14
О книге
Задания
Все задания и вспомогательные файлы можно скачать в репозитории, том же, где
располагаются примеры кода. Если в заданиях раздела есть задания с буквами
(например, 5.2a), то нужно выполнить сначала задания без букв, а затем с буквами.
Задания с буквами, как правило, немного сложнее заданий без букв и развивают идею
в соответствующем задании без буквы. Если получается, лучше делать задания по
порядку. В книге специально не приведены ответы на задания, так как, к сожалению,
когда есть ответы, очень часто вместо того, чтобы попытаться решить сложное
задание самостоятельно, подглядывают в них. Конечно, иногда возникает ситуация,
когда никак не получается решить задание – попробуйте отложить его, задать вопрос в
Slack и сделать какое-либо другое.
На Stack Overflow есть ответы практически на любые вопросы. Так что, если
Google отправил Вас на него, это, с большой вероятностью значит, что ответ
найден. Запросы, конечно же, лучше писать на английском – по Python очень
много материалов и, как правило, подсказку найти легко
Ответы могли бы показать, как ещё можно выполнить задание или же как лучше это
сделать. Но на этот счёт не следует переживать, так как, скорее всего, в следующих
разделах встретится пример, в котором будет показано, как писать такой код.
Тесты
Для части тем книги созданы тесты:
15
О книге
Эти тесты можно использовать как для проверки знаний, так и в роли заданий. Очень
полезно пройти тест после прочтения соответствующей темы. Он позволит Вам
вспомнить материал темы, а также увидеть на практике разные аспекты работы с
Python. Постарайтесь сначала ответить самостоятельно, а затем подсмотреть ответы в
IPython по тем вопросам, в которых Вы сомневаетесь.
Презентации
Для всех тем книги есть презентации в репозитории. По ним удобно быстро
просматривать информацию и повторять. Если Вы знаете основы Python, то стоит их
пролистать.
онлайн;
PDF/Mobi/ePub.
Обновление книги
В сентябре 2017 года книга была переведена на Python 3.6. Она ещё может
дополняться, а также будут исправляться ошибки и опечатки. Поэтому, если Вы не
будете читать книгу в ближайшее время, то лучше сохраните ссылку на онлайн-версию
книги, а не PDF/Mobi/ePub, а когда решите читать, скачайте свежую версию.
GitBook отображает, когда были сделаны последние изменения, поэтому легко можно
определить, были ли изменения за последнее время.
16
О книге
17
Изменения в книге
Изменения в книге
28.11.2017 – в задании 19.2b добавлены примеры команд с ошибками;
15.11.2017 – примеры в части Ansible проверены на версии 2.4.1;
05.11.2017 – задания 20.2, 20.2a переписаны, чтобы в них предполагалось
использование concurrent.futures, задания 20.3, 20.3a удалены;
05.11.2017 – глава Основы threading и multiprocessing перенесена в часть
Дополнительная информация. В этих главах рассматриваются только основы
модулей threading и multiprocessing, при этом, задача запуска функции в потоках и
процессах намного проще решается в модуле concurrent.futures. К тому же, при его
использовании, не надо переписывать существующий код. На случай, если задача
будет более сложная и функционала concurrent.futures не хватит, оставлены
основы модулей threading и multiprocessing. Разумеется, этих основ недостаточно,
чтобы решать более сложные задачи, но это неплохой старт;
21.10.2017 – раздел List, dict, set comprehensions перенесён в главу 8;
15.10.2017 – реорганизация книги. Книга разделена на главы:
18
Изменения в книге
19
Ресурсы для обучения по книге
20
Как учиться по этой книге
Первый вариант
Этот вариант подходит в следующих случаях:
подписи интерфейсов;
настройка общих параметров, например, alias;
генерация конфигураций по шаблону для первичной настройки оборудования.
Если задачу не получается решить в Ansible, или Вы просто желаете изучить Python,
то переходите ко второму варианту.
Второй вариант
Если Вы уже решили изучать Python и примерно представляете, что он может дать, то
можно просто взять Пример плана обучения и идти по нему. Многие вещи, особенно в
начале книги будут базовыми, но не пропускайте их, если Вы их не знаете. Можно с
уверенностью сказать, что такие темы, как типы данных или управление ходом
программы, будут основой всего, что мы будем изучать дальше.
План занятий можно менять под себя, но не стоит делать большие перерывы между
темами и не стоит заниматься слишком много: если делать большие перерывы, то всё,
что было изучено раньше, начнёт забываться, а если торопиться, то, скорее всего, всё
21
Как учиться по этой книге
сведется к чтению текста без выполнения заданий. В таком случае, будет очень мало
пользы. Если следовать расписанию, пусть и с небольшими отклонениями, то к концу
наберётся два месяца практики. За это время все базовые понятия хорошо усвоятся, и
главное, после завершения курса, не бросать практиковаться и учиться.
По мере чтения книги, скорее всего, будут возникать идеи, что сделать по работе – это
отлично! Пытайтесь реализовать это обязательно, записывайте идеи, чтобы не
забыть, если не можете сразу сделать это. Примеры использования, которые
придумываете Вы, будут особенно сильно помогать расти, так как они естественным
образом вырастают из Ваших задач – будет добавляться функционал, будут
улучшения, которые нужны именно Вам. Если же Вы чувствуете, что читаете книгу, но
пока никаких идей нет, не огорчайтесь – в книге достаточно упражнений, на которых Вы
сможете потренироваться писать код, и которые, весьма вероятно, натолкнут Вас на
идеи.
Если Вам удалось изобрести себе задач по ходу курса – отлично, реализуйте их,
напишите список и делайте задачи постепенно. Это прекрасный способ изучать язык
дальше и повторять пройденное. Идеи сами будут двигать Вас дальше, Вы будете
изучать новые темы и новые возможности более естественно, одновременно с
развитием Ваших программ. Создайте репозиторий на GitHub и выкладывайте туда
свои скрипты. Дорабатывайте их, поделитесь с коллегами. Отличный способ
запомнить лучше определённую тему - рассказать её другому.
22
Пример плана обучения
9 Unicode -
10 Сериализация данных Сериализация данных
11 Работа с базами данных Работа с базами данных
15 Ansible Ansible
23
FAQ
Зачем тогда учить Python? Дело в том, что тот же Ansible не решит все вопросы. И,
возможно, Вам понадобится добавить какой-то функционал самостоятельно. Кроме
непосредственной настройки оборудования, есть ежедневные рутинные задачи,
которые можно автоматизировать с помощью Python. Скажем так, если Вы не хотите
24
FAQ
И ещё, этот курс не только о том, как использовать Python для подключения к
оборудованию и его настройке. Он и о том, как решать задачи, которые не касаются
подключения к оборудованию, например, изменить что-то в нескольких файлах
конфигурации, или обработать log-файл – Python поможет Вам решать в том числе и
подобные задачи.
25
FAQ
крайней мере, Вы потратите время на язык, который позволит Вам легко шагнуть
дальше, чем написание простых скриптов;
из программ, связанных с сетями, на Python написан, например, GNS3.
26
FAQ
рутинными задачами;
проблемами и решениями, которые надо протестировать;
большим объёмом однотипных и повторяющихся задач;
большим количеством оборудования;
Сейчас явно наблюдается тенденция, которую можно описать фразой "все учимся
программировать", и это, в целом, хорошо. Но программирование это не что-то
элементарное, это сложно, в это нужно вкладывать много времени, особенно если Вы
никогда не имели отношения к техническому миру. Может сложиться впечатление, что
достаточно пройти "вот эти вот курсы" и через 3 месяца Вы крутой программист с
высокой зарплатой. Нет, этот книга не об этом :-) Мы не говорим в ней о
программировании как профессии и не ставим такую цель, мы говорим о
программировании как инструменте, таком как, например, знание CLI Linux. Дело не в
том, что инженеры какие-то особенные, просто, как правило:
Это не значит, что всем остальным "не дано". Просто инженерам это будет проще.
27
FAQ
28
Благодарности
Благодарности
Спасибо всем, кто проявил интерес к первому анонсу курса – ваш интерес подтвердил,
что это будет кому-то нужно. Павел Пасынок, спасибо тебе за то, что согласился на
курс. С вами было интересно работать, и это добавило мне мотивации завершить
курс, и я особенно рада, что знания, которые Вы получили на курсе, нашли
практическое применение. Алексей Кириллов, самое большое спасибо тебе :-) Я
всегда могла обсудить с тобой любой вопрос по курсу. Ты помогал мне поддерживать
мотивацию и не уходить в дебри. Общение с тобой вдохновляло меня продолжать,
особенно в сложные моменты. Спасибо тебе за вдохновение, положительные эмоции
и поддержку! Спасибо всем, кто писал комментарии к книге – благодаря вам в книге не
только стало меньше опечаток и ошибок, но и содержание книги стало лучше. Спасибо
всем слушателям онлайн-курса – благодаря вашим вопросам книга стала намного
лучше. Слава Скороход, спасибо тебе огромное, что вызвался быть редактором –
количество ошибок теперь стремится к нулю :-)
29
I. Основы Python
I. Основы Python
Первая часть книги посвящена основам Python. В ней рассматриваются:
Кроме того, в главе 8 показаны примеры использования пройденных тем для решения
задач.
30
1. Подготовка к работе
Подготовка к работе
Для того, чтобы начать работать с Python, надо определиться с несколькими вещами:
Виртуальная машина
Для выполнения заданий в книге лучше всего сделать отдельную виртуальную
машину. Имеются следующие варианты:
Вы можете выбрать одну из них или установить все самостоятельно, но будет лучше,
если Вы выделите отдельную виртуальную машину.
Облачный сервис
Ещё один вариант – использовать один из следующих сервисов:
31
1. Подготовка к работе
Если Вы используете Windows, то, скорее всего, Python нужно будет установить. Один
из самых простых вариантов для Windows – установить окружение Anaconda. В
окружении есть IDE Spyder (Integrated development environment), который можно
использовать вместо редактора. Windows не рекомендована в качестве ОС для
обучения, например потому, что на ней нельзя установить Ansible.
Выбор редактора
32
1. Подготовка к работе
подсветка кода;
подсказки синтаксиса;
автоматические отступы (важно для Python).
Всё это есть в любом хорошем редакторе, но для этого может потребоваться
установить дополнительные модули. В начале работы может получиться так, что IDE
будет только отвлекать Вас обилием возможностей. Список IDE для Python можно
можно посмотреть здесь. Например, можно выбрать PyCharm или Spyder для
Windows.
33
ОС и редактор
ОС и редактор
Можно выбрать любую ОС и любой редактор, но желательно использовать Python
версии 3.6, так как здесь будет использоваться именно эта версия. Также для курса
желательно не использовать Windows, так как, например, Ansible можно установить
только на Linux, однако более половины заданий книги можно без проблем выполнить
на Windows.
Linux:
gedit;
Geany;
nano;
Sublime Text.
Mac OS:
Geany;
TextMate;
TextWrangler.
Windows:
Notepad++;
Geany.
Для начала работы можно взять первый из списка для соответствующей операционной
системы. Далее выводы команд, интерпретатора и скриптов приводятся для Linux. В
других ОС вывод может незначительно отличаться.
34
Система управления пакетами pip
$ pip --version
pip 9.0.1 from /home/vagrant/venv/py3_convert/lib/python3.6/site-packages (python 3.6)
Если команда выдала ошибку, значит, pip не установлен. Установка pip описана в
документации
Установка модулей
Для установки модулей используется команда pip install:
$ pip --version
pip 9.0.1 from /usr/local/lib/python2.7/dist-packages (python 2.7)
35
Система управления пакетами pip
$ pip3 --version
pip 1.5.6 from /usr/lib/python3/dist-packages (python 3.4)
Таким образом, всегда понятно для какой именно версии Python устанавливается
пакет.
36
Виртуальные окружения
Виртуальные окружения
Виртуальные окружения:
virtualenvwrapper
Виртуальные окружения создаются с помощью virtualenvwrapper.
export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3.6
export WORKON_HOME=~/venv
. /usr/local/bin/virtualenvwrapper.sh
$ exec bash
37
Виртуальные окружения
(pyneng)$ deactivate
$
$ workon pyneng
(pyneng)$
$ workon Test
(Test)$ workon pyneng
(pyneng)$
38
Виртуальные окружения
$ rmvirtualenv Test
Removing Test...
$
(pyneng)$ lssitepackages
ANSI.py pexpect-3.3-py2.7.egg-info
ANSI.pyc pickleshare-0.5-py2.7.egg-info
decorator-4.0.4-py2.7.egg-info pickleshare.py
decorator.py pickleshare.pyc
decorator.pyc pip-1.1-py2.7.egg
distribute-0.6.24-py2.7.egg pxssh.py
easy-install.pth pxssh.pyc
fdpexpect.py requests
fdpexpect.pyc requests-2.7.0-py2.7.egg-info
FSM.py screen.py
FSM.pyc screen.pyc
IPython setuptools.pth
ipython-4.0.0-py2.7.egg-info simplegeneric-0.8.1-py2.7.egg-info
ipython_genutils simplegeneric.py
ipython_genutils-0.1.0-py2.7.egg-info simplegeneric.pyc
path.py test_path.py
path.py-8.1.1-py2.7.egg-info test_path.pyc
path.pyc traitlets
pexpect traitlets-4.0.0-py2.7.egg-info
Вместо python3.6 может использоваться python или python3, в зависимости от того, как
установлен Python 3.6. Эта команда создаёт указанный каталог и все необходимые
каталоги внутри него, если они не были созданы.
39
Виртуальные окружения
$ ls -ls new/pyneng
total 16
4 drwxr-xr-x 2 vagrant vagrant 4096 Aug 21 14:50 bin
4 drwxr-xr-x 2 vagrant vagrant 4096 Aug 21 14:50 include
4 drwxr-xr-x 3 vagrant vagrant 4096 Aug 21 14:50 lib
4 -rw-r--r-- 1 vagrant vagrant 75 Aug 21 14:50 pyvenv.cfg
$ source new/pyneng/bin/activate
$ deactivate
Установка пакетов
Например, установим в виртуальном окружении пакет simplejson.
40
Виртуальные окружения
(pyneng)$ ipython
In [2]: simplejson
simplejson
In [2]: simplejson.
simplejson.Decimal simplejson.decoder
simplejson.JSONDecodeError simplejson.dump
simplejson.JSONDecoder simplejson.dumps
simplejson.JSONEncoder simplejson.encoder
simplejson.JSONEncoderForHTML simplejson.load
simplejson.OrderedDict simplejson.loads
simplejson.absolute_import simplejson.scanner
simplejson.compat simplejson.simple_first
(pyneng)$ deactivate
$ ipython
41
Интерпретатор Python
Интерпретатор Python
Перед началом работы надо проверить, что при вызове интерпретатора Python вывод
будет таким:
$ python
Python 3.6.0 (default, May 31 2017, 07:04:38)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Вывод показывает, что установлен Python 3.6. Приглашение ">>>", это стандартное
приглашение интерпретатора Python. Вызов интерпретатора выполняется командой
python, а чтобы выйти, нужно набрать quit(), либо нажать Ctrl+D.
42
Дополнительные материалы
Дополнительные материалы
Документация:
Редакторы и IDE:
PythonEditors
IntegratedDevelopmentEnvironments
VIM and Python - a Match Made in Heaven
43
Задания
Задания
Задание 1.1
Единственное задание в этом разделе: подготовка к работе.
44
2. Использование Git и GitHub
Теории в этом подразделе будет мало, но будут даны ссылки на полезные ресурсы.
Попробуйте сначала провести все базовые настройки для выполнения заданий, а
потом продолжайте читать книгу. И в конце, когда базовая работа с Git и GitHub будет
уже привычным делом, почитайте о них подробнее. Для чего может пригодиться Git:
45
Основы Git
Основы Git
Git это распределённая система контроля версий (Version Control System, VCS),
которая широко используется и выпущена под лицензией GNU GPL v2. Она может:
Git хранит изменения как снимок (snapshot) всего репозитория. Этот снимок
выполняется после каждого коммита (commit).
Установка Git:
Инициализация репозитория
Инициализация репозитория выполняется с помощью команды git init:
[~/tools/first_repo]
$ git init
Initialized empty Git repository in /home/vagrant/tools/first_repo/.git/
После выполнения этой команды, в текущем каталоге создаётся папка .git, в которой
содержатся служебные файлы, необходимые для Git.
46
Основы Git
47
Основы Git
cd ~
git clone https://github.com/magicmonty/bash-git-prompt.git .bash-git-prompt --depth=1
GIT_PROMPT_ONLY_IN_REPO=1
source ~/.bash-git-prompt/gitprompt.sh
exec bash
[~]
vagrant@jessie-i386:
$
48
Основы Git
49
Основы Git
Работа с Git
Для управления Git, используются различные команды, смысл которых поясняется
далее.
git status
При работе с Git, важно понимать текущий статус репозитория. Для этого в Git есть
команда git status:
Git сообщает, что мы находимся в ветке master (эта ветка создаётся сама и
используется по умолчанию), и что ему нечего добавлять в коммит. Кроме этого, Git
предлагает создать или скопировать файлы и после этого воспользоваться командой
git add, чтобы Git начал за ними следить.
$ vi README
$ echo "test" >> README
В приглашении показано, что есть два файла, за которыми Git ещё не следит:
50
Основы Git
Два файла получилось из-за того, что у меня настроены undo-файлы для Vim. Это
специальные файлы, благодаря которым можно отменять изменения не только в
текущей сессии файла, но и прошлые. Обратите внимание, что Git сообщает, что есть
файлы, за которыми он не следит и подсказывает, какой командой это сделать.
Файл .gitignore
Для того, чтобы Git игнорировал undo-файлы Vim, можно добавить, например, такую
строку в файл .gitignore:
*.un~
Это значит, что Git должен игнорировать все файлы, которые заканчиваются на ".un~".
51
Основы Git
Обратите внимание, что теперь в выводе нет файла .README.un~. Как только в
репозиторий был добавлен файл .gitignore, файлы, которые указаны в нём, стали
игнорироваться.
git add
Для того, чтобы Git начал следить за файлами, используется команда git add.
git commit
После того, как все нужные файлы были добавлены в staging, можно закоммитить
изменения. Staging это совокупность файлов, которые будут добавлены в следующий
коммит. У команды git commit есть только один обязательный параметр – флаг "-m". Он
позволяет указать сообщение для этого коммита.
52
Основы Git
Фраза "nothing to commit, working directory clean" обозначает, что нет изменений,
которые нужно добавить в Git или закоммитить.
53
Основы Git
Дополнительные возможности
git diff
Команда git diff показывает, какие изменения были внесены с момента последнего
коммита:
Если добавить изменения, внесённые в файлы, в staging командой git add и ещё раз
выполнить команду git diff, то она ничего не покажет:
54
Основы Git
Закоммитим изменения:
git log
55
Основы Git
Флаг "-p" позволяет отобразить отличия, которые были внесены каждым коммитом:
56
Основы Git
57
Основы Git
58
Аутентификация на GitHub
Аутентификация на GitHub
Для того, чтобы начать работать с GitHub, надо на нём зарегистрироваться. Для
безопасной работы с GitHub лучше использовать аутентификацию по ключам SSH.
Запуск SSH-агента:
$ ssh-add ~/.ssh/id_rsa
$ cat ~/.ssh/id_rsa.pub
59
Аутентификация на GitHub
$ ssh -T [email protected]
Hi username! You've successfully authenticated, but GitHub does not provide shell acce
ss.
60
Работа со своим репозиторием
залогиниться на GitHub;
в правом верхнем углу нажать плюс и выбрать "New repository", чтобы создать
новый репозиторий;
в открывшемся окне надо ввести название репозитория;
Можно поставить галку "Initialize this repository with a README". Это создаст файл
README.md, в котором будет находиться только название репозитория.
61
Работа со своим репозиторием
В итоге, в текущем каталоге, в котором была выполнена команда git clone, появится
каталог с именем репозитория, в моём случае – "online-2-natasha-samoylenko". В этом
каталоге теперь находится содержимое репозитория на GitHub.
Работа с репозиторием
Предыдущая команда не просто скопировала репозиторий чтобы использовать его
локально, но и настроила соответствующим образом Git:
При работе с заданиями на работе и дома, надо обратить особое внимание на первый
и последний шаг:
62
Работа со своим репозиторием
$ git pull
Already up-to-date.
$ git pull
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 5 (delta 4), reused 5 (delta 4), pack-reused 0
Unpacking objects: 100% (5/5), done.
From ssh://github.com/pyneng/online-2-natasha-samoylenko
89c04b6..fc4c721 master -> origin/master
Updating 89c04b6..fc4c721
Fast-forward
exercises/03_data_structures/task_3_3.py | 2 ++
1 file changed, 2 insertions(+)
Коммит
При выполнении коммита обязательно надо указать сообщение. Лучше, если
сообщение будет со смыслом, а не просто "update" или подобное. Коммит делается
командой, подобной "git commit -m "Сделал задания 4.1-4.3"".
Push на GitHub
63
Работа со своим репозиторием
Для загрузки всех локальных изменений на GitHub используется команда git push:
Перед выполнением git push можно выполнить команду "git log -p origin/master.." – она
покажет, какие изменения Вы собираетесь добавлять в свой репозиторий на GitHub.
64
Работа с репозиторием заданий и примеров
$ git pull
Already up-to-date.
$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 2), reused 3 (delta 2), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/natenka/pyneng-examples-exercises
49e9f1b..1eb82ad master -> origin/master
Updating 49e9f1b..1eb82ad
Fast-forward
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
65
Работа с репозиторием заданий и примеров
Просмотр изменений
Если Вы хотите посмотреть, какие именно изменения были внесены, можно
воспользоваться командой git log:
$ git log -p -1
commit 98e393c27e7aae4b41878d9d979c7587bfeb24b4
Author: Наташа Самойленко <[email protected]>
Date: Fri Aug 18 17:32:07 2017 +0300
Update task_24_4.md
В этой команде флаг "-p" указывает, что надо отобразить вывод утилиты Linux diff для
внесённых изменений, а не только сообщение коммита. В свою очередь, "-1"
указывает, что надо показать только один самый свежий коммит.
66
Работа с репозиторием заданий и примеров
Update README.md
В данном случае изменения были только в одном файле. Эта команда будет очень
полезна для того, чтобы посмотреть, какие изменения были внесены в формулировку
заданий и каких именно заданий. Так будет легче ориентироваться, и понимать,
касается ли это заданий, которые Вы уже сделали, и если касается, то надо ли их
изменять.
Если изменения были в тех заданиях, которые Вы ещё не делали, этот вывод
подскажет, какие файлы нужно скопировать с репозитория курса в Ваш личный
репозиторий (а может быть и весь раздел, если Вы ещё не делали задания из этого
раздела).
67
Дополнительные материалы
Дополнительные материалы
Документация:
Про Git/GitHub:
68
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 2.1
Создайте клон репозитория с заданиями и примерами, а затем обновите его с
помощью команды git pull – должно отобразиться сообщение "Already up-to-date". В
этом репозитории Вы не можете вносить изменения, поэтому он будет использоваться
только для получения заданий и примеров. Для работы с заданиями необходимо
создать собственный репозиторий и скопировать задания в него.
Задание 2.2
В этом задании необходимо:
В своём репозитории можно создать любую структуру каталогов. Так как задания
иногда обновляются, лучше выбрать следующий подход к работе:
69
3. Начало работы с Python
синтаксис Python;
работа в интерактивном режиме;
переменные в Python.
70
Синтаксис Python
Синтаксис Python
Первое, что, как правило, бросается в глаза, если говорить о синтаксисе в Python, это
то, что отступы имеют значение:
a = 10
b = 5
if a > b:
print("A больше B")
print(a - b)
else:
print("B больше или равно A")
print(b - a)
print("The End")
def open_file(filename):
print("Reading file", filename)
with open(filename) as f:
return f.read()
print("Done")
Этот код показан для демонстрации синтаксиса, несмотря на то, что ещё не
рассматривалась конструкция if/else, так что всё должно быть понятно. Python
понимает, какие строки относятся к if на основе отступов. Выполнение блока "if a > b"
заканчивается, когда встречается строка с тем же отступом, что и сама строка "if a >
b". Аналогично с блоком else.
71
Синтаксис Python
Ещё одна особенность приведённого кода, это пустые строки. С их помощью код
форматируется, чтобы его было проще читать. Остальные особенности синтаксиса
будут показаны в процессе знакомства со структурами данных в Python.
Комментарии
При написании кода часто нужно оставить комментарий, например, чтобы описать
особенности работы кода.
"""Очень важный
и длинный комментарий
"""
a = 10
b = 5
72
Интерпретатор Python. IPython
$ ipython
Python 3.6.3 (default, Oct 9 2017, 11:46:27)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
Для выхода используется команда quit. Далее описывается, как будет использоваться
IPython.
73
Интерпретатор Python. IPython
In [1]: 1 + 2
Out[1]: 3
In [2]: 22*45
Out[2]: 990
In [3]: 2**3
Out[3]: 8
In [4]: print('Hello!')
Hello!
help()
В IPython есть возможность посмотреть справку по произвольному объекту, функции
или методу с помощью help():
74
Интерпретатор Python. IPython
In [1]: help(str)
Help on class str in module builtins:
class str(object)
| str(object='') -> str
| str(bytes_or_buffer[, encoding[, errors]]) -> str
|
| Create a new string object from the given object. If encoding or
| errors is specified, then the object must expose a data buffer
| that will be decoded using the given encoding and error handler.
...
In [2]: help(str.strip)
Help on method_descriptor:
strip(...)
S.strip([chars]) -> str
Второй вариант:
In [3]: ?str
Init signature: str(self, /, *args, **kwargs)
Docstring:
str(object='') -> str
str(bytes_or_buffer[, encoding[, errors]]) -> str
In [4]: ?str.strip
Docstring:
S.strip([chars]) -> str
print()
75
Интерпретатор Python. IPython
In [6]: print('Hello!')
Hello!
In [7]: print(5*5)
25
Если нужно вывести подряд несколько значений через пробел, то нужно перечислить
их через запятую (подробнее о print() в главе 10):
Но если в print() указать параметр end равным пустой строке, результат будет таким:
dir()
dir() может использоваться для того, чтобы посмотреть, какие имеются аттрибуты
(переменные, привязанные к объекту) и методы (функции, привязанные к объекту).
76
Интерпретатор Python. IPython
Например, для числа вывод будет таким (обратите внимание на различные методы,
которые позволяют делать арифметические операции):
In [10]: dir(5)
Out[10]:
['__abs__',
'__add__',
'__and__',
...
'bit_length',
'conjugate',
'denominator',
'imag',
'numerator',
'real']
In [11]: dir('hello')
Out[11]:
['__add__',
'__class__',
'__contains__',
...
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill']
77
Интерпретатор Python. IPython
In [12]: dir()
Out[12]:
[ '__builtin__',
'__builtins__',
'__doc__',
'__name__',
'_dh',
...
'_oh',
'_sh',
'exit',
'get_ipython',
'i',
'quit']
In [13]: a = 'hello'
In [15]: dir()
Out[15]:
...
'a',
'exit',
'get_ipython',
'i',
'quit',
'test']
78
Интерпретатор Python. IPython
Magic commands
В IPython есть специальные "магические" команды, которые упрощают работу с
интерпретатором. Все они начинаются со знака процента.
%history
In [1]: a = 10
In [2]: b = 5
In [3]: if a > b:
...: print("A is bigger")
...:
A is bigger
In [4]: %history
a = 10
b = 5
if a > b:
print("A is bigger")
%history
%cpaste
При вставке кода с отступами в IPython, из-за автоматических отступов самого IPython,
код начинает дополнительно сдвигаться:
79
Интерпретатор Python. IPython
In [1]: a = 10
In [2]: b = 5
In [3]: if a > b:
...: print("A is bigger")
...: else:
...: print("A is less or equal")
...:
A is bigger
In [4]: %hist
a = 10
b = 5
if a > b:
print("A is bigger")
else:
print("A is less or equal")
%hist
In [5]: if a > b:
...: print("A is bigger")
...: else:
...: print("A is less or equal")
...:
File "<ipython-input-8-4d18ff094f5c>", line 3
else:
^
IndentationError: unindent does not match any outer indentation level
If you want to paste code into IPython, try the %paste and %cpaste magic functions.
При использовании %cpaste, после того, как все строки скопированы, надо завершить
работу команды, набрав "--":
In [9]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:if a > b:
: print("A is bigger")
:else:
: print("A is less or equal")
:--
A is bigger
80
Интерпретатор Python. IPython
In [10]: %paste
if a > b:
print("A is bigger")
else:
print("A is less or equal")
81
Интерпретатор Python. IPython
obj?, obj?? : Get help, or more help for object (also works as
?obj, ??obj).
?foo.*abc* : List names in 'foo' containing 'abc' in them.
%magic : Information about IPython's 'magic' % functions.
Magic functions are prefixed by % or %%, and typically take their arguments
without parentheses, quotes or even commas for convenience. Line magics take a
single % and cell magics are prefixed with two %%.
System commands:
History:
_i, _ii, _iii : Previous, next previous, next next previous input
_i4, _ih[2:5] : Input history line 4, lines 2-4
exec _i81 : Execute input history line #81 again
%rep 81 : Edit input history line #81
_, __, ___ : previous, next previous, next next previous output
_dh : Directory history
_oh : Output history
%hist : Command history of current session.
%hist -g foo : Search command history of (almost) all sessions for 'foo'.
%hist -g : Command history of (almost) all sessions.
%hist 1/2-8 : Command history containing lines 2-8 of session 1.
%hist 1/ ~2/ : Command history of session 1 and 2 sessions before current.
82
Переменные
Переменные
Переменные в Python не требуют объявления типа переменной (так как Python – язык
с динамической типизацией) и являются ссылками на область памяти. Правила
именования переменных:
In [1]: a = 3
In [2]: b = 'Hello'
In [3]: c, d = 9, 'Test'
In [4]: print(a,b,c,d)
3 Hello 9 Test
Обратите внимание, что в Python не нужно указывать, что "a" это число, а "b" это
строка.
In [5]: a = b = c = 33
In [6]: id(a)
Out[6]: 31671480
In [7]: id(b)
Out[7]: 31671480
In [8]: id(c)
Out[8]: 31671480
В этом примере видно, что все три имени ссылаются на один и тот же идентификатор,
то есть, это один и тот же объект, на который указывают три ссылки – "a", "b" и "c". С
числами у Python есть одна особенность, которая может немного сбить с понимания:
числа от -5 до 256 заранее созданы и хранятся в массиве (списке). Поэтому при
создании числа из этого диапазона фактически создаётся ссылка на число в
созданном массиве.
83
Переменные
In [9]: a = 3
In [10]: b = 3
In [11]: id(a)
Out[11]: 4400936168
In [12]: id(b)
Out[12]: 4400936168
In [13]: id(3)
Out[13]: 4400936168
Обратите внимание, что у "a", "b" и числа "3" одинаковые идентификаторы. Все они
просто являются ссылками на существующее число в списке.
Если сделать то же самое с числом больше 256, идентификаторы у всех будут разные:
In [14]: a = 500
In [15]: b = 500
In [16]: id(a)
Out[16]: 140239990503056
In [17]: id(b)
Out[17]: 140239990503032
In [18]: id(500)
Out[18]: 140239990502960
84
Переменные
In [19]: a = b = c = 500
In [20]: id(a)
Out[20]: 140239990503080
In [21]: id(b)
Out[21]: 140239990503080
In [22]: id(c)
Out[22]: 140239990503080
Имена переменных
Имена переменных не должны пересекаться с названиями операторов и модулей или
же других зарезервированных слов. В Python есть рекомендации по именованию
функций, классов и переменных:
85
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 3.1
Выполните установку IPython в виртуальном окружении или глобально в системе, если
виртуальные окружения не используются. После установки, по команде ipython должен
открываться интерпретатор IPython (вывод может незначительно отличаться):
$ ipython
Python 3.6.3 (default, Oct 9 2017, 11:46:27)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
86
4. Типы данных в Python
Numbers (числа);
Strings (строки);
Lists (списки);
Dictionaries (словари);
Tuples (кортежи);
Sets (множества);
Boolean (логический тип данных).
87
Числа
Числа
С числами можно выполнять различные математические операции.
In [1]: 1 + 2
Out[1]: 3
In [2]: 1.0 + 2
Out[2]: 3.0
In [3]: 10 - 4
Out[3]: 6
In [4]: 2**3
Out[4]: 8
In [5]: 10/3
Out[5]: 3.3333333333333335
In [6]: 10/3.0
Out[6]: 3.3333333333333335
In [9]: round(10/3.0, 2)
Out[9]: 3.33
In [10]: round(10/3.0, 4)
Out[10]: 3.3333
Остаток от деления:
In [11]: 10 % 3
Out[11]: 1
Операторы сравнения
88
Числа
In [13]: 10 < 3
Out[13]: False
In [14]: 10 == 3
Out[14]: False
In [15]: 10 == 10
Out[15]: True
In [16]: 10 <= 10
Out[16]: True
In [17]: 10.0 == 10
Out[17]: True
Функция int() позволяет выполнять конвертацию в тип int. Во втором аргументе можно
указывать систему счисления:
In [18]: a = '11'
In [19]: int(a)
Out[19]: 11
Если указать, что строку a надо воспринимать как двоичное число, то результат будет
таким:
In [20]: int(a, 2)
Out[20]: 3
In [21]: int(3.333)
Out[21]: 3
In [22]: int(3.9)
Out[22]: 3
89
Числа
In [23]: bin(8)
Out[23]: '0b1000'
In [24]: bin(255)
Out[24]: '0b11111111'
In [25]: hex(10)
Out[25]: '0xa'
In [29]: math.sqrt(9)
Out[29]: 3.0
In [30]: math.sqrt(10)
Out[30]: 3.1622776601683795
In [31]: math.factorial(3)
Out[31]: 6
In [32]: math.pi
Out[32]: 3.141592653589793
90
Строки (Strings)
Строки (Strings)
Строка в Python - это последовательность символов, заключенная в кавычки. Строки -
это неизменяемый упорядоченный тип данных.
Примеры строк:
In [9]: 'Hello'
Out[9]: 'Hello'
In [10]: "Hello"
Out[10]: 'Hello'
In [12]: tunnel
Out[12]: '\ninterface Tunnel0\n ip address 10.10.10.1 255.255.255.0\n ip mtu 1416\n ip
ospf hello-interval 5\n tunnel source FastEthernet1/0\n tunnel protection ipsec profi
le DMVPN\n'
In [13]: print(tunnel)
interface Tunnel0
ip address 10.10.10.1 255.255.255.0
ip mtu 1416
ip ospf hello-interval 5
tunnel source FastEthernet1/0
tunnel protection ipsec profile DMVPN
91
Строки (Strings)
In [18]: intf * 5
Out[18]: 'interfaceinterfaceinterfaceinterfaceinterface'
In [19]: '#' * 40
Out[19]: '########################################'
In [21]: string1[0]
Out[21]: 'i'
Нумерация всех символов в строке идет с нуля. Но, если нужно обратиться к какому-то
по счету символу, начиная с конца, то можно указывать отрицательные значения (на
этот раз с единицы).
In [22]: string1[1]
Out[22]: 'n'
In [23]: string1[-1]
Out[23]: '0'
In [24]: string1[0:9]
Out[24]: 'interface'
In [25]: string1[10:22]
Out[25]: 'FastEthernet'
In [26]: string1[10:]
Out[26]: 'FastEthernet1/0'
92
Строки (Strings)
In [27]: string1[-3:]
Out[27]: '1/0'
In [28]: a = '0123456789'
In [29]: a[::]
Out[29]: '0123456789'
In [30]: a[::-1]
Out[30]: '9876543210'
In [31]: a[::2]
Out[31]: '02468'
In [32]: a[1::2]
Out[32]: '13579'
93
Строки (Strings)
Знание различных методов (то есть, действий), которые можно применять к строкам,
помогает более эффективно работать с ними.
Строки неизменяемый тип данных, поэтому все методы, которые преобразуют строку
возвращают новую строку, а исходная строка остается неизменной.
регистра строки:
In [26]: string1.upper()
Out[26]: 'FASTETHERNET'
In [27]: string1.lower()
Out[27]: 'fastethernet'
In [28]: string1.swapcase()
Out[28]: 'fASTeTHERNET'
In [30]: string2.capitalize()
Out[30]: 'Tunnel 0'
In [32]: print(string1)
FASTETHERNET
count()
94
Строки (Strings)
Метод count() используется для подсчета того, сколько раз символ или подстрока
встречаются в строке:
In [34]: string1.count('hello')
Out[34]: 3
In [35]: string1.count('ello')
Out[35]: 4
In [36]: string1.count('l')
Out[36]: 8
find()
Методу find() можно передать подстроку или символ, и он покажет, на какой позиции
находится первый символ подстроки (для первого совпадения):
In [38]: string1.find('Fast')
Out[38]: 10
In [39]: string1[string1.find('Fast')::]
Out[39]: 'FastEthernet0/1'
startswith(), endswith()
95
Строки (Strings)
In [41]: string1.startswith('Fast')
Out[41]: True
In [42]: string1.startswith('fast')
Out[42]: False
In [43]: string1.endswith('0/1')
Out[43]: True
In [44]: string1.endswith('0/2')
Out[44]: False
replace()
strip()
Часто при обработке файла файл открывается построчно. Но в конце каждой строки,
как правило, есть какие-то спецсимволы (а могут быть и в начале). Например, перевод
строки.
Для того, чтобы избавиться от них, очень удобно использовать метод strip() :
In [48]: print(string1)
interface FastEthernet0/1
In [49]: string1
Out[49]: '\n\tinterface FastEthernet0/1\n'
In [50]: string1.strip()
Out[50]: 'interface FastEthernet0/1'
96
Строки (Strings)
Методу strip можно передать как аргумент любые символы. Тогда в начале и в конце
строки будут удалены все символы, которые были указаны в строке:
In [52]: ad_metric.strip('[]')
Out[52]: '110/1045'
split()
Метод split() разбивает строку на части, используя как разделитель какой-то символ
(или символы). По умолчанию, в качестве разделителя используются пробелы. Но в
скобках можно указать любой разделитель.
In [55]: print(commands)
['switchport', 'trunk', 'allowed', 'vlan', '10,20,30,100-200']
In [57]: print(vlans)
['10', '20', '30', '100-200']
В строке string1 был символ пробела в начале и символ перевода строки в конце. В
строке номер 54 с помощью метода strip() эти символы удаляются.
97
Строки (Strings)
In [59]: sh_ip_int_br.split()
Out[59]: ['FastEthernet0/0', '15.0.15.1', 'YES', 'manual', 'up', 'up']
А вот так выглядит разделение той же строки, когда один пробел используется как
разделитель:
98
Строки (Strings)
Форматирование строк
При работе со строками часто возникают ситуации, когда в шаблон строки надо
подставить разные данные.
Это можно делать объединяя, части строки и данные, но в Python есть более удобный
способ: форматирование строк.
99
Строки (Strings)
In [3]: print('{}'.format('10.1.1.1'))
10.1.1.1
In [4]: print('{}'.format(100))
100
In [7]: print(ip_template.format('10.1.1.1'))
IP address:
10.1.1.1
In [9]: print("{:.3f}".format(10.0/3))
3.333
100
Строки (Strings)
А также можно указать, что надо дополнить числа нулями, вместо пробелов:
IP address:
192 100 1 1
11000000 01100100 00000001 00000001
101
Строки (Strings)
В примере выше октеты адреса приходится передавать два раза - один для
отображения в десятичном формате, а второй - для двоичного.
IP address:
192 100 1 1
11000000 01100100 00000001 00000001
%d - integer
%f - float
102
Строки (Strings)
103
Строки (Strings)
In [2]: s
Out[2]: 'TestString'
In [4]: s
Out[4]: 'TestString'
Можно даже переносить составляющие строки на разные строки, но только если они в
скобках
In [5]: s = ('Test'
...: 'String')
In [6]: s
Out[6]: 'TestString'
Так регулярное выражение можно разбивать на части и его будет проще понять. Плюс
можно добавлять поясняющие комментарии в строках.
Также этим приемом удобно пользоваться, когда надо написать длинное сообщение:
104
Строки (Strings)
In [8]: message
Out[8]: 'При выполнении команды "{}" возникла такая ошибка "{}".\nИсключить эту команд
у из списка? [y/n]'
105
Список (List)
Список (List)
Список - это изменяемый упорядоченный тип данных.
Примеры списков:
Так как список - это упорядоченный тип данных, то, как и в строках, в списках можно
обращаться к элементу по номеру, делать срезы:
In [5]: list3[1]
Out[5]: 20
In [6]: list3[1::]
Out[6]: [20, 4.0, 'word']
In [7]: list3[-1]
Out[7]: 'word'
In [8]: list3[::-1]
Out[8]: ['word', 4.0, 20, 1]
In [11]: vlans.reverse()
In [12]: vlans
Out[12]: ['100-200', '30', '20', '15', '10']
106
Список (List)
In [13]: list3
Out[13]: [1, 20, 4.0, 'word']
In [15]: list3
Out[15]: ['test', 20, 4.0, 'word']
In [17]: interfaces[0][0]
Out[17]: 'FastEthernet0/0'
In [18]: interfaces[2][0]
Out[18]: 'FastEthernet0/2'
In [19]: interfaces[2][1]
Out[19]: '10.0.2.1'
107
Список (List)
join()
Метод join() собирает список строк в одну строку с разделителем, который указан
перед join:
In [17]: ','.join(vlans)
Out[17]: '10,20,30'
Метод join на самом деле относится к строкам, но так как значение ему надо
передавать как список, он рассматривается тут.
append()
In [19]: vlans.append('300')
In [20]: vlans
Out[20]: ['10', '20', '30', '100-200', '300']
extend()
Если нужно объединить два списка, то можно использовать два способа: метод
extend() и операцию сложения.
У этих способов есть важное отличие - extend меняет список, к которому примерен
метод, а суммирование возвращает новый список, который состоит из двух.
Метод extend:
108
Список (List)
In [23]: vlans.extend(vlans2)
In [24]: vlans
Out[24]: ['10', '20', '30', '100-200', '300', '400', '500']
Суммирование списков:
Обратите внимание на то, что при суммировании списков в ipython появилась строка
Out. Это означает, что результат суммирования можно присвоить в переменную:
In [31]: result
Out[31]: ['10', '20', '30', '100-200', '300', '400', '500']
pop()
Метод pop() удаляет элемент, который соответствует указанному номеру. Но, что
важно, при этом метод возвращает этот элемент:
In [29]: vlans.pop(-1)
Out[29]: '100-200'
In [30]: vlans
Out[30]: ['10', '20', '30']
remove()
109
Список (List)
In [32]: vlans.remove('20')
In [33]: vlans
Out[33]: ['10', '30', '100-200']
В методе remove надо указывать сам элемент, который надо удалить, а не его номер в
списке. Если указать номер элемента, возникнет ошибка:
In [34]: vlans.remove(-1)
-------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-32-f4ee38810cb7> in <module>()
----> 1 vlans.remove(-1)
index()
Метод index() используется для того, чтобы проверить, под каким номером в списке
хранится элемент:
In [36]: vlans.index('30')
Out[36]: 2
insert()
In [38]: vlans.insert(1,'15')
In [39]: vlans
Out[39]: ['10', '15', '20', '30', '100-200']
sort()
110
Список (List)
In [41]: vlans.sort()
In [42]: vlans
Out[42]: [1, 10, 15, 50]
111
Список (List)
In [3]: print(list1)
['r', 'o', 'u', 't', 'e', 'r']
Генераторы списков:
In [5]: list2
Out[6]:
['FastEthernet0/0',
'FastEthernet0/1',
'FastEthernet0/2',
'FastEthernet0/3',
'FastEthernet0/4',
'FastEthernet0/5',
'FastEthernet0/6',
'FastEthernet0/7',
'FastEthernet0/8',
'FastEthernet0/9']
Генераторы списков требуют понимания работы цикла for и даже после этого, могут
быть немного необычны.
После 6 раздела, можно вернуться к этой теме и почитать о них подробнее в разделе
Примеры использования основ.
112
Словарь (Dictionary)
Словарь (Dictionary)
Словари - это изменяемый неупорядоченный тип данных
Пример словаря:
london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco', 'model': '44
51', 'ios': '15.4'}
london = {
'id': 1,
'name':'London',
'it_vlan':320,
'user_vlan':1010,
'mngmt_vlan':99,
'to_name': None,
'to_id': None,
'port':'G1/0/11'
}
Для того, чтобы получить значение из словаря, надо обратиться по ключу, таким же
образом, как это было в списках, только вместо номера будет использоваться ключ:
113
Словарь (Dictionary)
In [2]: london['name']
Out[2]: 'London1'
In [3]: london['location']
Out[3]: 'London Str'
In [5]: print(london)
{'vendor': 'Cisco', 'name': 'London1', 'location': 'London Str'}
london_co = {
'r1' : {
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'hostname': 'london_r2',
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'hostname': 'london_sw1',
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101'
}
}
114
Словарь (Dictionary)
In [7]: london_co['r1']['ios']
Out[7]: '15.4'
In [8]: london_co['r1']['model']
Out[8]: '4451'
In [9]: london_co['sw1']['ip']
Out[9]: '10.255.0.101'
115
Словарь (Dictionary)
clear()
In [1]: london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco', 'mod
el': '4451', 'ios': '15.4'}
In [2]: london.clear()
In [3]: london
Out[3]: {}
copy()
In [6]: id(london)
Out[6]: 25489072
In [7]: id(london2)
Out[7]: 25489072
In [9]: london2['vendor']
Out[9]: 'Juniper'
В этом случае london2 это еще одно имя, которое ссылается на словарь. И при
изменениях словаря london меняется и словарь london2, так как это ссылки на один и
тот же объект.
Поэтому, если нужно сделать копию словаря, надо использовать метод copy():
116
Словарь (Dictionary)
In [12]: id(london)
Out[12]: 25524512
In [13]: id(london2)
Out[13]: 25563296
In [15]: london2['vendor']
Out[15]: 'Cisco'
get()
Если при обращении к словарю указывается ключ, которого нет в словаре, возникает
ошибка:
In [17]: london['ios']
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-17-b4fae8480b21> in <module>()
----> 1 london['ios']
KeyError: 'ios'
Метод get() запрашивает ключ и, если его нет, вместо ошибки возвращает None .
In [19]: print(london.get('ios'))
None
setdefault()
117
Словарь (Dictionary)
Метод setdefault() ищет ключ и, если его нет, вместо ошибки создает ключ со
значением None .
In [23]: print(ios)
None
In [24]: london
Out[24]: {'ios': None, 'location': 'London Str', 'name': 'London1', 'vendor': 'Cisco'}
Но, если ключ есть, setdefault возвращает значение, которое ему соответствует:
In [25]: london.setdefault('name')
Out[25]: 'London1'
In [27]: print(model)
Cisco3580
In [28]: london
Out[28]:
{'ios': None,
'model': 'Cisco3580',
'location': 'London Str',
'name': 'London1',
'vendor': 'Cisco'}
118
Словарь (Dictionary)
In [25]: london.keys()
Out[25]: dict_keys(['name', 'location', 'vendor'])
In [26]: london.values()
Out[26]: dict_values(['London1', 'London Str', 'Cisco'])
In [27]: london.items()
Out[27]: dict_items([('name', 'London1'), ('location', 'London Str'), ('vendor', 'Cisc
o')])
Все три метода возвращают специальные объекты view, которые отображают ключи,
значения и пары ключ-значение словаря соответственно.
Очень важная особенность view заключается в том, что они меняются вместе с
изменением словаря. И фактически они лишь дают способ посмотреть на
соответствующие объекты, но не создают их копию.
In [30]: print(keys)
dict_keys(['name', 'location', 'vendor'])
Сейчас переменной keys соответствует view dict_keys, в котором три ключа: name,
location и vendor.
Но, если мы добавим в словарь еще одну пару ключ-значение, объект keys тоже
поменяется:
In [32]: keys
Out[32]: dict_keys(['name', 'location', 'vendor', 'ip'])
119
Словарь (Dictionary)
In [34]: list_keys
Out[34]: ['name', 'location', 'vendor', 'ip']
del
In [36]: del(london['name'])
In [37]: london
Out[37]: {'location': 'London Str', 'vendor': 'Cisco'}
update
Метод update позволяет добавлять в словарь содержимое другого словаря:
In [40]: r1
Out[40]: {'ios': '15.2', 'location': 'London Str', 'name': 'London1', 'vendor': 'Cisco'
}
In [42]: r1
Out[42]:
{'ios': '15.4',
'location': 'London Str',
'name': 'london-r1',
'vendor': 'Cisco'}
120
Словарь (Dictionary)
dict
Конструктор dict позволяет создавать словарь несколькими способами.
Если в роли ключей используются строки, можно использовать такой вариант создания
словаря:
In [3]: r1
Out[3]: {'ios': '15.4', 'model': '4451'}
In [5]: r1
Out[5]: {'ios': '15.4', 'model': '4451'}
dict.fromkeys
В ситуации, когда надо создать словарь с известными ключами, но, пока что, пустыми
значениями (или одинаковыми значениями), очень удобен метод fromkeys():
121
Словарь (Dictionary)
In [6]: r1 = dict.fromkeys(d_keys)
In [7]: r1
Out[7]:
{'ios': None,
'ip': None,
'hostname': None,
'location': None,
'model': None,
'vendor': None}
In [10]: models_count
Out[10]: {'ASR9002': 0, 'ISR2811': 0, 'ISR2911': 0, 'ISR2921': 0}
Этот вариант создания словаря подходит не для всех случаев. Например, при
использовании изменяемого типа данных в значении, будет создана ссылка на один и
тот же объект:
In [13]: routers
Out[13]: {'ASR9002': [], 'ISR2811': [], 'ISR2911': [], 'ISR2921': []}
In [14]: routers['ASR9002'].append('london_r1')
In [15]: routers
Out[15]:
{'ASR9002': ['london_r1'],
'ISR2811': ['london_r1'],
'ISR2911': ['london_r1'],
'ISR2921': ['london_r1']}
В данном случае каждый ключ ссылается на один и тот же список. Поэтому, при
добавлении значения в один из списков обновляются и остальные.
122
Словарь (Dictionary)
In [18]: routers
Out[18]: {'ASR9002': [], 'ISR2811': [], 'ISR2911': [], 'ISR2921': []}
In [19]: routers['ASR9002'].append('london_r1')
In [20]: routers
Out[20]: {'ASR9002': ['london_r1'], 'ISR2811': [], 'ISR2911': [], 'ISR2921': []}
123
Кортеж (Tuple)
Кортеж (Tuple)
Кортеж - это неизменяемый упорядоченный тип данных.
Грубо говоря, кортеж - это список, который нельзя изменить. То есть, в кортеже есть
только права на чтение. Это может быть защитой от случайных изменений.
In [2]: print(tuple1)
()
Кортеж из списка:
In [6]: tuple_keys
Out[6]: ('hostname', 'location', 'vendor', 'model', 'IOS', 'IP')
In [7]: tuple_keys[0]
Out[7]: 'hostname'
124
Кортеж (Tuple)
125
Множество (Set)
Множество (Set)
Множество - это изменяемый неупорядоченный тип данных. В множестве всегда
содержатся только уникальные элементы.
In [2]: set(vlans)
Out[2]: {10, 20, 30, 40, 100}
In [4]: print(set1)
{40, 100, 10, 20, 30}
126
Множество (Set)
add()
In [2]: set1.add(50)
In [3]: set1
Out[3]: {10, 20, 30, 40, 50}
discard()
In [3]: set1
Out[3]: {10, 20, 30, 40, 50}
In [4]: set1.discard(55)
In [5]: set1
Out[5]: {10, 20, 30, 40, 50}
In [6]: set1.discard(50)
In [7]: set1
Out[7]: {10, 20, 30, 40}
clear()
In [9]: set1.clear()
In [10]: set1
Out[10]: set()
127
Множество (Set)
128
Множество (Set)
Операции с множествами
Множества полезны тем, что с ними можно делать различные операции и находить
объединение множеств, пересечение и так далее.
In [3]: vlans1.union(vlans2)
Out[3]: {10, 20, 30, 50, 100, 101, 102, 200}
In [7]: vlans1.intersection(vlans2)
Out[7]: {100}
129
Множество (Set)
In [1]: set1 = {}
In [2]: type(set1)
Out[2]: dict
In [4]: type(set2)
Out[4]: set
Множество из строки:
Множество из списка:
In [6]: set([10,20,30,10,10,30])
Out[6]: {10, 20, 30}
Генератор множеств:
In [8]: set2
Out[8]: {100, 101, 102, 103, 104, 105, 106, 107, 108, 109}
In [9]: print(set2)
{100, 101, 102, 103, 104, 105, 106, 107, 108, 109}
130
Преобразование типов
Преобразование типов
В Python есть несколько полезных встроенных функций, которые позволяют
преобразовать данные из одного типа в другой.
int()
int() - преобразует строку в int:
In [1]: int("10")
Out[1]: 10
In [2]: int("11111111", 2)
Out[2]: 255
bin()
Преобразовать десятичное число в двоичный формат можно с помощью bin() :
In [3]: bin(10)
Out[3]: '0b1010'
In [4]: bin(255)
Out[4]: '0b11111111'
hex()
Аналогичная функция есть и для преобразования в шестнадцатеричный формат:
In [5]: hex(10)
Out[5]: '0xa'
In [6]: hex(255)
Out[6]: '0xff'
list()
131
Преобразование типов
In [7]: list("string")
Out[7]: ['s', 't', 'r', 'i', 'n', 'g']
In [8]: list({1,2,3})
Out[8]: [1, 2, 3]
In [9]: list((1,2,3,4))
Out[9]: [1, 2, 3, 4]
set()
Функция set() преобразует аргумент в множество:
In [10]: set([1,2,3,3,4,4,4,4])
Out[10]: {1, 2, 3, 4}
In [11]: set((1,2,3,3,4,4,4,4))
Out[11]: {1, 2, 3, 4}
tuple()
Функция tuple() преобразует аргумент в кортеж:
In [13]: tuple([1,2,3,4])
Out[13]: (1, 2, 3, 4)
In [14]: tuple({1,2,3,4})
Out[14]: (1, 2, 3, 4)
In [15]: tuple("string")
Out[15]: ('s', 't', 'r', 'i', 'n', 'g')
Это может пригодиться в том случае, если нужно получить неизменяемый объект.
str()
Функция str() преобразует аргумент в строку:
132
Преобразование типов
In [16]: str(10)
Out[16]: '10'
Например, она пригодится в ситуации, когда есть список VLANов, который надо
преобразовать в одну строку, где номера перечислены через запятую.
In [18]: ','.join(vlans)
------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-39-d705aed3f1b3> in <module>()
----> 1 ','.join(vlans)
Чтобы исправить это, нужно преобразовать числа в строки. Это удобно делать с
помощью list comprehensions:
133
Проверка типов
Проверка типов
При преобразовании типов данных могут возникнуть ошибки такого рода:
In [1]: int('a')
------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-42-b3c3f4515dd4> in <module>()
----> 1 int('a')
И если тут пример выглядит, возможно, глупым, тем не менее, когда нужно, например,
пройтись по списку строк и преобразовать в числа те из них, которые содержат числа,
можно получить такую ошибку.
isdigit()
В Python такие методы есть. Например, чтобы проверить, состоит ли строка из одних
цифр, можно использовать метод isdigit() :
In [2]: "a".isdigit()
Out[2]: False
In [3]: "a10".isdigit()
Out[3]: False
In [4]: "10".isdigit()
Out[4]: True
isalpha()
134
Проверка типов
In [7]: "a".isalpha()
Out[7]: True
In [8]: "a100".isalpha()
Out[8]: False
isalnum()
In [11]: "a".isalnum()
Out[1]: True
In [12]: "a10".isalnum()
Out[12]: True
type()
In [13]: type("string")
Out[13]: str
135
Проверка типов
In [15]: type((1,2,3))
Out[15]: tuple
136
Дополнительные материалы
Дополнительные материалы
Документация:
Форматирование строк:
137
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 4.1
Обработать строку NAT таким образом, чтобы в имени интерфейса вместо
FastEthernet было GigabitEthernet.
NAT = "ip nat inside source list ACL interface FastEthernet0/1 overload"
Задание 4.2
Преобразовать строку MAC из формата XXXX:XXXX:XXXX в формат
XXXX.XXXX.XXXX
MAC = 'AAAA:BBBB:CCCC'
Задание 4.3
Получить из строки CONFIG список VLANов вида: ['1', '3', '10', '20', '30', '100']
Задание 4.4
138
Задания
Для данного примера, результатом должен быть список: [1, 3, 100] Этот список
содержит подсказку по типу итоговых данных.
Задание 4.5
Список VLANS это список VLANов, собранных со всех устройств сети, поэтому в
списке есть повторяющиеся номера VLAN.
Задание 4.6
Обработать строку ospf_route и вывести информацию на стандартный поток вывода в
виде:
Protocol: OSPF
Prefix: 10.0.24.0/24
AD/Metric: 110/41
Next-Hop: 10.0.13.3
Last update: 3d18h
Outbound Interface: FastEthernet0/0
Задание 4.7
Преобразовать MAC-адрес в двоичную строку (без двоеточий).
139
Задания
MAC = 'AAAA:BBBB:CCCC'
Задание 4.8
Преобразовать IP-адрес (переменная IP) в двоичный формат и вывести вывод
столбцами на стандартный поток вывода, таким образом:
столбцами
ширина столбца 10 символов
Пример вывода:
10 1 1 1
00001010 00000001 00000001 00000001
IP = '192.168.3.1'
140
5. Создание базовых скриптов
print('\n'.join(access_template).format(5))
$ python access_template.py
switchport mode access
switchport access vlan 5
switchport nonegotiate
spanning-tree portfast
spanning-tree bpduguard enable
Но, если Вы используете Windows, то это желательно делать, так как Windows
использует расширение файла для определения того, как обрабатывать файл.
Исполняемый файл
141
5. Создание базовых скриптов
Для того, чтобы файл был исполняемым, и не нужно было каждый раз писать python
перед вызовом файла, нужно:
по умолчанию
#!/usr/bin/env python3
print('\n'.join(access_template).format(5))
После этого:
chmod +x access_template_exec.py
$ ./access_template_exec.py
142
Передача аргументов скрипту
Гораздо лучше будет передавать имя файла как аргумент скрипта и затем
использовать уже указанный файл.
В модуле sys есть очень простой и удобный способ для работы с аргументами - argv.
print('interface {}'.format(interface))
print('\n'.join(access_template).format(vlan))
143
Передача аргументов скрипту
Выражение argv[1:] должно быть знакомым. Это срез списка. То есть, в правой
стороне остается список с двумя элементами: ['Gi0/7', '4'] .
In [16]: a = 5
In [17]: b = 6
In [18]: c, d = 5, 6
In [19]: c
Out[19]: 5
In [20]: d
Out[20]: 6
In [23]: interface
Out[23]: 'Gi0/7'
In [24]: vlan
Out[24]: '4'
144
Ввод информации пользователем
В данном случае информация просто тут же выводится пользователю, но, кроме этого,
информация, которую ввел пользователь, может быть сохранена в какую-то
переменную и может использоваться далее в скрипте.
In [3]: print(protocol)
OSPF
In [6]: print(protocol)
OSPF
145
Ввод информации пользователем
Выполняем скрипт:
$ python access_template_input.py
Enter interface type and number: Gi0/3
Enter VLAN number: 55
------------------------------
interface Gi0/3
switchport mode access
switchport access vlan 55
switchport nonegotiate
spanning-tree portfast
spanning-tree bpduguard enable
146
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 5.1
Запросить у пользователя ввод IP-сети в формате: 10.1.1.0/24
Network:
10 1 1 0
00001010 00000001 00000001 00000000
Mask:
/24
255 255 255 0
11111111 11111111 11111111 00000000
Задание 5.1a
Всё, как в задании 5.1. Но, если пользователь ввел адрес хоста, а не адрес сети, то
надо адрес хоста преобразовать в адрес сети и вывести адрес сети и маску, как в
задании 5.1.
10.0.1.0/24
190.1.0.0/16
147
Задания
Network:
10 0 1 0
00001010 00000000 00000001 00000000
Mask:
/24
255 255 255 0
11111111 11111111 11111111 00000000
Задание 5.1b
Преобразовать скрипт из задания 5.1a таким образом, чтобы сеть/маска не
запрашивались у пользователя, а передавались как аргумент скрипту.
Задание 5.2
В задании создан словарь с информацией о разных устройствах.
Вам нужно запросить у пользователя ввод имени устройства (r1, r2 или sw1). И
вывести информацию о соответствующем устройстве на стандартный поток вывода
(информация будет в виде словаря).
$ python task_5_2.py
Enter device name: r1
{'ios': '15.4', 'model': '4451', 'vendor': 'Cisco', 'location': '21 New Globe Walk', '
ip': '10.255.0.1'}
Все задания надо выполнять используя только пройденные темы. То есть эту задачу
можно решить без использования условия if.
148
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 5.2a
Переделать скрипт из задания 5.2 таким образом, чтобы, кроме имени устройства,
запрашивался также параметр устройства, который нужно отобразить.
$ python task_5_2a.py
Enter device name: r1
Enter parameter name: ios
15.4
Все задания надо выполнять используя только пройденные темы. То есть эту задачу
можно решить без использования условия if.
149
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 5.2b
Переделать скрипт из задания 5.2a таким образом, чтобы, при запросе параметра,
отображался список возможных параметров.
$ python task_5_2b.py
Enter device name: r1
Enter parameter name (ios,model,vendor,location,ip): ip
10.255.0.1
Все задания надо выполнять используя только пройденные темы. То есть эту задачу
можно решить без использования условия if.
150
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 5.2c
Переделать скрипт из задания 5.2b таким образом, чтобы, при запросе параметра,
которого нет в словаре устройства, отображалось сообщение 'Такого параметра нет'.
$ python task_5_2c.py
Enter device name: r1
Enter parameter name (ios,model,vendor,location,ip): io
Такого параметра нет
Все задания надо выполнять используя только пройденные темы. То есть эту задачу
можно решить без использования условия if.
151
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 5.2d
Переделать скрипт из задания 5.2c таким образом, чтобы, при запросе параметра,
пользователь мог вводить название параметра в любом регистре.
$ python task_5_2d.py
Enter device name: r1
Enter parameter name (ios,model,vendor,location,ip): IOS
15.4
Все задания надо выполнять используя только пройденные темы. То есть эту задачу
можно решить без использования условия if.
152
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 5.3
Скрипт должен запрашивать у пользователя:
При этом, сначала должна идти строка interface и подставлен номер интерфейса, а
затем соответствующий шаблон, в который подставлен номер VLANа (или список
VLANов).
153
Задания
$ python task_5_3.py
Enter interface mode (access/trunk): access
Enter interface type and number: Fa0/6
Enter vlan(s): 3
interface Fa0/6
switchport mode access
switchport access vlan 3
switchport nonegotiate
spanning-tree portfast
spanning-tree bpduguard enable
$ python task_5_3.py
Enter interface mode (access/trunk): trunk
Enter interface type and number: Fa0/7
Enter vlan(s): 2,3,4,5
interface Fa0/7
switchport trunk encapsulation dot1q
switchport mode trunk
switchport trunk allowed vlan 2,3,4,5
Задание 5.3a
Дополнить скрипт из задания 5.3 таким образом, чтобы, в зависимости от выбранного
режима, задавались разные вопросы в запросе о номере VLANа или списка VLANов:
154
Задания
Задание 5.4
Найти индекс последнего вхождения элемента.
Для этого надо запросить у пользователя сначала ввод числа из списка num_list и
затем вывести индекс его последнего появления в списке. А затем аналогично для
списка word_list.
155
6. Контроль хода программы
if/elif/else
циклом for
циклом while
Раздел про конструкции for/else и while/else, возможно, будет проще понять, если
прочесть их после раздела об обработке исключений.
156
if/elif/else
if/elif/else
Конструкция if/elif/else дает возможность выполнять различные действия в
зависимости от условий.
Пример конструкции:
In [1]: a = 9
In [2]: if a == 10:
...: print('a равно 10')
...: elif a < 10:
...: print('a меньше 10')
...: else:
...: print('a больше 10')
...:
a меньше 10
157
if/elif/else
In [7]: 5 > 3
Out[7]: True
In [8]: 5 == 5
Out[8]: True
In [10]: 1 in [ 1, 2, 3 ]
Out[10]: True
In [11]: 0 in [ 1, 2, 3 ]
Out[11]: False
True и False
В Python:
True (истина)
любое ненулевое число
любая непустая строка
любой непустой объект
False (ложь)
0
None
пустая строка
пустой объект
Остальные значения True или False, как правило, логически следуют из условия.
Например, так как пустой список это ложь, проверить, пустой ли список, можно таким
образом:
In [13]: if list_to_test:
....: print("В списке есть объекты")
....:
В списке есть объекты
158
if/elif/else
In [14]: if len(list_to_test) != 0:
....: print("В списке есть объекты")
....:
В списке есть объекты
Операторы сравнения
Операторы сравнения, которые могут использоваться в условиях:
In [3]: 5 > 6
Out[3]: False
In [4]: 5 > 2
Out[4]: True
In [5]: 5 < 2
Out[5]: False
In [6]: 5 == 2
Out[6]: False
In [7]: 5 == 5
Out[7]: True
In [8]: 5 >= 5
Out[8]: True
In [9]: 5 <= 10
Out[9]: True
In [10]: 8 != 10
Out[10]: True
Оператор in
Оператор in позволяет выполнять проверку на наличие элемента в
последовательности (например, элемента в списке или подстроки в строке):
159
if/elif/else
In [11]: 10 in vlan
Out[11]: True
In [12]: 50 in vlan
Out[12]: False
In [15]: r1 = {
....: 'IOS': '15.4',
....: 'IP': '10.255.0.1',
....: 'hostname': 'london_r1',
....: 'location': '21 New Globe Walk',
....: 'model': '4451',
....: 'vendor': 'Cisco'}
In [16]: 'IOS' in r1
Out[16]: True
In [17]: '4451' in r1
Out[17]: False
160
if/elif/else
In [15]: r1 = {
....: 'IOS': '15.4',
....: 'IP': '10.255.0.1',
....: 'hostname': 'london_r1',
....: 'location': '21 New Globe Walk',
....: 'model': '4451',
....: 'vendor': 'Cisco'}
Оператор and
В Python оператор and возвращает не булево значение, а значение одного из
операторов.
161
if/elif/else
Оператор or
Оператор or , как и оператор and, возвращает значение одного из операторов.
In [31]: '' or [] or {}
Out[31]: {}
if len(password) < 8:
print('Пароль слишком короткий')
elif username in password:
print('Пароль содержит имя пользователя')
else:
print('Пароль для пользователя {} установлен'.format(username))
162
if/elif/else
Проверка скрипта:
$ python check_password.py
Введите имя пользователя: nata
Введите пароль: nata1234
Пароль содержит имя пользователя
$ python check_password.py
Введите имя пользователя: nata
Введите пароль: 123nata123
Пароль содержит имя пользователя
$ python check_password.py
Введите имя пользователя: nata
Введите пароль: 1234
Пароль слишком короткий
$ python check_password.py
Введите имя пользователя: nata
Введите пароль: 123456789
Пароль для пользователя nata установлен
s = [1, 2, 3, 4]
result = True if len(s) > 5 else False
163
for
for
Цикл for проходится по указанной последовательности и выполняет действия, которые
указаны в блоке for.
строка
список
словарь
функция range()
любой итерируемый объект
s
t
r
i
n
g
В цикле используется переменная с именем letter. Хотя имя может быть любое,
удобно, когда имя подсказывает, через какие объекты проходит цикл.
164
for
В этом примере цикл проходит по списку VLANов, поэтому переменную можно назвать
vlan:
165
for
In [5]: r1 = {
'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'}
В словаре есть специальный метод items, который позволяет проходится в цикле сразу
по паре ключ:значение:
Метод items возвращает специальный объект view, который отображает пары ключ-
значение:
166
for
In [9]: r1.items()
Out[9]: dict_items([('IOS', '15.4'), ('IP', '10.255.0.1'), ('hostname', 'london_r1'), (
'location', '21 New Globe Walk'), ('model', '4451'), ('vendor', 'Cisco')])
167
for
Вложенные for
Циклы for можно вкладывать друг в друга.
В этом примере в списке commands хранятся команды, которые надо выполнить для
каждого из интерфейсов в списке fast_int:
168
for
Совмещение for и if
Рассмотрим пример совмещения for и if.
Файл generate_access_port_config.py:
Комментарии к коду:
169
for
$ python generate_access_port_config.py
interface FastEthernet0/12
switchport mode access
switchport access vlan 10
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/14
switchport mode access
switchport access vlan 11
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/16
switchport mode access
switchport access vlan 17
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/17
switchport mode access
switchport access vlan 150
spanning-tree portfast
spanning-tree bpduguard enable
170
while
while
Цикл while - это еще одна разновидность цикла в Python.
В цикле while, как и в выражении if, надо писать условие. Если условие истинно,
выполняются действия внутри блока while. Но, в отличии от if, после выполнения while
возвращается в начало цикла.
In [1]: a = 5
Затем, в цикле while указано условие a > 0. То есть, пока значение а больше 0, будут
выполняться действия в теле цикла. В данном случае, будет выводиться значение
переменной а.
Кроме того, в теле цикла при каждом прохождении значение а становится на единицу
меньше.
171
while
С помощью цикла while можно сделать так, что скрипт сам будет запрашивать пароль
заново, если он не соответствует требованиям.
Файл check_password_with_while.py:
password_correct = False
В этом случае цикл while полезен, так как он возвращает скрипт снова в начало
проверок, позволяет снова набрать пароль, но при этом не требует перезапуска самого
скрипта.
$ python check_password_with_while.py
Введите имя пользователя: nata
Введите пароль: nata
Пароль слишком короткий
172
break, continue, pass
Оператор break
Оператор break позволяет досрочно прервать цикл:
In [2]: i = 0
In [3]: while i < 10:
...: if i == 5:
...: break
...: else:
...: print(i)
...: i += 1
...:
0
1
2
3
4
173
break, continue, pass
while True:
if len(password) < 8:
print('Пароль слишком короткий\n')
elif username in password:
print('Пароль содержит имя пользователя\n')
else:
print('Пароль для пользователя {} установлен'.format(username))
# завершает цикл while
break
password = input('Введите пароль еще раз: ')
Теперь можно не повторять строку password = input('Введите пароль еще раз: ') в
каждом ответвлении, достаточно перенести ее в конец цикла.
И, как только будет введен правильный пароль, break выведет программу из цикла
while.
Оператор continue
Оператор continue возвращает управление в начало цикла. То есть, continue позволяет
"перепрыгнуть" оставшиеся выражения в цикле и перейти к следующей итерации.
174
break, continue, pass
In [5]: i = 0
In [6]: while i < 6:
....: i += 1
....: if i == 3:
....: print("Пропускаем 3")
....: continue
....: print("Это никто не увидит")
....: else:
....: print("Текущее значение: ", i)
....:
Текущее значение: 1
Текущее значение: 2
Пропускаем 3
Текущее значение: 4
Текущее значение: 5
Текущее значение: 6
password_correct = False
175
break, continue, pass
$ python check_password_with_while_continue.py
Введите имя пользователя: nata
Введите пароль: nata12
Пароль слишком короткий
Оператор pass
Оператор pass ничего не делает. Фактически, это такая заглушка для объектов.
Например, pass может помочь в ситуации, когда нужно прописать структуру скрипта.
Его можно ставить в циклах, функциях, классах. И это не будет влиять на исполнение
кода.
176
for/else, while/else
for/else, while/else
В циклах for и while опционально может использоваться блок else.
for/else
В цикле for:
блок else выполняется в том случае, если цикл завершил итерацию списка
но else не выполняется, если в цикле был выполнен break
Пример цикла for с else (блок else выполняется после завершения цикла for):
Пример цикла for с else и break в цикле (из-за break блок else не выполняется):
Пример цикла for с else и continue в цикле (continue не влияет на блок else):
177
for/else, while/else
while/else
В цикле while:
блок else выполняется в том случае, если цикл завершил итерацию списка
но else не выполняется, если в цикле был выполнен break
Пример цикла while с else (блок else выполняется после завершения цикла while):
In [4]: i = 0
In [5]: while i < 5:
....: print(i)
....: i += 1
....: else:
....: print("Конец")
....:
0
1
2
3
4
Конец
Пример цикла while с else и break в цикле (из-за break блок else не выполняется):
178
for/else, while/else
In [6]: i = 0
179
Работа с исключениями try/except/else/finally
Но, даже если код синтаксически написан правильно, все равно могут возникать
ошибки. Эти ошибки называются исключения (exceptions).
Примеры исключений:
In [1]: 2/0
-----------------------------------------------------
ZeroDivisionError: division by zero
In [2]: 'test' + 2
-----------------------------------------------------
TypeError: must be str, not int
Например, если программа на вход ожидает два числа, а на выходе выдает их сумму,
а пользователь ввел вместо одного из чисел строку, появится ошибка TypeError, как в
примере выше.
180
Работа с исключениями try/except/else/finally
In [3]: try:
...: 2/0
...: except ZeroDivisionError:
...: print("You can't divide by zero")
...:
You can't divide by zero
In [4]: try:
...: print("Let's divide some numbers")
...: 2/0
...: print('Cool!')
...: except ZeroDivisionError:
...: print("You can't divide by zero")
...:
Let's divide some numbers
You can't divide by zero
В конструкции try/except может быть много except, если нужны разные действия в
зависимости от типа ошибки.
try:
a = input("Введите первое число: ")
b = input("Введите второе число: ")
print("Результат: ", int(a)/int(b))
except ValueError:
print("Пожалуйста, вводите только числа")
except ZeroDivisionError:
print("На ноль делить нельзя")
181
Работа с исключениями try/except/else/finally
$ python divide.py
Введите первое число: 3
Введите второе число: 1
Результат: 3
$ python divide.py
Введите первое число: 5
Введите второе число: 0
На ноль делить нельзя
$ python divide.py
Введите первое число: qewr
Введите второе число: 3
Пожалуйста, вводите только числа
try:
a = input("Введите первое число: ")
b = input("Введите второе число: ")
print("Результат: ", int(a)/int(b))
except (ValueError, ZeroDivisionError):
print("Что-то пошло не так...")
Проверка:
$ python divide_ver2.py
Введите первое число: wer
Введите второе число: 4
Что-то пошло не так...
$ python divide_ver2.py
Введите первое число: 5
Введите второе число: 0
Что-то пошло не так...
182
Работа с исключениями try/except/else/finally
try/except/else
В конструкции try/except есть опциональный блок else. Он выполняется в том случае,
если не было исключения.
try:
a = input("Введите первое число: ")
b = input("Введите второе число: ")
result = int(a)/int(b)
except (ValueError, ZeroDivisionError):
print("Что-то пошло не так...")
else:
print("Результат в квадрате: ", result**2)
Пример выполнения:
$ python divide_ver3.py
Введите первое число: 10
Введите второе число: 2
Результат в квадрате: 25
$ python divide_ver3.py
Введите первое число: werq
Введите второе число: 3
Что-то пошло не так...
try/except/finally
Блок finally - это еще один опциональный блок в конструкции try. Он выполняется
всегда, независимо от того, было ли исключение или нет.
Сюда ставятся действия, которые надо выполнить в любом случае. Например, это
может быть закрытие файла.
183
Работа с исключениями try/except/else/finally
try:
a = input("Введите первое число: ")
b = input("Введите второе число: ")
result = int(a)/int(b)
except (ValueError, ZeroDivisionError):
print("Что-то пошло не так...")
else:
print("Результат в квадрате: ", result**2)
finally:
print("Вот и сказочке конец, а кто слушал - молодец.")
Проверка:
$ python divide_ver4.py
Введите первое число: 10
Введите второе число: 2
Результат в квадрате: 25
Вот и сказочке конец, а кто слушал - молодец.
$ python divide_ver4.py
Введите первое число: qwerewr
Введите второе число: 3
Что-то пошло не так...
Вот и сказочке конец, а кто слушал - молодец.
$ python divide_ver4.py
Введите первое число: 4
Введите второе число: 0
Что-то пошло не так...
Вот и сказочке конец, а кто слушал - молодец.
184
Работа с исключениями try/except/else/finally
while True:
a = input("Введите число: ")
b = input("Введите второе число: ")
try:
result = int(a)/int(b)
except ValueError:
print("Поддерживаются только числа")
except ZeroDivisionError:
print("На ноль делить нельзя")
else:
print(result)
break
while True:
a = input("Введите число: ")
b = input("Введите второе число: ")
if a.isdigit() and b.isdigit():
if int(b) == 0:
print("На ноль делить нельзя")
else:
print(int(a)/int(b))
break
else:
print("Поддерживаются только числа")
Важно в каждой конкретной ситуации оценивать, какой вариант кода более понятный,
компактный и универсальный - с исключениями или без.
185
Дополнительные материалы
Дополнительные материалы
Документация:
Статьи:
Stackoverflow:
Why does python use 'else' after for and while loops?
Is it a good practice to use try-except-else in Python?
186
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 6.1
1. Запросить у пользователя ввод IP-адреса в формате 10.0.1.1.
2. Определить какому классу принадлежит IP-адрес.
3. В зависимости от класса адреса, вывести на стандартный поток вывода:
'unicast' - если IP-адрес принадлежит классу A, B или C
'multicast' - если IP-адрес принадлежит классу D
'local broadcast' - если IP-адрес равен 255.255.255.255
'unassigned' - если IP-адрес равен 0.0.0.0
'unused' - во всех остальных случаях
A: 1-127
B: 128-191
C: 192-223
D: 224-239
Задание 6.1a
Сделать копию скрипта задания 6.1.
Дополнить скрипт:
187
Задания
Задание 6.1b
Сделать копию скрипта задания 6.1a.
Дополнить скрипт:
Задание 6.2
Список mac содержит MAC-адреса в формате XXXX:XXXX:XXXX. Однако, в
оборудовании cisco MAC-адреса используются в формате XXXX.XXXX.XXXX.
mac_cisco = []
Задание 6.3
В скрипте сделан генератор конфигурации для access-портов.
В транках ситуация усложняется тем, что VLANов может быть много, и надо понимать,
что с ними делать.
add - значит VLANы надо будет добавить (команда switchport trunk allowed vlan add
10,20)
188
Задания
del - значит VLANы надо удалить из списка разрешенных (команда switchport trunk
allowed vlan remove 17)
only - значит, что на интерфейсе должны остаться разрешенными только
указанные VLANы (команда switchport trunk allowed vlan 11,30)
fast_int = {'access':{'0/12':'10','0/14':'11','0/16':'17','0/17':'150'},
'trunk':{'0/1':['add','10','20'],
'0/2':['only','11','30'],
'0/4':['del','17']} }
189
7. Работа с файлами
Работа с файлами
В реальной жизни, для того чтобы полноценно использовать всё, что рассматривалось
до этого раздела, надо разобраться как работать с файлами.
открытие/закрытие
чтение
запись
190
Открытие файлов
Открытие файлов
Для начала работы с файлом, его надо открыть.
open()
В функции open():
Функция open() создает объект file, к которому потом можно применять различные
методы, для работы с ним.
191
Чтение файлов
Чтение файлов
В Python есть несколько методов чтения файла:
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
read()
In [1]: f = open('r1.txt')
In [2]: f.read()
Out[2]: '!\nservice timestamps debug datetime msec localtime show-timezone year\nservi
ce timestamps log datetime msec localtime show-timezone year\nservice password-encrypt
ion\nservice sequence-numbers\n!\nno ip domain lookup\n!\nip ssh version 2\n!\n'
In [3]: f.read()
Out[3]: ''
При повторном чтении файла в 3 строке, отображается пустая строка. Так происходит
из-за того, что при вызове метода read() , считывается весь файл. И после того, как
файл был считан, курсор остается в конце файла. Управлять положением курсора
можно с помощью метода seek() .
readline()
192
Чтение файлов
In [4]: f = open('r1.txt')
In [5]: f.readline()
Out[5]: '!\n'
In [6]: f.readline()
Out[6]: 'service timestamps debug datetime msec localtime show-timezone year\n'
Но, чаще всего, проще пройтись по объекту file в цикле, не используя методы read... :
In [7]: f = open('r1.txt')
service password-encryption
service sequence-numbers
no ip domain lookup
ip ssh version 2
readlines()
193
Чтение файлов
In [9]: f = open('r1.txt')
In [10]: f.readlines()
Out[10]:
['!\n',
'service timestamps debug datetime msec localtime show-timezone year\n',
'service timestamps log datetime msec localtime show-timezone year\n',
'service password-encryption\n',
'service sequence-numbers\n',
'!\n',
'no ip domain lookup\n',
'!\n',
'ip ssh version 2\n',
'!\n']
Если нужно получить строки файла, но без перевода строки в конце, можно
воспользоваться методом split и как разделитель, указать символ \n :
In [11]: f = open('r1.txt')
In [12]: f.read().split('\n')
Out[12]:
['!',
'service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'!',
'no ip domain lookup',
'!',
'ip ssh version 2',
'!',
'']
194
Чтение файлов
In [13]: f = open('r1.txt')
In [14]: f.read().rstrip().split('\n')
Out[14]:
['!',
'service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'!',
'no ip domain lookup',
'!',
'ip ssh version 2',
'!']
seek()
До сих пор, файл каждый раз приходилось открывать заново, чтобы снова его считать.
Так происходит из-за того, что после методов чтения, курсор находится в конце файла.
И повторное чтение возвращает пустую строку.
In [15]: f = open('r1.txt')
In [16]: print(f.read())
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
In [17]: print(f.read())
Но, с помощью метода seek , можно перейти в начало файла (0 означает начало
файла):
195
Чтение файлов
In [18]: f.seek(0)
После того, как, с помощью seek , курсор был переведен в начало файла, можно
In [19]: print(f.read())
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
196
Запись файлов
Запись файлов
При записи, очень важно определиться с режимом открытия файла, чтобы случайно
его не удалить:
write()
197
Запись файлов
In [4]: cfg_lines_as_string
Out[4]: '!\nservice timestamps debug datetime msec localtime show-timezone year\nservi
ce timestamps log datetime msec localtime show-timezone year\nservice password-encrypt
ion\nservice sequence-numbers\n!\nno ip domain lookup\n!\nip ssh version 2\n!'
In [5]: f.write(cfg_lines_as_string)
In [7]: f.close()
Так как ipython поддерживает команду cat, можно легко посмотреть содержимое
файла:
writelines()
198
Запись файлов
In [10]: f.writelines(cfg_lines)
In [11]: f.close()
В результате, все строки из списка, записались в одну строку файла, так как в конце
строк не было символа \n .
In [13]: cfg_lines2 = []
In [15]: cfg_lines2
Out[15]:
['!\n',
'service timestamps debug datetime msec localtime show-timezone year\n',
'service timestamps log datetime msec localtime show-timezone year\n',
'service password-encryption\n',
'service sequence-numbers\n',
'!\n',
'no ip domain lookup\n',
'!\n',
'ip ssh version 2\n',
199
Запись файлов
In [17]: cfg_lines3
Out[17]:
['!\n',
'service timestamps debug datetime msec localtime show-timezone year\n',
'service timestamps log datetime msec localtime show-timezone year\n',
'service password-encryption\n',
'service sequence-numbers\n',
'!\n',
'no ip domain lookup\n',
'!\n',
'ip ssh version 2\n',
'!\n']
Если любой, из получившихся списков записать заново в файл, то в нём уже будут
переводы строк:
In [19]: f.writelines(cfg_lines3)
In [20]: f.close()
200
Закрытие файлов
Закрытие файлов
В реальной жизни, для закрытия файлов, чаще всего, используется конструкция
with . Её намного удобней использовать, чем закрывать файл явно. Но, так как в
жизни можно встретить и метод close , в этом разделе рассматривается как его
использовать.
close()
In [2]: print(f.read())
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
У объекта file есть специальный атрибут closed , который позволяет проверить закрыт
файл или нет.
Если файл открыт, он возвращает False :
201
Закрытие файлов
In [3]: f.closed
Out[3]: False
In [4]: f.close()
In [5]: f.closed
Out[5]: True
In [6]: print(f.read())
------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-53-2c962247edc5> in <module>()
----> 1 print(f.read())
Если попытаться открыть для чтения файл, которого не существует, возникнет такое
исключение:
202
Закрытие файлов
In [8]: try:
....: f = open('r3.txt', 'r')
....: except IOError:
....: print('No such file')
....:
No such file
In [9]: try:
....: f = open('r1.txt', 'r')
....: print(f.read())
....: except IOError:
....: print('No such file')
....: finally:
....: f.close()
....:
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
In [10]: f.closed
Out[10]: True
203
Конструкция with
Конструкция with
Конструкция with называется менеджер контекста.
В Python существует более удобный способ работы с файлами, чем те, которые
использовались до сих пор - конструкция with :
service password-encryption
service sequence-numbers
no ip domain lookup
ip ssh version 2
for line in f:
print(line)
В предыдущем выводе, между строками файла были лишние пустые строки, так как
print добавляет ещё один перевод строки.
204
Конструкция with
In [3]: f.closed
Out[3]: True
В таком случае, в блоке with можно открывать два файла таким образом:
205
Конструкция with
206
Дополнительные материалы
Дополнительные материалы
Документация:
Статьи:
Stackoverflow:
207
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 7.1
Аналогично заданию 4.6 обработать строки из файла ospf.txt и вывести информацию
по каждой в таком виде:
Protocol: OSPF
Prefix: 10.0.24.0/24
AD/Metric: 110/41
Next-Hop: 10.0.13.3
Last update: 3d18h
Outbound Interface: FastEthernet0/0
Задание 7.2
Создать скрипт, который будет обрабатывать конфигурационный файл config_sw1.txt:
Задание 7.2a
208
Задания
Дополнить скрипт:
Задание 7.2b
Дополнить скрипт из задания 7.2a:
При этом, должны быть отфильтрованы строки, которые содержатся в списке ignore.
Задание 7.2c
Переделать скрипт из задания 7.2b:
209
Задания
Задание 7.3
Скрипт должен обрабатывать записи в файле CAM_table.txt таким образом чтобы:
Задание 7.3a
Сделать копию скрипта задания 7.3
Дополнить скрипт:
Задание 7.3b
Сделать копию скрипта задания 7.3a
Дополнить скрипт:
210
8. Примеры использования основ
211
Распаковка переменных
Распаковка переменных
Распаковка переменных - это специальный синтаксис, который позволяет присваивать
переменным элементы итерируемого объекта.
In [3]: intf
Out[3]: 'FastEthernet0/1'
In [4]: ip
Out[4]: '10.1.1.1'
212
Распаковка переменных
Если, например, из строки line надо получить только VLAN, MAC и интерфейс, надо
все равно указать переменную для типа записи:
In [10]: vlan
Out[10]: '100'
In [11]: intf
Out[11]: 'Gi0/1'
Но, если тип записи не нужен в дальнейшем, можно заменить переменную item_type
нижним подчеркиванием:
213
Распаковка переменных
In [15]: mac
Out[15]: '00:09:BB:3D:D6:58'
In [16]: vlan
Out[16]: '10'
Использование *
Распаковка переменных поддерживает специальный синтаксис, который позволяет
распаковывать несколько элементов в один. Если поставить * перед именем
переменной, в нее запишутся все элементы, кроме тех, что присвоены явно.
Например, так можно получить первый элемент в переменную first, а остальные в rest:
In [20]: first
Out[20]: 10
In [21]: rest
Out[21]: [11, 13, 30]
In [23]: first
Out[23]: 10
In [24]: rest
Out[24]: [11, 13, 30]
214
Распаковка переменных
In [26]: first
Out[26]: 55
In [27]: rest
Out[27]: []
In [32]: rest
Out[32]: [10, 11, 13]
In [33]: last
Out[33]: 30
Таким образом можно указать, что нужен первый, второй и последний элемент:
In [36]: name
Out[36]: 'SW1'
In [37]: l_intf
Out[37]: 'Eth'
In [38]: r_intf
Out[38]: '0/1'
215
Распаковка переменных
Примеры распаковки
Распаковка range:
In [40]: first
Out[40]: 1
In [41]: rest
Out[41]: [2, 3, 4, 5]
Распаковка zip:
In [42]: a = [1,2,3,4,5]
In [43]: b = [100,200,300,400,500]
In [44]: zip(a, b)
Out[44]: <zip at 0xb4df4fac>
In [47]: first
Out[47]: (1, 100)
In [48]: rest
Out[48]: [(2, 200), (3, 300), (4, 400)]
In [49]: last
Out[49]: (5, 500)
216
Распаковка переменных
217
Распаковка переменных
In [54]: table
Out[54]:
[['100', 'a1b2.ac10.7000', 'DYNAMIC', 'Gi0/1'],
['200', 'a0d4.cb20.7000', 'DYNAMIC', 'Gi0/2'],
['300', 'acb4.cd30.7000', 'DYNAMIC', 'Gi0/3'],
['100', 'a2bb.ec40.7000', 'DYNAMIC', 'Gi0/4'],
['500', 'aa4b.c550.7000', 'DYNAMIC', 'Gi0/5'],
['200', 'a1bb.1c60.7000', 'DYNAMIC', 'Gi0/6'],
['300', 'aa0b.cc70.7000', 'DYNAMIC', 'Gi0/7']]
218
List, dict, set comprehensions
List comprehensions
Dict comprehensions
Set comprehensions
In [2]: print(vlans)
['vlan 10', 'vlan 11', 'vlan 12', 'vlan 13', 'vlan 14', 'vlan 15']
219
List, dict, set comprehensions
In [3]: vlans = []
In [5]: print(vlans)
['vlan 10', 'vlan 11', 'vlan 12', 'vlan 13', 'vlan 14', 'vlan 15']
In [7]: only_digits = []
In [9]: print(only_digits)
[10, 20, 30, 40]
In [12]: print(only_digits)
[10, 20, 30, 40]
Конечно, далеко не все циклы можно переписать как генератор списка, но когда это
можно сделать, и при этом выражение не усложняется, лучше использовать
генераторы списка.
220
List, dict, set comprehensions
In [13]: london_co = {
...: 'r1' : {
...: 'hostname': 'london_r1',
...: 'location': '21 New Globe Walk',
...: 'vendor': 'Cisco',
...: 'model': '4451',
...: 'IOS': '15.4',
...: 'IP': '10.255.0.1'
...: },
...: 'r2' : {
...: 'hostname': 'london_r2',
...: 'location': '21 New Globe Walk',
...: 'vendor': 'Cisco',
...: 'model': '4451',
...: 'IOS': '15.4',
...: 'IP': '10.255.0.2'
...: },
...: 'sw1' : {
...: 'hostname': 'london_sw1',
...: 'location': '21 New Globe Walk',
...: 'vendor': 'Cisco',
...: 'model': '3850',
...: 'IOS': '3.6.XE',
...: 'IP': '10.255.0.101'
...: }
...: }
Из этого списка надо сформировать один плоский список с номерами VLAN. Первый
вариант, с помощью циклов for:
221
List, dict, set comprehensions
In [17]: result = []
In [19]: print(result)
[10, 21, 35, 101, 115, 150, 111, 40, 50]
In [22]: print(result)
[10, 21, 35, 101, 115, 150, 111, 40, 50]
In [25]: result = ['vlan {}\n name {}'.format(vlan, name) for vlan, name in zip(vlans,
names)]
In [26]: print('\n'.join(result))
vlan 100
name mngmt
vlan 110
name voice
vlan 150
name video
vlan 200
name dmz
222
List, dict, set comprehensions
In [27]: d = {}
In [29]: print(d)
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
In [31]: print(d)
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
In [33]: lower_r1 = {}
In [35]: lower_r1
Out[35]:
{'hostname': 'london_r1',
'ios': '15.4',
'ip': '10.255.0.1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'}
223
List, dict, set comprehensions
In [38]: lower_r1
Out[38]:
{'hostname': 'london_r1',
'ios': '15.4',
'ip': '10.255.0.1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'}
In [39]: london_co = {
...: 'r1' : {
...: 'hostname': 'london_r1',
...: 'location': '21 New Globe Walk',
...: 'vendor': 'Cisco',
...: 'model': '4451',
...: 'IOS': '15.4',
...: 'IP': '10.255.0.1'
...: },
...: 'r2' : {
...: 'hostname': 'london_r2',
...: 'location': '21 New Globe Walk',
...: 'vendor': 'Cisco',
...: 'model': '4451',
...: 'IOS': '15.4',
...: 'IP': '10.255.0.2'
...: },
...: 'sw1' : {
...: 'hostname': 'london_sw1',
...: 'location': '21 New Globe Walk',
...: 'vendor': 'Cisco',
...: 'model': '3850',
...: 'IOS': '3.6.XE',
...: 'IP': '10.255.0.101'
...: }
...: }
In [40]: lower_london_co = {}
224
List, dict, set comprehensions
In [42]: lower_london_co
Out[42]:
{'r1': {'hostname': 'london_r1',
'ios': '15.4',
'ip': '10.255.0.1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'r2': {'hostname': 'london_r2',
'ios': '15.4',
'ip': '10.255.0.2',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'sw1': {'hostname': 'london_sw1',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'location': '21 New Globe Walk',
'model': '3850',
'vendor': 'Cisco'}}
225
List, dict, set comprehensions
In [44]: result
Out[44]:
{'r1': {'hostname': 'london_r1',
'ios': '15.4',
'ip': '10.255.0.1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'r2': {'hostname': 'london_r2',
'ios': '15.4',
'ip': '10.255.0.2',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'sw1': {'hostname': 'london_sw1',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'location': '21 New Globe Walk',
'model': '3850',
'vendor': 'Cisco'}}
In [47]: unique_vlans
Out[47]: {10, 30, 56}
226
List, dict, set comprehensions
In [51]: unique_vlans
Out[51]: {10, 30, 56}
227
Работа со словарями
Работа со словарями
При обработке вывода команд или конфигурации часто надо будет записать итоговые
данные в словарь.
Файл working_with_dict_example_1.py:
result = {}
with open('sh_ip_int_br.txt') as f:
for line in f:
line = line.split()
if line and line[1][0].isdigit():
interface, address, *other = line
result[interface] = address
print(result)
228
Работа со словарями
Полученный в итоге список содержит столбцы вывода. Так как из всего вывода нужны
только интерфейсы на которых настроен IP-адрес, выполняется проверка первого
символа второго столбца: если первый символ число, значит на интерфейсе назначен
адрес и эту строку надо обрабатывать.
Так как для каждой строки есть пара ключ и значение, они присваиваются в словарь:
result[interface] = address .
Результатом выполнения скрипта будет такой словарь (тут он разбит на пары ключ-
значение для удобства, в реальном выводе скрипта словарь будет отображаться в
одну строку):
{'FastEthernet0/0': '15.0.15.1',
'FastEthernet0/1': '10.0.12.1',
'FastEthernet0/2': '10.0.13.1',
'Loopback0': '10.1.1.1',
'Loopback100': '100.0.0.1'}
229
Работа со словарями
Вывод организован таким образом, что всегда сначала идет строка с интерфейсом, а
затем через несоколько строк - строка с MTU. Если запоминать имя интерфейса
каждый раз, когда оно встречается, то на момент когда встретится строка с MTU,
последний запомненный интерфейс - это тот к которому относится MTU.
230
Работа со словарями
Файл working_with_dict_example_2.py:
result = {}
with open('sh_ip_interface.txt') as f:
for line in f:
if 'line protocol' in line:
interface = line.split()[0]
elif 'MTU is' in line:
mtu = line.split()[-2]
result[interface] = mtu
print(result)
Результатом выполнения скрипта будет такой словарь (тут он разбит на пары ключ-
значение для удобства, в реальном выводе скрипта словарь будет отображаться в
одну строку):
{'Ethernet0/0': '1500',
'Ethernet0/1': '1500',
'Ethernet0/2': '1500',
'Ethernet0/3': '1500',
'Loopback0': '1514'}
Этот прием будет достаточно часто полезен, так как вывод команд, в целом,
организован очень похожим образом.
Вложенный словарь
Если из вывода команды надо получить несколько параметров, очень удобно
использовать словарь с вложенным словарем.
231
Работа со словарями
result = {}
with open('sh_ip_interface.txt') as f:
for line in f:
if 'line protocol' in line:
interface = line.split()[0]
result[interface] = {}
elif 'Internet address' in line:
ip_address = line.split()[-1]
result[interface]['ip'] = ip_address
elif 'MTU' in line:
mtu = line.split()[-2]
result[interface]['mtu'] = mtu
print(result)
Каждый раз, когда встречается интерфейс, в словаре result создается ключ с именем
интерфейса, которому соответствует пустой словарь. Эта заготовка нужна для того,
чтобы на момент когда встретится IP-адрес или MTU можно было записать параметр
во вложенный словарь соответствующего интерфейса.
Результатом выполнения скрипта будет такой словарь (тут он разбит на пары ключ-
значение для удобства, в реальном выводе скрипта словарь будет отображаться в
одну строку):
232
Работа со словарями
233
Работа со словарями
result = {}
with open('sh_ip_interface2.txt') as f:
for line in f:
if 'line protocol' in line:
interface = line.split()[0]
elif 'Internet address' in line:
ip_address = line.split()[-1]
result[interface] = {}
result[interface]['ip'] = ip_address
elif 'MTU' in line:
mtu = line.split()[-2]
result[interface]['mtu'] = mtu
print(result)
234
Дополнительные материалы
Дополнительные материалы
Документация:
Статьи:
List, Dict And Set Comprehensions By Example - хорошая статья. И в конце статьи
есть несколько упражнений (с ответами)
Python List Comprehensions: Explained Visually - отличное объяснение list
comprehensions, плюс видео
Stackoverflow:
235
II. Повторное использование кода
Копировать код плохая затея. Так как, если потом понадобится обновить одну из
копий, надо будет обновлять и другие.
Вместо этого, надо создать специальный блок кода с именем - функцию. И каждый
раз, когда код надо повторить, достаточно вызвать функцию. Функция позволяет не
только назвать какой-то блок кода, но и сделать его более абстрактным за счет
параметров. Параметры дают возможность передавать разные исходные данные для
выполнения функции. И, соответственно, получать разный результат, в зависимости от
входящих параметров.
236
9. Функции
Функции
Функция - это блок кода, выполняющий определенные действия:
у функции есть имя, с помощью которого можно запускать этот блок кода сколько
угодно раз
запуск кода функции называется вызовом функции
при создании функции, как правило, определяются параметры функции.
параметры функции определяют, какие аргументы функция может принимать
функциям можно передавать аргументы
соответственно, код функции будет выполняться с учетом указанных
аргументов
открытие файла
удаление (или пропуск) строк, которые начинаются на знак восклицания (для
Cisco)
удаление (или пропуск) пустых строк
удаление символов перевода строки в конце строк
преобразование полученного результата в список
Часто получается, что есть кусок кода, который повторяется. Конечно, его можно
копировать из одного скрипта в другой. Но это очень неудобно, так как при внесении
изменений в код нужно будет обновить его во всех файлах, в которые он скопирован.
Гораздо проще и правильней вынести этот код в функцию (это может быть и несколько
функций).
И тогда будет просто производиться вызов этой функции - в этом файле или каком-то
другом.
237
9. Функции
238
Создание функций
Создание функций
Создание функции:
Пример функции:
Когда функция создана, она ещё ничего не выполняет. Только при вызыве функции
действия, которые в ней перечислены, будут выполняться. Это чем-то похоже на ACL в
сетевом оборудовании: при создании ACL в конфигурации, он ничего не делает до тех
пор, пока не будет куда-то применен.
Вызов функции
При вызове функции нужно указать её имя и передать аргументы, если нужно.
Эта функция в качестве аргумента ожидает имя файла и затем выводит содержимое
файла:
239
Создание функций
In [2]: open_file('r1.txt')
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
In [3]: open_file('ospf.txt')
router ospf 1
router-id 10.0.0.3
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
network 10.0.2.0 0.0.0.255 area 2
network 10.1.1.0 0.0.0.255 area 0
In [4]: open_file.__doc__
Out[4]: 'Documentation string'
Оператор return
Оператор return используется для прекращения работы функции, выхода из нее, и, как
правило, возврата какого-то значения. Функция может возвращать любой объект
Python.
В данном случае, если присвоить вывод функции переменной result, результат будет
таким:
240
Создание функций
In [6]: print(result)
None
Переменная result равна None . Так получилось из-за того, что функция ничего не
Для того, чтобы функция возвращала значение, которое потом можно, например,
присвоить переменной, используется оператор return :
In [9]: print(result)
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
Ещё один важный аспект работы оператора return: выражения, которые идут после
return, не выполняются.
То есть, в функции ниже, строка "Done" не будет выводиться, так как она стоит после
return:
241
Создание функций
242
Пространства имен. Области видимости
При использовании имен переменных в программе, Python каждый раз ищет, создает
или изменяет эти имена в соответствующем пространстве имен. Пространство имен,
которое доступно в каждый момент, зависит от области, в которой находится код.
локальные переменные:
переменные, которые определены внутри функции
эти переменные становятся недоступными после выхода из функции
глобальные переменные
переменные, которые определены вне функции
эти переменные 'глобальны' только в пределах модуля
например, чтобы они были доступны в другом модуле, их надо
импортировать
243
Пространства имен. Области видимости
In [3]: open_file('r1.txt')
Out[3]: '!\nservice timestamps debug datetime msec localtime show-timezone year\nservi
ce timestamps log datetime msec localtime show-timezone year\nservice password-encrypt
ion\nservice sequence-numbers\n!\nno ip domain lookup\n!\nip ssh version 2\n!\n'
In [4]: result
Out[4]: 'test string'
Обратите внимание, что переменная result по-прежнему осталась равной 'test string',
несмотря на то, что внутри функции ей присвоено содержимое файла.
244
Параметры и аргументы функций
Для того, чтобы функция могла принимать входящие значения, ее нужно создать с
параметрами (файл func_params_args.py):
Функция открывает файл in_cfg, читает содержимое в список; затем открывает файл
out_cfg и записывает в него только те строки, которые не начинаются на знак
восклицания.
245
Параметры и аргументы функций
In [5]: delete_exclamation_from_cfg('r1.txt')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-12-66ae381f1c4f> in <module>()
----> 1 delete_exclamation_from_cfg('r1.txt')
246
Параметры и аргументы функций
обязательными
необязательными (опциональными, параметрами со значением по умолчанию)
Обязательные параметры
Обязательные параметры - определяют, какие аргументы нужно передать функции
обязательно. При этом, их нужно передать ровно столько, сколько указано параметров
функции (нельзя указать большее или меньшее количество аргументов)
Внутри она открывает файл cfg_file для чтения, проходится по всем строкам и, если
аргумент delete_exclamation истина и строка начинается с восклицательного знака,
строка пропускается. Оператор pass означает, что ничего не выполняется.
247
Параметры и аргументы функций
Так как аргументу delete_exclamation передано значение True, в итоговом словаре нет
строк с восклицательными знаками.
248
Параметры и аргументы функций
In [5]: cfg_to_list('r1.txt')
Out[5]:
['service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'no ip domain lookup',
'ip ssh version 2']
249
Параметры и аргументы функций
Позиционные аргументы
Позиционные аргументы при вызове функции надо передать в правильном порядке
(поэтому они и называются позиционные)
250
Параметры и аргументы функций
Если при вызове функции поменять аргументы местами, скорее всего, возникнет
ошибка, в зависимости от конкретной функции.
Ключевые аргументы
Ключевые аргументы:
Если передать оба аргумента как ключевые, можно передавать их в любом порядке:
251
Параметры и аргументы функций
252
Параметры и аргументы функций
Даже если Вы не будете использовать этот прием в своих скриптах, есть большая
вероятность, что Вы встретите его в чужом коде.
Пример функции:
параметр a
если передается как позиционный аргумент, должен идти первым
если передается как ключевой аргумент, то порядок не важен
параметр *args - ожидает аргументы переменной длины
сюда попадут все остальные аргументы в виде кортежа
эти аргументы могут отсутствовать
253
Параметры и аргументы функций
In [2]: sum_arg(1,10,20,30)
1 (10, 20, 30)
Out[2]: 61
In [3]: sum_arg(1,10)
1 (10,)
Out[3]: 11
In [4]: sum_arg(1)
1 ()
Out[4]: 1
In [7]: sum_arg()
()
Out[7]: 0
Пример функции:
параметр a
если передается как позиционный аргумент, должен идти первым
если передается как ключевой аргумент, то порядок не важен
254
Параметры и аргументы функций
In [9]: sum_arg(a=10,b=10,c=20,d=30)
10 {'c': 20, 'b': 10, 'd': 30}
Out[9]: 70
In [10]: sum_arg(b=10,c=20,d=30,a=10)
10 {'c': 20, 'b': 10, 'd': 30}
Out[10]: 70
Обратите внимание, что, хотя a можно указывать как позиционный аргумент, нельзя
указывать позиционный аргумент после ключевого:
In [11]: sum_arg(10,b=10,c=20,d=30)
10 {'c': 20, 'b': 10, 'd': 30}
Out[11]: 70
In [12]: sum_arg(b=10,c=20,d=30,10)
File "<ipython-input-14-71c121dc2cf7>", line 1
sum_arg(b=10,c=20,d=30,10)
^
SyntaxError: positional argument follows keyword argument
255
Параметры и аргументы функций
Распаковка аргументов
В Python выражения *args и **kwargs позволяют выполнять ещё одну задачу -
распаковку аргументов.
256
Параметры и аргументы функций
mask_bits = int(cidr_mask.split('/')[-1])
bin_mask = '1'*mask_bits + '0'*(32-mask_bits)
dec_mask = [str(int(bin_mask[i:i+8], 2)) for i in range(0,25,8)]
dec_mask_str = '.'.join(dec_mask)
result.append(ip_addr.format(ip_address, dec_mask_str))
return result
Например:
257
Параметры и аргументы функций
Python сам 'распакует' список info и передаст в функцию элементы списка как
аргументы.
258
Параметры и аргументы функций
Пример использования:
In [9]: config_to_list('r1.txt')
Out[9]:
['service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'no ip domain lookup',
'ip ssh version 2']
259
Параметры и аргументы функций
Ошибка такая, так как все параметры, кроме имени файла, опциональны. И на стадии
открытия файла возникает ошибка, так как вместо файла передан словарь.
260
Параметры и аргументы функций
In [1]: config_to_list('r1.txt')
Out[1]:
['service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'no ip domain lookup',
'ip ssh version 2']
261
Параметры и аргументы функций
Но, при этом, мы не хотим терять возможность управлять тем, какие строки будут
отброшены. То есть, необходимо, чтобы функция clear_cfg_and_write_to_file
поддерживала те же параметры, что и функция config_to_list.
262
Параметры и аргументы функций
В этом примере **kwargs используется и для того, чтобы указать, что функция
clear_cfg_and_write_to_file может принимать аргументы переменной длины, и для
того, чтобы 'распаковать' словарь kwargs, когда мы передаем его в функцию
config_to_list.
263
Дополнительные материалы
Дополнительные материалы
Документация:
Defining Functions
Built-in Functions
Sorting HOW TO
Functional Programming HOWTO
Функция range
264
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 9.1
Создать функцию, которая генерирует конфигурацию для access-портов.
{'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17,
'FastEthernet0/17':150}
Пример итогового списка (перевод строки после каждого элемента сделан для
удобства чтения):
265
Задания
[
'interface FastEthernet0/12',
'switchport mode access',
'switchport access vlan 10',
'switchport nonegotiate',
'spanning-tree portfast',
'spanning-tree bpduguard enable',
'interface FastEthernet0/17',
'switchport mode access',
'switchport access vlan 150',
'switchport nonegotiate',
'spanning-tree portfast',
'spanning-tree bpduguard enable',
...]
def generate_access_config(access):
'''
access - словарь access-портов,
для которых необходимо сгенерировать конфигурацию, вида:
{ 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17}
access_dict = { 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17,
'FastEthernet0/17':150 }
Задание 9.1a
Сделать копию скрипта задания 9.1.
Дополнить скрипт:
266
Задания
security
имя параметра 'psecurity'
по умолчанию значение False
def generate_access_config(access):
'''
access - словарь access-портов,
для которых необходимо сгенерировать конфигурацию, вида:
{ 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17 }
access_dict = { 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17,
'FastEthernet0/17':150 }
Задание 9.1b
Сделать копию скрипта задания 9.1a.
267
Задания
def generate_access_config(access):
'''
access - словарь access-портов,
для которых необходимо сгенерировать конфигурацию, вида:
{ 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17 }
access_dict = { 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17,
'FastEthernet0/17':150 }
Задание 9.2
268
Задания
Словарь trunk имеет такой формат (тестовый словарь trunk_dict уже создан):
{ 'FastEthernet0/1':[10,20],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
def generate_trunk_config(trunk):
'''
trunk - словарь trunk-портов для которых необходимо сгенерировать конфигурацию.
trunk_dict = { 'FastEthernet0/1':[10,20,30],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
Задание 9.2a
Сделать копию скрипта задания 9.2
269
Задания
def generate_trunk_config(trunk):
'''
trunk - словарь trunk-портов,
для которых необходимо сгенерировать конфигурацию, вида:
{ 'FastEthernet0/1':[10,20],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
Возвращает словарь:
- ключи: имена интерфейсов, вида 'FastEthernet0/1'
- значения: список команд, который надо выполнить на этом интерфейсе
'''
trunk_template = ['switchport trunk encapsulation dot1q',
'switchport mode trunk',
'switchport trunk native vlan 999',
'switchport trunk allowed vlan']
trunk_dict = { 'FastEthernet0/1':[10,20,30],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
Задание 9.3
Создать функцию get_int_vlan_map, которая обрабатывает конфигурационный файл
коммутатора и возвращает два объекта:
словарь портов в режиме access, где ключи номера портов, а значения access
VLAN:
{'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17}
словарь портов в режиме trunk, где ключи номера портов, а значения список
разрешенных VLAN:
{'FastEthernet0/1':[10,20],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17]}
270
Задания
Задание 9.3a
Сделать копию скрипта задания 9.3.
Дополнить скрипт:
interface FastEthernet0/20
switchport mode access
duplex auto
В таком случае, в словарь портов должна добавляться информация, что порт в VLAN 1
Пример словаря:
{'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/20':1 }
Задание 9.4
Создать функцию, которая обрабатывает конфигурационный файл коммутатора и
возвращает словарь:
271
Задания
Возвращает True, если в команде содержится слово из списка ignore, False - если не
т
'''
return any(word in command for word in ignore)
Задание 9.4a
Задача такая же, как и задании 9.4. Проверить работу функции надо на примере
файла config_r1.txt
interface Ethernet0/3.100
router bgp 100
272
Задания
{'interface Ethernet0/3.100':{
'encapsulation dot1Q 100':[],
'xconnect 10.2.2.2 12100 encapsulation mpls':
['backup peer 10.4.4.4 14100',
'backup delay 1 1']}}
Возвращает True, если в команде содержится слово из списка ignore, False - если не
т
'''
return any(word in command for word in ignore)
273
10. Полезные функции
print
range
sorted
enumerate
zip
all, any
274
Функция print
Функция print
Функция print уже не раз использовалась в книге, но до сих пор не встречался ее
полный синтаксис:
Функция print выводит все элементы, разделяя их значением sep, и завершает вывод
значением end.
In [6]: str(f)
Out[6]: '<function f at 0xb4de926c>'
In [7]: str(range(10))
Out[7]: 'range(0, 10)'
sep
Параметр sep контролирует то, какой разделитель будет использоваться между
элементами.
In [8]: print(1, 2, 3)
1 2 3
275
Функция print
end
Параметр end контролирует то, какое значение выведется после вывода всех
элементов.
In [19]: print(1,2,3)
1 2 3
file
276
Функция print
Параметр file контролирует то, куда выводятся значения функции print. По умолчанию
все выводится на стандартный поток вывода - sys.stdout.
Но Python позволяет передавать file как аргумент любой объект с методом write(string).
За счет этого с помощью print можно записывать строки в файл:
In [3]: f.close()
flush
По умолчанию при записи в файл или выводе на стандартный поток вывода вывод
буферизируется. Функция print позволяет отключать буферизацию.
import time
277
Функция print
import time
Это связано с тем, что при выводе на стандартный поток вывода flush выполняется
после перевода строки.
Чтобы скрипт отрабатывал как нужно, необходимо установить flush равным True (файл
print_nums_oneline_fixed.py):
import time
278
Функция range
Функция range
Функция range возвращает неизменяемую последовательность чисел в виде объекта
range.
Синтаксис функции:
range(stop)
range(start, stop[, step])
Параметры функции:
Функция range хранит только информацию о значениях start, stop и step и вычисляет
значения по мере необходимости. Это значит, что, независимо от размера диапазона,
который описывает функция range, она всегда будет занимать фиксированный объем
памяти.
In [1]: range(5)
Out[1]: range(0, 5)
In [2]: list(range(5))
Out[2]: [0, 1, 2, 3, 4]
Если передаются два аргумента, то первый используется как start, а второй - как stop:
279
Функция range
In [12]: nums
Out[12]: range(0, 5)
In [13]: 3 in nums
Out[13]: True
In [14]: 7 in nums
Out[14]: False
280
Функция range
In [16]: nums[0]
Out[16]: 0
In [17]: nums[-1]
Out[17]: 4
In [19]: nums[1:]
Out[19]: range(1, 5)
In [20]: nums[:3]
Out[20]: range(0, 3)
In [22]: len(nums)
Out[22]: 5
In [24]: min(nums)
Out[24]: 0
In [25]: max(nums)
Out[25]: 4
In [27]: nums.index(3)
Out[27]: 2
281
Функция sorted
Функция sorted
Функция sorted() возвращает новый отсортированный список, который получен из
итерируемого объекта, который был передан как аргумент. Функция также
поддерживает дополнительные параметры, которые позволяют управлять
сортировкой.
In [2]: sorted(list_of_words)
Out[2]: ['', 'dict', 'list', 'one', 'two']
In [4]: sorted(tuple_of_words)
Out[4]: ['', 'dict', 'list', 'one', 'two']
Сортировка множества:
In [6]: sorted(set_of_words)
Out[6]: ['', 'dict', 'list', 'one', 'two']
Сортировка строки:
In [8]: sorted(string_to_sort)
Out[8]: [' ', 'g', 'g', 'i', 'l', 'n', 'n', 'o', 'r', 's', 't']
282
Функция sorted
In [9]: dict_for_sort = {
...: 'id': 1,
...: 'name':'London',
...: 'IT_VLAN':320,
...: 'User_VLAN':1010,
...: 'Mngmt_VLAN':99,
...: 'to_name': None,
...: 'to_id': None,
...: 'port':'G1/0/11'
...: }
In [10]: sorted(dict_for_sort)
Out[10]:
['IT_VLAN',
'Mngmt_VLAN',
'User_VLAN',
'id',
'name',
'port',
'to_id',
'to_name']
reverse
Флаг reverse позволяет управлять порядком сортировки. По умолчанию сортировка
будет по возрастанию элементов.
In [12]: sorted(list_of_words)
Out[12]: ['', 'dict', 'list', 'one', 'two']
key
С помощью параметра key можно указывать, как именно выполнять сортировку.
Параметр key ожидает функцию, с помощью которой должно быть выполнено
сравнение.
283
Функция sorted
Если нужно отсортировать ключи словаря, но при этом игнорировать регистр строк:
In [16]: dict_for_sort = {
...: 'id': 1,
...: 'name':'London',
...: 'IT_VLAN':320,
...: 'User_VLAN':1010,
...: 'Mngmt_VLAN':99,
...: 'to_name': None,
...: 'to_id': None,
...: 'port':'G1/0/11'
...: }
Параметру key можно передавать любые функции, не только встроенные. Также тут
удобно использовать анонимную функцию lambda.
284
Функция sorted
285
Функция enumerate
enumerate
Иногда, при переборе объектов в цикле for, нужно не только получить сам объект, но и
его порядковый номер. Это можно сделать, создав дополнительную переменную,
которая будет расти на единицу с каждым прохождением цикла. Однако, гораздо
удобнее это делать с помощью итератора enumerate() .
Базовый пример:
286
Функция enumerate
В этом примере используется Cisco EEM. Если в двух словах, то EEM позволяет
выполнять какие-то действия (action) в ответ на событие (event).
В EEM, в ситуации, когда действий выполнить нужно много, неудобно каждый раз
набирать action x cli command . Плюс, чаще всего, уже есть готовый кусок
конфигурации, который должен выполнить EEM.
import sys
config = sys.argv[1]
en
conf t
no int Gi0/0/0.300
no int Gi0/0/0.301
no int Gi0/0/0.302
int range gi0/0/0-2
channel-group 1 mode active
interface Port-channel1.300
encapsulation dot1Q 300
vrf forwarding Management
ip address 10.16.19.35 255.255.255.248
287
Функция enumerate
288
Функция zip
Функция zip
Функция zip():
Так как zip - это итератор, для отображение его содержимого используется list()
In [1]: a = [1,2,3]
In [2]: b = [100,200,300]
In [3]: list(zip(a,b))
Out[3]: [(1, 100), (2, 200), (3, 300)]
In [4]: a = [1,2,3,4,5]
In [5]: b = [10,20,30,40,50]
In [6]: c = [100,200,300]
In [7]: list(zip(a,b,c))
Out[7]: [(1, 10, 100), (2, 20, 200), (3, 30, 300)]
289
Функция zip
In [6]: list(zip(d_keys,d_values))
Out[6]:
[('hostname', 'london_r1'),
('location', '21 New Globe Walk'),
('vendor', 'Cisco'),
('model', '4451'),
('IOS', '15.4'),
('IP', '10.255.0.1')]
In [7]: dict(zip(d_keys,d_values))
Out[7]:
{'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'}
In [8]: r1 = dict(zip(d_keys,d_values))
In [9]: r1
Out[9]:
{'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'}
290
Функция zip
In [11]: data = {
....: 'r1': ['london_r1', '21 New Globe Walk', 'Cisco', '4451', '15.4', '10.255.0.1'
],
....: 'r2': ['london_r2', '21 New Globe Walk', 'Cisco', '4451', '15.4', '10.255.0.2'
],
....: 'sw1': ['london_sw1', '21 New Globe Walk', 'Cisco', '3850', '3.6.XE', '10.255
.0.101']
....: }
In [12]: london_co = {}
In [14]: london_co
Out[14]:
{'r1': {'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'r2': {'IOS': '15.4',
'IP': '10.255.0.2',
'hostname': 'london_r2',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'sw1': {'IOS': '3.6.XE',
'IP': '10.255.0.101',
'hostname': 'london_sw1',
'location': '21 New Globe Walk',
'model': '3850',
'vendor': 'Cisco'}}
291
Функции any и all
Функция all
Функция all() возвращает True, если все элементы истина (или объект пустой).
In [3]: all([])
Out[3]: True
In [4]: IP = '10.0.1.1'
Функция any
Функция any() возвращает True, если хотя бы один элемент истина.
In [9]: any([])
Out[9]: False
292
Функции any и all
def ignore_command(command):
'''
Функция проверяет содержится ли в команде слово из списка ignore.
* command - строка. Команда, которую надо проверить
* Возвращает True, если в команде содержится слово из списка ignore, False - если
нет
'''
ignore = ['duplex', 'alias', 'Current configuration']
ignore_command = False
На такой вариант:
def ignore_command(command):
'''
Функция проверяет содержится ли в команде слово из списка ignore.
command - строка. Команда, которую надо проверить
Возвращает True, если в команде содержится слово из списка ignore, False - если не
т
'''
ignore = ['duplex', 'alias', 'Current configuration']
293
11. Модули
Модули
Модуль в Python - это обычный текстовый файл с кодом Python и расширением .py. Он
позволяет логически упорядочить и сгруппировать код.
Модули хороши тем, что позволяют повторно использовать уже написанный код и не
копировать его (например, не копировать когда-то написанную функцию).
294
Импорт модуля
Импорт модуля
В Python есть несколько способов импорта модуля:
import module
import module as
import module
Вариант import module:
In [1]: dir()
Out[1]:
['In',
'Out',
...
'exit',
'get_ipython',
'quit']
In [2]: import os
In [3]: dir()
Out[3]:
['In',
'Out',
...
'exit',
'get_ipython',
'os',
'quit']
После импорта модуль os появился в выводе dir(). Это значит, что он теперь в текущем
именном пространстве.
Чтобы вызвать какую-то функцию или метод из модуля os, надо указать os. и затем
имя объекта:
In [4]: os.getlogin()
Out[4]: 'natasha'
295
Импорт модуля
Этот способ импорта хорош тем, что объекты модуля не попадают в именное
пространство текущей программы. То есть, если создать функцию с именем getlogin(),
она не будет конфликтовать с аналогичной функцией модуля os.
import module as
Конструкция import module as позволяет импортировать модуль под другим именем
(как правило, более коротким):
In [2]: dir()
Out[2]:
['In',
'Out',
...
'exit',
'get_ipython',
'getcwd',
'getlogin',
'quit']
296
Импорт модуля
In [3]: getlogin()
Out[3]: 'natasha'
In [4]: getcwd()
Out[4]: '/Users/natasha/Desktop/Py_net_eng/code_test'
In [2]: dir()
Out[2]:
['EX_CANTCREAT',
'EX_CONFIG',
...
'wait',
'wait3',
'wait4',
'waitpid',
'walk',
'write']
In [3]: len(dir())
Out[3]: 218
В модуле os очень много объектов, поэтому вывод сокращен. В конце указана длина
списка имен текущего именного пространства.
Такой вариант импорта лучше не использовать. При таком импорте по коду непонятно,
что какая-то функция взята, например, из модуля os. Это заметно усложняет
понимание кода.
297
Создание своих модулей
Файл sw_int_templates.py:
Файл sw_data.py:
sw1_fast_int = {
'access':{
'0/12':'10',
'0/14':'11',
'0/16':'17'}}
298
Создание своих модулей
def generate_access_cfg(sw_dict):
result = []
for intf, vlan in sw_dict['access'].items():
result.append('interface FastEthernet' + intf)
for command in sw_temp.access_template:
if command.endswith('access vlan'):
result.append(' {} {}'.format(command, vlan))
else:
result.append(' {}'.format(command))
return result
print('\n'.join(generate_access_cfg(sw1_fast_int)))
$ python generate_sw_int_cfg.py
interface FastEthernet0/12
switchport mode access
switchport access vlan 10
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/14
switchport mode access
switchport access vlan 11
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/16
switchport mode access
switchport access vlan 17
spanning-tree portfast
spanning-tree bpduguard enable
299
if name == "main"
if __name__ == "__main__"
$ python filter_functions.py
[' ip address 10.1.1.1 255.255.255.255\n',
' ip address 10.0.13.1 255.255.255.0\n',
' no ip address\n',
' ip address 10.0.19.1 255.255.255.0\n',
' no ip address\n',
' no ip address\n']
pprint(filter_file_lines('config_r1.txt', 'interface'))
300
if name == "main"
$ python get_data.py
[' ip address 10.1.1.1 255.255.255.255\n',
' ip address 10.0.13.1 255.255.255.0\n',
' no ip address\n',
' ip address 10.0.19.1 255.255.255.0\n',
' no ip address\n',
' no ip address\n']
['interface Loopback0\n',
'interface Tunnel0\n',
'interface Ethernet0/0\n',
'interface Ethernet0/1\n',
'interface Ethernet0/2\n',
'interface Ethernet0/3\n',
'interface Ethernet0/3.100\n',
'interface Ethernet1/0\n',
' event neighbor-discovery interface regexp .*Ethernet.* cdp add\n',
' action 3.0 cli command "interface $_nd_local_intf_name"\n']
Так происходит из-за того, что при импорте модуля, Python выполняет его.
В Python есть специальный прием, который позволяет указать, что какой-то код
должен выполняться, только когда файл запускается напрямую.
Файл filter_functions.py:
if __name__ == "__main__":
pprint(filter_file_lines('config_r1.txt', 'ip address'))
301
if name == "main"
if __name__ == '__main__':
pprint(filter_file_lines('config_r1.txt', 'ip address'))
$ python get_data.py
['interface Loopback0\n',
'interface Tunnel0\n',
'interface Ethernet0/0\n',
'interface Ethernet0/1\n',
'interface Ethernet0/2\n',
'interface Ethernet0/3\n',
'interface Ethernet0/3.100\n',
'interface Ethernet1/0\n',
' event neighbor-discovery interface regexp .*Ethernet.* cdp add\n',
' action 3.0 cli command "interface $_nd_local_intf_name"\n']
При выводе информации на стандартный поток вывода, проще всего заметить тот
факт, что модуль выполняется при импорте, но гораздо больше проблем возникает
когда, например, надо импортировать функцию из скрипта, который выполняет
подклюнение к сотням устройств. В таком случае, во время импорта будет
выполняться подключение, а только затем сможет выполниться скрипт, который
импортировал другой модуль.
302
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 11.1
Создать функцию parse_cdp_neighbors, которая обрабатывает вывод команды show
cdp neighbors.
Функция ожидает, как аргумент, вывод команды одной строкой (а не имя файла).
303
Задания
Задание 11.2
Для выполнения этого задания, должен быть установлен graphviz:
apt-get install graphviz
При этом:
Задание 11.2a
304
Задания
sh_cdp_n_sw1.txt
sh_cdp_n_r1.txt
sh_cdp_n_r2.txt
sh_cdp_n_r3.txt
При этом:
305
Задания
306
12. Полезные модули
Полезные модули
В этом разделе описаны такие модули:
subprocess
os
argparse
ipaddress
pprint
tabulate
307
Модуль subprocess
Модуль subprocess
Модуль subprocess позволяет создавать новые процессы.
При этом он может подключаться к стандартным потокам ввода/вывода/ошибок и
получать код возврата.
Функция subprocess.run()
Функция subprocess.run() - основной способ работы с модулем subprocess.
In [3]: result
Out[3]: CompletedProcess(args='ls', returncode=0)
In [4]: result.returncode
Out[4]: 0
308
Модуль subprocess
Ещё одна особенность функции run() - она ожидает завершения выполнения команды.
Если попробовать, например, запустить команду ping, то этот аспект будет заметен:
Если нужно получить результат выполнения команды, надо добавить аргумент stdout и
указать ему значение subprocess.PIPE:
309
Модуль subprocess
In [10]: print(result.stdout)
b'total 28\n4 -rw-r--r-- 1 vagrant vagrant 56 Jun 7 19:35 ipython_as_mngmt_console.
md\n4 -rw-r--r-- 1 vagrant vagrant 1638 Jun 7 19:35 module_search.md\n4 drwxr-xr-x 2
vagrant vagrant 4096 Jun 7 19:35 naming_conventions\n4 -rw-r--r-- 1 vagrant vagrant
277 Jun 7 19:35 README.md\n4 drwxr-xr-x 2 vagrant vagrant 4096 Jun 16 05:11 useful_fu
nctions\n4 drwxr-xr-x 2 vagrant vagrant 4096 Jun 17 16:30 useful_modules\n4 -rw-r--r--
1 vagrant vagrant 49 Jun 7 19:35 version_control.md\n'
Обратите внимание на букву b перед строкой. Она означает, что модуль вернул вывод
в виде байтовой строки.
Вариант с decode:
In [11]: print(result.stdout.decode('utf-8'))
total 28
4 -rw-r--r-- 1 vagrant vagrant 56 Jun 7 19:35 ipython_as_mngmt_console.md
4 -rw-r--r-- 1 vagrant vagrant 1638 Jun 7 19:35 module_search.md
4 drwxr-xr-x 2 vagrant vagrant 4096 Jun 7 19:35 naming_conventions
4 -rw-r--r-- 1 vagrant vagrant 277 Jun 7 19:35 README.md
4 drwxr-xr-x 2 vagrant vagrant 4096 Jun 16 05:11 useful_functions
4 drwxr-xr-x 2 vagrant vagrant 4096 Jun 17 16:30 useful_modules
4 -rw-r--r-- 1 vagrant vagrant 49 Jun 7 19:35 version_control.md
Вариант с encoding:
In [13]: print(result.stdout)
total 28
4 -rw-r--r-- 1 vagrant vagrant 56 Jun 7 19:35 ipython_as_mngmt_console.md
4 -rw-r--r-- 1 vagrant vagrant 1638 Jun 7 19:35 module_search.md
4 drwxr-xr-x 2 vagrant vagrant 4096 Jun 7 19:35 naming_conventions
4 -rw-r--r-- 1 vagrant vagrant 277 Jun 7 19:35 README.md
4 drwxr-xr-x 2 vagrant vagrant 4096 Jun 16 05:11 useful_functions
4 drwxr-xr-x 2 vagrant vagrant 4096 Jun 17 16:31 useful_modules
4 -rw-r--r-- 1 vagrant vagrant 49 Jun 7 19:35 version_control.md
310
Модуль subprocess
Отключение вывода
Иногда достаточно получения кода возврата и нужно отключить вывод результата
выполнения на стандартный поток вывода, и при этом сам результат не нужен.
In [15]: print(result.stdout)
None
In [16]: print(result.returncode)
0
Получить этот вывод можно так же, как и стандартный поток вывода:
In [18]: print(result.stdout)
None
In [19]: print(result.stderr)
ping: unknown host a
In [20]: print(result.returncode)
2
311
Модуль subprocess
import subprocess
if reply.returncode == 0:
print('Alive')
else:
print('Unreachable')
$ python subprocess_run_basic.py
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=43 time=54.0 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=43 time=54.4 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=43 time=53.9 ms
312
Модуль subprocess
import subprocess
def ping_ip(ip_address):
"""
Ping IP address and return tuple:
On success:
* True
* command output (stdout)
On failure:
* False
* error output (stderr)
"""
reply = subprocess.run(['ping', '-c', '3', '-n', ip_address],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding='utf-8')
if reply.returncode == 0:
return True, reply.stdout
else:
return False, reply.stderr
print(ping_ip('8.8.8.8'))
print(ping_ip('a'))
$ python subprocess_ping_function.py
(True, 'PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_seq=
1 ttl=43 time=63.8 ms\n64 bytes from 8.8.8.8: icmp_seq=2 ttl=43 time=55.6 ms\n64 bytes
from 8.8.8.8: icmp_seq=3 ttl=43 time=55.9 ms\n\n--- 8.8.8.8 ping statistics ---\n3 pa
ckets transmitted, 3 received, 0% packet loss, time 2003ms\nrtt min/avg/max/mdev = 55.
643/58.492/63.852/3.802 ms\n')
(False, 'ping: unknown host a\n')
На основе этой функции, можно сделать функцию, которая будет проверять список IP-
адресов и возвращать в результате выполнения два списка: доступные и недоступные
адреса.
313
Модуль os
Модуль os
Модуль os позволяет работать с файловой системой, с окружением, управлять
процессами.
In [1]: import os
In [2]: os.mkdir('test')
In [3]: ls -ls
total 0
0 drwxr-xr-x 2 nata nata 68 Jan 23 18:58 test/
In [4]: os.mkdir('test')
---------------------------------------------------------------------------
FileExistsError Traceback (most recent call last)
<ipython-input-4-cbf3b897c095> in <module>()
----> 1 os.mkdir('test')
In [5]: os.path.exists('test')
Out[5]: True
In [7]: os.listdir('.')
Out[7]: ['cover3.png', 'dir2', 'dir3', 'README.txt', 'test']
314
Модуль os
In [9]: dirs
Out[9]: ['dir2', 'dir3', 'test']
In [11]: files
Out[11]: ['cover3.png', 'README.txt']
In [12]: os.path.basename(file)
Out[12]: 'README.md'
In [13]: os.path.dirname(file)
Out[13]: 'Programming/PyNEng/book/25_additional_info'
In [14]: os.path.split(file)
Out[14]: ('Programming/PyNEng/book/25_additional_info', 'README.md')
315
Модуль ipaddress
Модуль ipaddress
Модуль ipaddress может пригодиться для работы с IP-адресами.
ipaddress.ip_address()
Функция ipaddress.ip_address() позволяет создавать объект IPv4Address или
IPv6Address соответственно.
IPv4 адрес:
In [3]: ipv4
Out[3]: IPv4Address('10.0.1.1')
In [4]: print(ipv4)
10.0.1.1
In [5]: ipv4.
ipv4.compressed ipv4.is_loopback ipv4.is_unspecified ipv4.version
ipv4.exploded ipv4.is_multicast ipv4.max_prefixlen
ipv4.is_global ipv4.is_private ipv4.packed
ipv4.is_link_local ipv4.is_reserved ipv4.reverse_pointer
In [6]: ipv4.is_loopback
Out[6]: False
In [7]: ipv4.is_multicast
Out[7]: False
In [8]: ipv4.is_reserved
Out[8]: False
In [9]: ipv4.is_private
Out[9]: True
316
Модуль ipaddress
In [16]: str(ip1)
Out[16]: '10.0.1.1'
In [17]: int(ip1)
Out[17]: 167772417
In [18]: ip1 + 5
Out[18]: IPv4Address('10.0.1.6')
In [19]: ip1 - 5
Out[19]: IPv4Address('10.0.0.252')
ipaddress.ip_network()
Функция ipaddress.ip_network() позволяет создать объект, который описывает сеть
(IPv4 или IPv6).
Сеть IPv4:
317
Модуль ipaddress
In [21]: subnet1.broadcast_address
Out[21]: IPv4Address('80.0.1.15')
In [22]: subnet1.with_netmask
Out[22]: '80.0.1.0/255.255.255.240'
In [23]: subnet1.with_hostmask
Out[23]: '80.0.1.0/0.0.0.15'
In [24]: subnet1.prefixlen
Out[24]: 28
In [25]: subnet1.num_addresses
Out[25]: 16
Метод hosts() возвращает генератор, поэтому, чтобы посмотреть все хосты, надо
применить функцию list:
In [26]: list(subnet1.hosts())
Out[26]:
[IPv4Address('80.0.1.1'),
IPv4Address('80.0.1.2'),
IPv4Address('80.0.1.3'),
IPv4Address('80.0.1.4'),
IPv4Address('80.0.1.5'),
IPv4Address('80.0.1.6'),
IPv4Address('80.0.1.7'),
IPv4Address('80.0.1.8'),
IPv4Address('80.0.1.9'),
IPv4Address('80.0.1.10'),
IPv4Address('80.0.1.11'),
IPv4Address('80.0.1.12'),
IPv4Address('80.0.1.13'),
IPv4Address('80.0.1.14')]
In [27]: list(subnet1.subnets())
Out[27]: [IPv4Network('80.0.1.0/29'), IPv4Network(u'80.0.1.8/29')]
318
Модуль ipaddress
In [28]: list(subnet1.subnets(prefixlen_diff=2))
Out[28]:
[IPv4Network('80.0.1.0/30'),
IPv4Network('80.0.1.4/30'),
IPv4Network('80.0.1.8/30'),
IPv4Network('80.0.1.12/30')]
Или с помощью параметра new_prefix просто указать, какая маска должна быть у
подсетей:
In [29]: list(subnet1.subnets(new_prefix=30))
Out[29]:
[IPv4Network('80.0.1.0/30'),
IPv4Network('80.0.1.4/30'),
IPv4Network('80.0.1.8/30'),
IPv4Network('80.0.1.12/30')]
In [30]: list(subnet1.subnets(new_prefix=29))
Out[30]: [IPv4Network('80.0.1.0/29'), IPv4Network(u'80.0.1.8/29')]
319
Модуль ipaddress
In [32]: subnet1[0]
Out[32]: IPv4Address('80.0.1.0')
In [33]: subnet1[5]
Out[33]: IPv4Address('80.0.1.5')
ipaddress.ip_interface()
Функция ipaddress.ip_interface() позволяет создавать объект IPv4Interface или
IPv6Interface соответственно.
Используя методы объекта IPv4Interface, можно получать адрес, маску или сеть
интерфейса:
In [37]: int1.ip
Out[37]: IPv4Address('10.0.1.1')
In [38]: int1.network
Out[38]: IPv4Network('10.0.1.0/24')
In [39]: int1.netmask
Out[39]: IPv4Address('255.255.255.0')
320
Модуль ipaddress
In [43]: check_if_ip_is_network(IP1)
Out[43]: False
In [44]: check_if_ip_is_network(IP2)
Out[44]: True
321
Модуль argparse
Модуль argparse
argparse - это модуль для обработки аргументов командной строки.
322
Модуль argparse
import subprocess
import argparse
args = parser.parse_args()
print(args)
Создание парсера:
Добавление аргументов:
323
Модуль argparse
324
Модуль argparse
$ python ping_function.py
Namespace(count=2, ip=None)
Traceback (most recent call last):
File "ping_function.py", line 31, in <module>
rc, message = ping_ip( args.ip, args.count )
File "ping_function.py", line 16, in ping_ip
stderr=temp)
File "/usr/local/lib/python3.6/subprocess.py", line 336, in check_output
**kwargs).stdout
File "/usr/local/lib/python3.6/subprocess.py", line 403, in run
with Popen(*popenargs, **kwargs) as process:
File "/usr/local/lib/python3.6/subprocess.py", line 707, in __init__
restore_signals, start_new_session)
File "/usr/local/lib/python3.6/subprocess.py", line 1260, in _execute_child
restore_signals, start_new_session, preexec_fn)
TypeError: expected str, bytes or os.PathLike object, not NoneType
$ python ping_function.py
usage: ping_function.py [-h] -a IP [-c COUNT]
ping_function.py: error: the following arguments are required: -a
325
Модуль argparse
$ python ping_function.py -h
usage: ping_function.py [-h] -a IP [-c COUNT]
Ping script
optional arguments:
-h, --help show this help message and exit
-a IP
-c COUNT
arguments .
argparse сам определяет, что указаны опции, так как они начинаются с - и в имени
только одна буква.
Файл ping_function_ver2.py:
326
Модуль argparse
import subprocess
from tempfile import TemporaryFile
import argparse
args = parser.parse_args()
print(args)
Кроме того, в скрипте указаны сообщения, которые будут выводиться при вызове help.
327
Модуль argparse
$ python ping_function_ver2.py -h
usage: ping_function_ver2.py [-h] [-c COUNT] host
Ping script
positional arguments:
host IP or name to ping
optional arguments:
-h, --help show this help message and exit
-c COUNT Number of packets
Вложенные парсеры
Рассмотрим один из способов организации более сложной иерархии аргументов.
Файл parse_dhcp_snooping.py:
# Default values:
DFLT_DB_NAME = 'dhcp_snooping.db'
DFLT_DB_SCHEMA = 'dhcp_snooping_schema.sql'
def create(args):
print("Creating DB {} with DB schema {}".format((args.name, args.schema)))
def add(args):
if args.sw_true:
328
Модуль argparse
def get(args):
if args.key and args.value:
print("Geting data from DB: {}".format(args.db_file))
print("Request data for host(s) with {} {}".format((args.key, args.value)))
elif args.key or args.value:
print("Please give two or zero args\n")
print(show_subparser_help('get'))
else:
print("Showing {} content...".format(args.db_file))
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands',
description='valid subcommands',
help='description')
if __name__ == '__main__':
args = parser.parse_args()
if not vars(args):
329
Модуль argparse
parser.print_usage()
else:
args.func(args)
Функция create получает как аргумент все аргументы, которые были переданы.
И внутри функции можно обращаться к нужным:
def create(args):
print("Creating DB {} with DB schema {}".format((args.name, args.schema)))
330
Модуль argparse
$ python parse_dhcp_snooping.py -h
usage: parse_dhcp_snooping.py [-h] {create_db,add,get} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
valid subcommands
{create_db,add,get} description
create_db create new db
add add data to db
get get data from db
optional arguments:
-h, --help show this help message and exit
-n db-filename db filename
-s SCHEMA db schema filename
metavar
Аргумент metavar позволяет указывать имя аргумента для вывода в сообщении usage
и help:
331
Модуль argparse
optional arguments:
-h, --help show this help message and exit
-n db-filename db filename
-s SCHEMA db schema filename
nargs
positional arguments:
filename file(s) to add to db
optional arguments:
-h, --help show this help message and exit
--db DB_FILE db name
-s add switch data if set, else add normal data
332
Модуль argparse
аргумент
choices
get_parser.add_argument('-k', dest="key",
choices=['mac', 'ip', 'vlan', 'interface', 'switch'],
help='host key (parameter) to search')
optional arguments:
-h, --help show this help message and exit
--db DB_FILE db name
-k {mac,ip,vlan,interface,switch}
host key (parameter) to search
-v VALUE value of key
-a show db content
333
Модуль argparse
В данном примере важно указать варианты на выбор, так как затем на основании
выбранного варианта генерируется SQL-запрос. И, благодаря choices , нет
возможности указать какой-то параметр, кроме разрешенных.
Импорт парсера
В файле parse_dhcp_snooping.py последние две строки будут выполняться только в
том случае, если скрипт был вызван как основной.
if __name__ == '__main__':
args = parser.parse_args()
args.func(args)
args = parser.parse_args()
args.func(args)
$ python call_pds.py -h
usage: call_pds.py [-h] {create_db,add,get} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
valid subcommands
{create_db,add,get} description
create_db create new db
add add data to db
get get data from db
334
Модуль argparse
Вызов аргумента:
Аргументы можно передать как список при вызове метода parse_args() (файл
call_pds2.py):
Необходимо использовать метод split(), так как метод parse_args ожидает список
аргументов.
$ python call_pds2.py
Reading info from file(s)
test.txt, test2.txt
335
Модуль tabulate
Модуль tabulate
tabulate - это библиотека, которая позволяет красиво отображать табличные данные.
In [4]: print(tabulate(sh_ip_int_br))
--------------- --------- -- --
FastEthernet0/0 15.0.15.1 up up
FastEthernet0/1 10.0.12.1 up up
FastEthernet0/2 10.0.13.1 up up
Loopback0 10.1.1.1 up up
Loopback100 100.0.0.1 up up
--------------- --------- -- --
headers
Параметр headers позволяет передавать дополнительный аргумент, в котором указаны
имена столбцов:
336
Модуль tabulate
Достаточно часто первый набор данных - это заголовки. Тогда достаточно указать
headers равным "firstrow":
In [18]: data
Out[18]:
[('Interface', 'IP', 'Status', 'Protocol'),
('FastEthernet0/0', '15.0.15.1', 'up', 'up'),
('FastEthernet0/1', '10.0.12.1', 'up', 'up'),
('FastEthernet0/2', '10.0.13.1', 'up', 'up'),
('Loopback0', '10.1.1.1', 'up', 'up'),
('Loopback100', '100.0.0.1', 'up', 'up')]
Если данные в виде списка словарей, надо указать headers равным "keys":
337
Модуль tabulate
In [22]: list_of_dict
Out[22]:
[{'IP': '15.0.15.1',
'Interface': 'FastEthernet0/0',
'Protocol': 'up',
'Status': 'up'},
{'IP': '10.0.12.1',
'Interface': 'FastEthernet0/1',
'Protocol': 'up',
'Status': 'up'},
{'IP': '10.0.13.1',
'Interface': 'FastEthernet0/2',
'Protocol': 'up',
'Status': 'up'},
{'IP': '10.1.1.1',
'Interface': 'Loopback0',
'Protocol': 'up',
'Status': 'up'},
{'IP': '100.0.0.1',
'Interface': 'Loopback100',
'Protocol': 'up',
'Status': 'up'}]
Стиль таблицы
tabulate поддерживает разные стили отображения таблицы.
Формат grid:
338
Модуль tabulate
339
Модуль tabulate
Выравнивание столбцов
Можно указывать выравнивание для столбцов:
Дополнительные материалы
Документация tabulate
Stackoverflow:
Printing Lists as Tabular Data. Обратите внимание на ответ - в нём указаны другие
аналоги tabulate.
340
Модуль pprint
Модуль pprint
Модуль pprint позволяет красиво отображать объекты Python. При этом сохраняется
структура объекта и отображение, которое выводит pprint, можно использовать для
создания объекта.
In [6]: london_co = {'r1': {'hostname': 'london_r1', 'location': '21 New Globe Wal
...: k', 'vendor': 'Cisco', 'model': '4451', 'IOS': '15.4', 'IP': '10.255.0.1'}
...: , 'r2': {'hostname': 'london_r2', 'location': '21 New Globe Walk', 'vendor
...: ': 'Cisco', 'model': '4451', 'IOS': '15.4', 'IP': '10.255.0.2'}, 'sw1': {'
...: hostname': 'london_sw1', 'location': '21 New Globe Walk', 'vendor': 'Cisco
...: ', 'model': '3850', 'IOS': '3.6.XE', 'IP': '10.255.0.101'}}
...:
In [8]: pprint(london_co)
{'r1': {'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'r2': {'IOS': '15.4',
'IP': '10.255.0.2',
'hostname': 'london_r2',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'sw1': {'IOS': '3.6.XE',
'IP': '10.255.0.101',
'hostname': 'london_sw1',
'location': '21 New Globe Walk',
'model': '3850',
'vendor': 'Cisco'}}
Список списков:
341
Модуль pprint
In [14]: pprint(interfaces)
[['FastEthernet0/0', '15.0.15.1', 'YES', 'manual', 'up', 'up'],
['FastEthernet0/1', '10.0.1.1', 'YES', 'manual', 'up', 'up'],
['FastEthernet0/2', '10.0.2.1', 'YES', 'manual', 'up', 'down']]
Строка:
In [18]: tunnel
Out[18]: '\ninterface Tunnel0\n ip address 10.10.10.1 255.255.255.0\n ip mtu 1416\n ip
ospf hello-interval 5\n tunnel source FastEthernet1/0\n tunnel protection ipsec profi
le DMVPN\n'
In [19]: pprint(tunnel)
('\n'
'interface Tunnel0\n'
' ip address 10.10.10.1 255.255.255.0\n'
' ip mtu 1416\n'
' ip ospf hello-interval 5\n'
' tunnel source FastEthernet1/0\n'
' tunnel protection ipsec profile DMVPN\n')
Ограничение вложенности
У функции pprint есть дополнительный параметр depth, который позволяет
ограничивать глубину отображения структуры данных.
342
Модуль pprint
In [3]: result = {
...: 'interface Tunnel0': [' ip unnumbered Loopback0',
...: ' tunnel mode mpls traffic-eng',
...: ' tunnel destination 10.2.2.2',
...: ' tunnel mpls traffic-eng priority 7 7',
...: ' tunnel mpls traffic-eng bandwidth 5000',
...: ' tunnel mpls traffic-eng path-option 10 dynamic',
...: ' no routing dynamic'],
...: 'ip access-list standard LDP': [' deny 10.0.0.0 0.0.255.255',
...: ' permit 10.0.0.0 0.255.255.255'],
...: 'router bgp 100': {' address-family vpnv4': [' neighbor 10.2.2.2 activat
...: e',
...: ' neighbor 10.2.2.2 send-community both',
...: ' exit-address-family'],
...: ' bgp bestpath igp-metric ignore': [],
...: ' bgp log-neighbor-changes': [],
...: ' neighbor 10.2.2.2 next-hop-self': [],
...: ' neighbor 10.2.2.2 remote-as 100': [],
...: ' neighbor 10.2.2.2 update-source Loopback0': [],
...: ' neighbor 10.4.4.4 remote-as 40': []},
...: 'router ospf 1': [' mpls ldp autoconfig area 0',
...: ' mpls traffic-eng router-id Loopback0',
...: ' mpls traffic-eng area 0',
...: ' network 10.0.0.0 0.255.255.255 area 0']}
...:
343
Модуль pprint
pformat
pformat - это функция, которая отображает результат в виде строки. Ее удобно
использовать, если необходимо записать структуру данных в какой-то файл,
например, для логирования.
344
Модуль pprint
In [17]: print(formatted_result)
{'interface Tunnel0': [' ip unnumbered Loopback0',
' tunnel mode mpls traffic-eng',
' tunnel destination 10.2.2.2',
' tunnel mpls traffic-eng priority 7 7',
' tunnel mpls traffic-eng bandwidth 5000',
' tunnel mpls traffic-eng path-option 10 dynamic',
' no routing dynamic'],
'ip access-list standard LDP': [' deny 10.0.0.0 0.0.255.255',
' permit 10.0.0.0 0.255.255.255'],
'router bgp 100': {' address-family vpnv4': [' neighbor 10.2.2.2 activate',
' neighbor 10.2.2.2 '
'send-community both',
' exit-address-family'],
' bgp bestpath igp-metric ignore': [],
' bgp log-neighbor-changes': [],
' neighbor 10.2.2.2 next-hop-self': [],
' neighbor 10.2.2.2 remote-as 100': [],
' neighbor 10.2.2.2 update-source Loopback0': [],
' neighbor 10.4.4.4 remote-as 40': []},
'router ospf 1': [' mpls ldp autoconfig area 0',
' mpls traffic-eng router-id Loopback0',
' mpls traffic-eng area 0',
' network 10.0.0.0 0.255.255.255 area 0']}
Дополнительные материалы
Документация:
345
Дополнительные материалы
Дополнительные материалы
Стандартная библиотека модулей Python:
Индекс модулей
Python 3 Module of the Week
Документация:
Видео:
David Beazley - Modules and Packages: Live and Let Die! - PyCon 2015
argparse
Документация модуля
Статья на PyMOTW
tabulate
Документация tabulate
Stackoverflow:
Printing Lists as Tabular Data. Обратите внимание на ответ - в нем указаны другие
аналоги tabulate.
pprint
pprint — Data pretty printer
PyMOTW. pprint — Pretty-Print Data Structures
346
Дополнительные материалы
347
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 12.1
Создать функцию check_ip_addresses, которая проверяет доступность IP-адресов.
Задание 12.2
Функция check_ip_addresses из задания 12.1 принимает только список адресов, но
было бы удобно иметь возможность указывать адреса с помощью диапазона,
например, 192.168.100.1-10.
10.1.1.1
10.1.1.1-10.1.1.10
10.1.1.1-10
Если адрес указан в виде диапазона, надо проверить доступность всех адресов
диапазона включая последний.
348
Задания
Для упрощения задачи, можно считать, что в диапазоне всегда меняется только
последний октет адреса.
Задание 12.3
Создать функцию ip_table, которая отображает таблицу доступных и недоступных IP-
адресов.
Reachable Unreachable
----------- -------------
10.1.1.1 10.1.1.7
10.1.1.2 10.1.1.8
10.1.1.9
349
13. Итераторы, итерируемые объекты и генераторы
350
Итерируемый объект
Итерируемый объект
Итерация - это общий термин, который описывает процедуру взятия элементов чего-то
по очереди.
In [2]: iter(lista)
Out[2]: <list_iterator at 0xb4ede28c>
Функция iter() отработает на любом объекте, у которого есть метод __iter__ или
метод __getitem__ .
Метод __iter__ возвращает итератор. Но если этого метода нет, функция iter()
проверяет, нет ли метода __getitem__ - метода, который позволяет получать элементы
по индексу.
351
Итератор
Итераторы
Итератор (iterator) - это объект, который возвращает свои элементы по одному за раз.
С точки зрения Python - это любой объект, у которого есть метод __next__ . Этот метод
возвращает следующий элемент, если он есть, или возвращает исключение
StopIteration, когда элементы закончились.
In [4]: i = iter(lista)
Теперь можно использовать функцию next(), которая вызывает метод __next__ , чтобы
взять следующий элемент:
In [5]: next(i)
Out[5]: 1
In [6]: next(i)
Out[6]: 2
In [7]: next(i)
Out[7]: 3
In [8]: next(i)
------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-8-bed2471d02c1> in <module>()
----> 1 next(i)
StopIteration:
Для того, чтобы итератор снова начал возвращать элементы, его надо заново
создать.
352
Итератор
Итераторы полезны тем, что они отдают элементы по одному. Например, при работе с
файлом это полезно тем, что в памяти будет находиться не весь файл, а только одна
строка файла.
Файл r1.txt:
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
In [10]: f = open('r1.txt')
Этот объект является итератором, что можно проверить, вызвав метод __next__ :
353
Итератор
In [11]: f.__next__()
Out[11]: '!\n'
In [12]: f.__next__()
Out[12]: 'service timestamps debug datetime msec localtime show-timezone year\n'
Поэтому при работе с файлами в Python чаще всего используется конструкция вида:
354
Generator
generator (генератор)
Генераторы - это специальный класс функций, который позволяет легко создавать
свои итераторы. В отличии от обычных функций, генератор не просто возвращает
значение и завершает работу, а возвращает итератор, который отдает элементы по
одному.
генераторное выражение
функция-генератор
355
Generator
In [2]: genexpr
Out[2]: <generator object <genexpr> at 0xb571ec8c>
In [3]: next(genexpr)
Out[3]: 0
In [4]: next(genexpr)
Out[4]: 1
In [5]: next(genexpr)
Out[5]: 4
Оно полезно в том случае, когда надо работать с большим итерируемым объектом или
бесконечным итератором.
356
Дополнительные материалы
Дополнительные материалы
Документация Python:
Sequence types
Iterator types
Functional Programming HOWTO
Статьи:
357
III. Регулярные выражения
Регулярные выражения
Регулярное выражение - это последовательность из обычных и специальных
символов. Эта последовательность задает шаблон, который позже используется для
поиска подстрок.
Несколько примеров:
обработав вывод команды show version, можно собрать информацию про версию
ОС и uptime оборудования.
получить из log-файла те строки, которые соответствуют шаблону.
получить из конфигурации те интерфейсы, на которых нет описания (description)
Но методы строк могут справиться не со всеми задачами или могут сильно усложнить
решение задачи. В этом случае могут помочь регулярные выражения.
358
14. Синтаксис регулярных выражений
В первой половине этого раздела для всех примеров будет использоваться функция
search. А в следующих подразделах будут рассматриваться остальные функции
модуля re.
Если совпадение было найдено, функция вернет специальный объект Match. Если же
совпадения не было, функция вернет None.
При этом особенность функции search в том, что она ищет только первое совпадение.
То есть, если в строке есть несколько подстрок, которые соответствуют регулярному
выражению, search вернет только первое найденное совпадение.
In [1]: import re
In [2]: int_line = ' MTU 1500 bytes, BW 10000 Kbit, DLY 1000 usec,'
В этом примере:
359
14. Синтаксис регулярных выражений
Если она есть, в переменной match будет находиться специальный объект Match:
In [4]: print(match)
<_sre.SRE_Match object; span=(2, 5), match='MTU'>
In [5]: match.group()
Out[5]: 'MTU'
In [6]: int_line = ' MTU 1500 bytes, BW 10000 Kbit, DLY 1000 usec,'
In [8]: print(match)
None
In [9]: int_line = ' MTU 1500 bytes, BW 10000 Kbit, DLY 1000 usec,'
In [11]: match.group()
Out[11]: 'BW 10000'
360
14. Синтаксис регулярных выражений
In [13]: re.search('Host (\S+) in vlan (\d+) is flapping between port (\S+) and port (
\S+)', log2).groups()
Out[13]: ('f03a.b216.7ad7', '10', 'Gi0/5', 'Gi0/15')
Метод groups возвращает только те части исходной строки, которые попали в круглые
скобки. Таким образом, заключив часть выражения в скобки, можно указать, какие
части строки надо запомнить.
Выражение \d+ уже использовалось ранее - оно описывает одну или более цифр. А
выражение \S+ описывает все символы, кроме whitespace (пробел, таб и другие).
361
Наборы символов
Наборы символов
В Python есть специальные обозначения для наборов символов:
\d - любая цифра
\s - whitespace (\t\n\r\f\v)
Группы символов очень удобны, но, пока что, приходится вручную указывать
повторение символа. В следующем подразделе рассматриваются символы
повторения, которые упростят описание выражений.
362
Наборы символов
363
Символы повторения
Символы повторения
regex+ - одно или более повторений предшествующего элемента
+
Плюс указывает, что предыдущее выражение может повторяться сколько угодно раз,
но, как минимум, один раз.
В выражении (a1)+ скобки используются для того, чтобы указать, что повторение
относится к последовательности символов 'a1'.
Оно необходимо из-за того, что точка является специальным символом (она
обозначает любой символ). И чтобы указать, что нас интересует именно точка, надо ее
экранировать - поместить перед точкой обратный слеш.
364
Символы повторения
Еще один пример выражения: \d+\s+\S+ - оно описывает строку, в которой идут
цифры, пробел (whitespace), не whitespace символы, то есть, все, кроме пробела, таба
и других whitespace символов. С его помощью можно получить VLAN и MAC-адрес из
строки:
*
Звездочка указывает, что предыдущее выражение может повторяться 0 или более раз.
Например, если звездочка стоит после символа, она означает повторение этого
символа.
365
Символы повторения
Чтобы описать оба варианта адресов, надо указать, что точка в адресе опциональна:
'\w+\.*\w+@\w+\.\w+'
?
В последнем примере регулярное выражение указывает, что точка опциональна. Но, в
то же время, указывает и то, что точка может появиться много раз.
В этой ситуации логичней использовать знак вопроса. Он обозначает ноль или одно
повторение предыдущего выражения или символа. Теперь регулярное выражение
выглядит так \w+\.?\w+@\w+\.\w+ :
366
Символы повторения
{n}
С помощью фигурных скобок можно указать, сколько раз должно повторяться
предшествующее выражение.
367
Символы повторения
Так так search ищет только первое совпадение, в выражение \d{1,4} попадет номер
VLAN:
Обратите внимание, что в выводе команды нет первого VLAN. Такой результат
получился из-за того, что в имени коммутатора есть цифра и она совпала с
выражением.
Чтобы исправить это, достаточно дополнить выражение и указать, что после цифр
должен идти хотя бы один пробел:
368
Специальные символы
Специальные символы
. - любой символ, кроме символа новой строки
^ - начало строки
$ - конец строки
.
Точка обозначает любой символ.
В результат попала только одна строка, так как точка обозначает любой символ, кроме
символа перевода строки. Кроме того, символы повторения + и * по умолчанию
захватывают максимально длинную строку. Этот аспект рассматривается в подразделе
"Жадность символов повторения".
369
Специальные символы
$
Символ $ обозначает конец строки.
[]
Символы, которые перечислены в квадратных скобках, означают, что любой из этих
символов будет совпадением. Таким образом можно описывать разные регистры:
370
Специальные символы
371
Специальные символы
|
Вертикальная черта работает как 'или':
Обратите внимание на то, как срабатывает | - Fast и 0/1 воспринимаются как целое
выражение. То есть, в итоге выражение означает, что мы ищем Fast или 0/1, а не то,
что мы ищем Fas, затем t или 0 и 0/1.
()
372
Специальные символы
Скобки позволяют указывать, какое выражение является одним целым. Это особенно
полезно при использовании символов повторения:
373
Жадность символов повторения
In [1]: import re
In [2]: line = '<text line> some text>'
In [3]: match = re.search('<.*>', line)
In [4]: match.group()
Out[4]: '<text line> some text>'
Если нужно отключить жадность, достаточно добавить знак вопроса после символов
повторения:
In [7]: match.group()
Out[7]: '<text line>'
374
Жадность символов повторения
375
Группировка выражений
Группировка выражений
Группировка выражений указывает, что последовательность символов надо
рассматривать как одно целое. Но это не единственное преимущество группировки.
Кроме этого, с помощью групп можно получать только определенную часть строки,
которая была описана выражением. Это очень полезно в ситуациях, когда надо
описать строку достаточно подробно, чтобы отобрать нужные строки, но, в то же
время, из самой строки надо получить только определенное значение.
Нумерованные группы
Именованные группы
Нумерованные группы
Группа определяется помещением выражения в круглые скобки () .
Вторую группу можно было описать так же, как и первую. Другой вариант сделан
просто для примера
376
Группировка выражений
In [10]: match.group(0)
Out[10]: 'FastEthernet0/1 10.0.12.1 YES manual up
up'
In [11]: match.group(1)
Out[11]: 'FastEthernet0/1'
In [12]: match.group(2)
Out[12]: '10.0.12.1'
In [13]: match.group(1, 2)
Out[13]: ('FastEthernet0/1', '10.0.12.1')
In [14]: match.group(2, 1, 2)
Out[14]: ('10.0.12.1', 'FastEthernet0/1', '10.0.12.1')
In [15]: match[0]
Out[15]: 'FastEthernet0/1 10.0.12.1 YES manual up
up'
In [16]: match[1]
Out[16]: 'FastEthernet0/1'
In [17]: match[2]
Out[17]: '10.0.12.1'
In [18]: match.groups()
Out[18]: ('FastEthernet0/1', '10.0.12.1')
Именованные группы
Когда выражение сложное, не очень удобно определять номер группы.
Плюс, при дополнении выражения, может получиться так, что порядок групп
изменился, и придется изменить и код, который ссылается на группы.
377
Группировка выражений
In [21]: match.group('intf')
Out[21]: 'FastEthernet0/1'
In [22]: match.group('address')
Out[22]: '10.0.12.1'
Также очень полезно то, что с помощью метода groupdict(), можно получить словарь,
где ключи - имена групп, а значения - подстроки, которые им соответствуют:
In [23]: match.groupdict()
Out[23]: {'address': '10.0.12.1', 'intf': 'FastEthernet0/1'}
In [25]: match.groupdict()
Out[25]:
{'address': '10.0.12.1',
'intf': 'FastEthernet0/1',
'protocol': 'up',
'status': 'up'}
378
Группировка выражений
более пробелов
сюда попадет значение Lease
\S+ + - последовательность любых символов, кроме whitespace
379
Группировка выражений
whitespace
In [3]: match.groupdict()
Out[3]:
{'int': 'FastEthernet0/1',
'ip': '10.1.10.2',
'mac': '00:09:BB:3D:D6:58',
'vlan': '10'}
Так как регулярное выражение отработало как нужно, можно создавать скрипт.
В скрипте перебираются все строки файла dhcp_snooping.txt, и на стандартный поток
вывода выводится информация об устройствах.
Файл parse_dhcp_snooping.py:
Результат выполнения:
380
Группировка выражений
$ python parse_dhcp_snooping.py
К коммутатору подключено 4 устройства
Параметры устройства 1:
int: FastEthernet0/1
ip: 10.1.10.2
mac: 00:09:BB:3D:D6:58
vlan: 10
Параметры устройства 2:
int: FastEthernet0/10
ip: 10.1.5.2
mac: 00:04:A3:3E:5B:69
vlan: 5
Параметры устройства 3:
int: FastEthernet0/9
ip: 10.1.5.4
mac: 00:05:B3:7E:9B:60
vlan: 5
Параметры устройства 4:
int: FastEthernet0/3
ip: 10.1.10.6
mac: 00:09:BC:3F:A6:50
vlan: 10
381
Группа без захвата
Но иногда скобки нужны для указания части выражения, которое повторяется. И, при
этом, не нужно запоминать выражение.
In [3]: match.groups()
Out[3]: ('f03a.b216.7ad7', 'b216.', '10', 'Gi0/5', 'Gi0/15')
Второй элемент, по сути, лишний. Он попал в вывод из-за скобок в выражении ([0-9a-
fA-F]{4}\.){2} .
В этом случае нужно отключить захват в группе. Это делается добавлением ?: после
открывающейся скобки группы.
382
Группа без захвата
И, соответственно, группы:
In [5]: match.groups()
Out[5]: ('f03a.b216.7ad7', '10', 'Gi0/5', 'Gi0/15')
383
Повторение захваченного результата
Это можно сделать с помощью ссылки на результат, который был захвачен группой.
Например, такое выражение отображает все строки, в которых один и тот же номер
повторяется хотя бы два раза:
Кроме того, в этом выражении перед строкой регулярного выражения стоит буква r.
Это так называемая raw строка.
384
Повторение захваченного результата
Тут удобней использовать ее, так как иначе надо будет экранировать обратный слеш,
чтобы ссылка на группу сработала корректно:
385
15. Модуль re
Модуль re
В Python для работы с регулярными выражениями используется модуль re.
виде списка
finditer() - ищет все совпадения с шаблоном. Выдает итератор
386
Объект Match
Объект Match
В модуле re несколько функций возвращают объект Match, если было найдено
совпадение:
search
match
finditer возвращает итератор с объектами Match
In [2]: match = re.search('Host (\S+) in vlan (\d+) .* port (\S+) and port (\S+)', log
)
In [3]: match
Out[3]: <_sre.SRE_Match object; span=(47, 124), match='Host f03a.b216.7ad7 in vlan 10
is flapping betwee>
group()
Метод group возвращает подстроку, которая совпала с выражением или с выражением
в группе.
In [4]: match.group()
Out[4]: 'Host f03a.b216.7ad7 in vlan 10 is flapping between port Gi0/5 and port Gi0/15'
In [13]: match.group(0)
Out[13]: 'Host f03a.b216.7ad7 in vlan 10 is flapping between port Gi0/5 and port Gi0/1
5'
387
Объект Match
In [14]: match.group(1)
Out[14]: 'f03a.b216.7ad7'
In [15]: match.group(2)
Out[15]: '10'
In [16]: match.group(3)
Out[16]: 'Gi0/5'
In [17]: match.group(4)
Out[17]: 'Gi0/15'
Если вызвать метод group с номером группы, который больше, чем количество
существующих групп, возникнет ошибка:
In [18]: match.group(5)
-----------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-18-9df93fa7b44b> in <module>()
----> 1 match.group(5)
In [19]: match.group(1, 2, 3)
Out[19]: ('f03a.b216.7ad7', '10', 'Gi0/5')
In [36]: match.group(2)
Out[36]: ''
Если группа описывает часть шаблона и совпадений было несколько, метод отобразит
последнее совпадение:
388
Объект Match
In [45]: match.group(1)
Out[46]: 'b216.'
Такой вывод получился из-за того, что выражение в скобках описывает 4 буквы или
цифры, и после этого стоит плюс. Соответственно, сначала с выражением в скобках
совпала первая часть MAC-адреса, потом вторая. Но запоминается и возвращается
только последнее выражение.
In [53]: match.group('mac')
Out[53]: 'f03a.b216.7ad7'
In [54]: match.group('int2')
Out[54]: 'Gi0/15'
In [58]: match.group(3)
Out[58]: 'Gi0/5'
In [59]: match.group(4)
Out[59]: 'Gi0/15'
groups()
Метод groups() возвращает кортеж со строками, в котором элементы - это те
подстроки, которые попали в соответствующие группы:
389
Объект Match
In [65]: match.groups()
Out[65]: ('f03a.b216.7ad7', '10', 'Gi0/5', 'Gi0/15')
In [78]: match.groups()
Out[78]: ('100', 'aab1')
Если же в строке нет ничего после пробела, в группу ничего не попадет. Но совпадение
будет, так как в регулярном выражении описано, что группа опциональна:
In [82]: match.groups()
Out[82]: ('100', None)
390
Объект Match
In [85]: match.groups(0)
Out[85]: ('100', 0)
groupdict()
Метод groupdict возвращает словарь, в котором ключи - имена групп, а значения -
соответствующие строки:
In [89]: match.groupdict()
Out[89]: {'int1': 'Gi0/5', 'int2': 'Gi0/15', 'mac': 'f03a.b216.7ad7', 'vlan': '10'}
start(), end()
Методы start и end возвращают индексы начала и конца совпадения с регулярным
выражением.
Если методы вызываются без аргументов, они возвращают индексы для всего
совпадения:
391
Объект Match
In [103]: match.start()
Out[103]: 2
In [104]: match.end()
Out[104]: 42
In [105]: line[match.start():match.end()]
Out[105]: '10 aab1.a1a1.a5d3 FastEthernet0/1'
Методам можно передавать номер или имя группы. Тогда они возвращают индексы
для этой группы:
In [108]: match.start(2)
Out[108]: 9
In [109]: match.end(2)
Out[109]: 23
In [110]: line[match.start(2):match.end(2)]
Out[110]: 'aab1.a1a1.a5d3'
In [9]: match.start('mac')
Out[9]: 52
In [10]: match.end('mac')
Out[10]: 66
span()
Метод span возвращает кортеж с индексом начала и конца подстроки. Он работает
аналогично методам start, end, но возвращает пару чисел.
392
Объект Match
In [114]: match.span()
Out[114]: (2, 42)
In [117]: match.span(2)
Out[117]: (9, 23)
In [14]: match.span('mac')
Out[14]: (52, 66)
In [15]: match.span('vlan')
Out[15]: (75, 77)
393
re.search
re.search()
Функция search() :
Функция search подходит в том случае, когда надо найти только одно совпадение в
строке, например, когда регулярное выражение описывает всю строку или часть
строки.
В файле log.txt находятся лог-сообщения с информацией о том, что один и тот же MAC
слишком быстро переучивается то на одном, то на другом интерфейсе. Одна из
причин таких сообщений - петля в сети.
При этом, MAC-адрес может прыгать между несколькими портами. В таком случае
очень важно знать, с каких портов прилетает MAC. И, если это вызвано петлей,
выключить все порты, кроме одного.
394
re.search
In [1]: import re
Регулярное выражение для удобства чтения разбито на части. В нём есть три группы:
In [4]: match.groups()
Out[4]: ('10', 'Gi0/16', 'Gi0/24')
import re
ports = set()
with open('log.txt') as f:
for line in f:
match = re.search(regex, line)
if match:
vlan = match.group(1)
ports.add(match.group(2))
ports.add(match.group(3))
395
re.search
$ python parse_log_search.py
Петля между портами Gi0/19, Gi0/24, Gi0/16 в VLAN 10
Version :
Cisco IOS Software, C2960 Software (C2960-LANBASEK9-M), Version 12.2(55)SE9, RELEASE S
OFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2014 by Cisco Systems, Inc.
Compiled Mon 03-Mar-14 22:53 by prod_rel_team
advertisement version: 2
VTP Management Domain: ''
Native VLAN: 1
Duplex: full
Management address(es):
IP address: 10.1.1.2
И, для удобства, надо получить данные в виде словаря. Пример итогового словаря для
коммутатора SW2:
396
re.search
import re
from pprint import pprint
def parse_cdp(filename):
result = {}
with open(filename) as f:
for line in f:
if line.startswith('Device ID'):
neighbor = re.search('Device ID: (\S+)', line).group(1)
result[neighbor] = {}
elif line.startswith(' IP address'):
ip = re.search('IP address: (\S+)', line).group(1)
result[neighbor]['ip'] = ip
elif line.startswith('Platform'):
platform = re.search('Platform: (\S+ \S+),', line).group(1)
result[neighbor]['platform'] = platform
elif line.startswith('Cisco IOS Software'):
ios = re.search('Cisco IOS Software, (.+), RELEASE', line).group(1)
result[neighbor]['ios'] = ios
return result
pprint(parse_cdp('sh_cdp_neighbors_sw1.txt'))
$ python parse_sh_cdp_neighbors_detail_ver1.py
{'R1': {'ios': '3800 Software (C3825-ADVENTERPRISEK9-M), Version 12.4(24)T1',
'ip': '10.1.1.1',
'platform': 'Cisco 3825'},
'R2': {'ios': '2900 Software (C3825-ADVENTERPRISEK9-M), Version 15.2(2)T1',
'ip': '10.2.2.2',
'platform': 'Cisco 2911'},
'SW2': {'ios': 'C2960 Software (C2960-LANBASEK9-M), Version 12.2(55)SE9',
'ip': '10.1.1.2',
'platform': 'cisco WS-C2960-8TC-L'}}
397
re.search
Все получилось как нужно. Но, с помощью регулярных выражений эту задачу можно
решить более компактно.
import re
from pprint import pprint
def parse_cdp(filename):
regex = ('Device ID: (?P<device>\S+)'
'|IP address: (?P<ip>\S+)'
'|Platform: (?P<platform>\S+ \S+),'
'|Cisco IOS Software, (?P<ios>.+), RELEASE')
result = {}
with open('sh_cdp_neighbors_sw1.txt') as f:
for line in f:
match = re.search(regex, line)
if match:
if match.lastgroup == 'device':
device = match.group(match.lastgroup)
result[device] = {}
elif device:
result[device][match.lastgroup] = match.group(match.lastgroup)
return result
pprint(parse_cdp('sh_cdp_neighbors_sw1.txt'))
398
re.search
$ python parse_sh_cdp_neighbors_detail_ver2.py
{'R1': {'ios': '3800 Software (C3825-ADVENTERPRISEK9-M), Version 12.4(24)T1',
'ip': '10.1.1.1',
'platform': 'Cisco 3825'},
'R2': {'ios': '2900 Software (C3825-ADVENTERPRISEK9-M), Version 15.2(2)T1',
'ip': '10.2.2.2',
'platform': 'Cisco 2911'},
'SW2': {'ios': 'C2960 Software (C2960-LANBASEK9-M), Version 12.2(55)SE9',
'ip': '10.1.1.2',
'platform': 'cisco WS-C2960-8TC-L'}}
399
re.match
re.match()
Функция match() :
Функция match отличается от search тем, что match всегда ищет совпадение в начале
строки. Например, если повторить пример, который использовался для функции
search, но уже с match:
In [2]: import re
In [6]: print(match)
None
Так получилось из-за того, что match ищет слово Host в начале строки. Но это
сообщение находится в середине.
В данном случае можно легко исправить выражение, чтобы функция match находила
совпадение:
Перед словом Host добавлено выражение \S+: . Теперь совпадение будет найдено:
400
re.match
In [11]: print(match)
<_sre.SRE_Match object; span=(0, 104), match='%SW_MATM-4-MACFLAP_NOTIF: Host 01e2.4c18
.0156 in >
In [12]: match.groups()
Out[12]: ('10', 'Gi0/16', 'Gi0/24')
import re
ports = set()
with open('log.txt') as f:
for line in f:
match = re.match(regex, line)
if match:
vlan = match.group(1)
ports.add(match.group(2))
ports.add(match.group(3))
Результат:
$ python parse_log_match.py
Петля между портами Gi0/19, Gi0/24, Gi0/16 в VLAN 10
401
re.finditer
re.finditer()
Функция finditer() :
Функция finditer отлично подходит для обработки тех команд, вывод которых
отображается столбцами. Например, sh ip int br, sh mac address-table и др. В этом
случае его можно применять ко всему выводу команды.
In [12]: result
Out[12]: <callable_iterator at 0xb583f46c>
402
re.finditer
In [16]: groups = []
In [19]: groups
Out[19]:
[('FastEthernet0/0', '15.0.15.1', 'up', 'up'),
('FastEthernet0/1', '10.0.12.1', 'up', 'up'),
('FastEthernet0/2', '10.0.13.1', 'up', 'up'),
('Loopback0', '10.1.1.1', 'up', 'up'),
('Loopback100', '100.0.0.1', 'up', 'up')]
In [22]: result
Out[22]:
[('FastEthernet0/0', '15.0.15.1', 'up', 'up'),
('FastEthernet0/1', '10.0.12.1', 'up', 'up'),
('FastEthernet0/2', '10.0.13.1', 'up', 'up'),
('Loopback0', '10.1.1.1', 'up', 'up'),
('Loopback100', '100.0.0.1', 'up', 'up')]
403
re.finditer
import re
ports = set()
with open('log.txt') as f:
for m in re.finditer(regex, f.read()):
vlan = m.group(1)
ports.add(m.group(2))
ports.add(m.group(3))
В реальной жизни log-файл может быть очень большим. В таком случае, его
лучше обрабатывать построчно.
$ python parse_log_finditer.py
Петля между портами Gi0/19, Gi0/24, Gi0/16 в VLAN 10
404
re.finditer
import re
from pprint import pprint
def parse_cdp(filename):
regex = ('Device ID: (?P<device>\S+)'
'|IP address: (?P<ip>\S+)'
'|Platform: (?P<platform>\S+ \S+),'
'|Cisco IOS Software, (?P<ios>.+), RELEASE')
result = {}
with open('sh_cdp_neighbors_sw1.txt') as f:
match_iter = re.finditer(regex, f.read())
for match in match_iter:
if match.lastgroup == 'device':
device = match.group(match.lastgroup)
result[device] = {}
elif device:
result[device][match.lastgroup] = match.group(match.lastgroup)
return result
pprint(parse_cdp('sh_cdp_neighbors_sw1.txt'))
with open('sh_cdp_neighbors_sw1.txt') as f:
match_iter = re.finditer(regex, f.read())
with open('sh_cdp_neighbors_sw1.txt') as f:
match_iter = re.finditer(regex, f.read())
for match in match_iter:
Остальное аналогично.
405
re.finditer
$ python parse_sh_cdp_neighbors_detail_finditer.py
{'R1': {'ios': '3800 Software (C3825-ADVENTERPRISEK9-M), Version 12.4(24)T1',
'ip': '10.1.1.1',
'platform': 'Cisco 3825'},
'R2': {'ios': '2900 Software (C3825-ADVENTERPRISEK9-M), Version 15.2(2)T1',
'ip': '10.2.2.2',
'platform': 'Cisco 2911'},
'SW2': {'ios': 'C2960 Software (C2960-LANBASEK9-M), Version 12.2(55)SE9',
'ip': '10.1.1.2',
'platform': 'cisco WS-C2960-8TC-L'}}
Хотя результат аналогичный, с finditer больше возможностей, так как можно указывать
не только то, что должно находиться в нужной строке, но и в строках вокруг.
...
Native VLAN: 1
Duplex: full
Management address(es):
IP address: 10.1.1.2
Например, если нужно взять первый IP-адрес, можно так дополнить регулярное
выражение:
406
re.findall
re.findall()
Функция findall() :
In [3]: print(mac_address_table)
sw1#sh mac address-table
Mac Address Table
-------------------------------------------
Первый пример - регулярное выражение без групп. В этом случае findall возвращает
список строк, которые совпали с регулярным выражением.
Например, с помощью findall можно получить список строк с соответствиями vlan - mac
- interface и избавиться от заголовка в выводе команды:
407
re.findall
Но как только в регулярном выражении появляется группа, findall ведет себя по-
другому.
Если в выражении используется одна группа, findall возвращает список строк, которые
совпали с выражением в группе:
При этом findall ищет совпадение всей строки, но возвращает результат, похожий на
метод groups() в объекте Match.
408
re.findall
import re
ports = set()
with open('log.txt') as f:
result = re.findall(regex, f.read())
for vlan, port1, port2 in result:
ports.add(port1)
ports.add(port2)
Результат:
$ python parse_log_findall.py
Петля между портами Gi0/19, Gi0/16, Gi0/24 в VLAN 10
409
re.compile
re.compile()
В Python есть возможность заранее скомпилировать регулярное выражение, а затем
использовать его. Это особенно полезно в тех случаях, когда регулярное выражение
много используется в скрипте.
In [53]: regex
Out[53]: re.compile(r'\d+ +\S+ +\w+ +\S+', re.UNICODE)
Обратите внимание, что у объекта Regex доступны методы search, match, finditer,
findall. Это те же функции, которые доступны в модуле глобально, но теперь их надо
применять к объекту.
410
re.compile
Теперь search надо вызывать как метод объекта regex. И передать как аргумент строку.
In [69]: match
Out[69]: <_sre.SRE_Match object; span=(1, 43), match='100 a1b2.ac10.7000 DYNAMIC
Gi0/1'>
In [70]: match.group()
Out[70]: '100 a1b2.ac10.7000 DYNAMIC Gi0/1'
import re
ports = set()
with open('log.txt') as f:
for m in regex.finditer(f.read()):
vlan = m.group(1)
ports.add(m.group(2))
ports.add(m.group(3))
411
re.compile
for m in regex.finditer(f.read()):
In [78]: match.group()
Out[78]: '100 a1b2.ac10.7000 DYNAMIC Gi0/1'
In [80]: match.group()
Out[80]: '00 a1b2.ac10.7000 DYNAMIC Gi0/1'
In [82]: match.group()
Out[82]: '00 a1b2.ac10.7000 DYNAMIC Gi0/1'
412
re.compile
In [93]: match.group()
Out[93]: '00 a1b2.ac10.7000 DYNAMIC Gi'
In [95]: match.group()
Out[95]: '00 a1b2.ac10.7000 DYNAMIC Gi'
413
Флаги
Флаги
При использовании функций или создании скомпилированного регулярного выражения
можно указывать дополнительные флаги, которые влияют на поведение регулярного
выражения.
re.ASCII (re.A)
re.IGNORECASE (re.I)
re.MULTILINE (re.M)
re.DOTALL (re.S)
re.VERBOSE (re.X)
re.LOCALE (re.L)
re.DEBUG
re.DOTALL
С помощью регулярных выражений можно работать и с многострочной строкой.
414
Флаги
Конечно, в этом случае можно разделить строку на части и работать с каждой строкой
отдельно.
Но можно получить часть с MAC-адресами и без разделения.
In [13]: m.group()
Out[13]: ' 100 aabb.cc80.7000 DYNAMIC Gi0/1'
Учитывая то, что по умолчанию регулярные выражения жадные, можно получить все
соответствия таким образом:
In [15]: print(m.group())
100 aabb.cc10.7000 DYNAMIC Gi0/1
200 aabb.cc20.7000 DYNAMIC Gi0/2
300 aabb.cc30.7000 DYNAMIC Gi0/3
100 aabb.cc40.7000 DYNAMIC Gi0/4
500 aabb.cc50.7000 DYNAMIC Gi0/5
200 aabb.cc60.7000 DYNAMIC Gi0/6
300 aabb.cc70.7000 DYNAMIC Gi0/7
Тут описана строка с MAC-адресом, перевод строки, и указано, что это выражение
должно повторяться, как минимум, один раз.
Получается, что в данном случае надо получить все строки, начиная с первого
соответствия VLAN-MAC-интерфейс.
In [17]: print(m.group())
100 aabb.cc10.7000 DYNAMIC Gi0/1
Пока что в результате только одна строка, так как по умолчанию точка не включает в
себя перевод строки.
Но, если добавить специальный флаг, re.DOTALL, точка будет включать и перевод
415
Флаги
In [19]: print(m.group())
100 aabb.cc10.7000 DYNAMIC Gi0/1
200 aabb.cc20.7000 DYNAMIC Gi0/2
300 aabb.cc30.7000 DYNAMIC Gi0/3
100 aabb.cc40.7000 DYNAMIC Gi0/4
500 aabb.cc50.7000 DYNAMIC Gi0/5
200 aabb.cc60.7000 DYNAMIC Gi0/6
300 aabb.cc70.7000 DYNAMIC Gi0/7
416
re.split
re.split
Функция split работает аналогично методу split в строках.
Но в функции re.split можно использовать регулярные выражения, а значит, разделять
строку на части по более сложным условиям.
417
re.split
418
re.sub
re.sub
Функция re.sub работает аналогично методу replace в строках.
Но в функции re.sub можно использовать регулярные выражения, а значит, делать
замены по более сложным условиям.
419
re.sub
MAC-адрес
(\S+) - пятая группа. Описывает интерфейс.
420
Дополнительные материалы
Дополнительные материалы
Регулярные выражения в Python:
для Python - тут можно указывать и методы search, match, findall, и флаги. Пример
регулярного выражения. К сожалению, иногда не все выражения воспринимает.
Еще один сайт для Python - не поддерживает методы, но хорошо работает и
отработал те выражения, на которые ругнулся предыдущий сайт. Подходит для
однострочного текста отлично. С многострочным надо учитывать, что в питоне
будет другая ситуация. Пример регулярного выражения
regex101
421
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 15.1
Создать скрипт, который будет ожидать два аргумента:
422
Задания
В данном случае, скрипт будет работать как фильтр include в CLI Cisco. Вы можете
попробовать использовать регулярные выражения для фильтрации вывода
команд show.
Задание 15.1a
Напишите регулярное выражение, которое отобразит строки с интерфейсами 0/1 и 0/3
из вывода sh ip int br.
Задание 15.1b
Переделайте регулярное выражение из задания 15.1a таким образом, чтобы оно, по-
прежнему, отображало строки с интерфейсами 0/1 и 0/3, но, при этом, в регулярном
выражении было не более 7 символов (не считая кавычки вокруг регулярного
выражения).
423
Задания
Задание 15.1c
Проверить работу скрипта из задания 15.1 и регулярного выражения из задания 15.1a
или 15.1b на выводе sh ip int br из файла sh_ip_int_br_switch.txt.
Задание 15.2
Создать функцию return_match, которая ожидает два аргумента:
Задание 15.3
Создать функцию parse_cfg, которая ожидает как аргумент имя файла, в котором
находится конфигурация устройства.
424
Задания
Задание 15.3a
Переделать функцию parse_cfg из задания 15.3 таким образом, чтобы она возвращала
словарь:
Задание 15.3b
Проверить работу функции parse_cfg из задания 15.3a на конфигурации config_r2.txt.
interface Ethernet0/1
ip address 10.255.2.2 255.255.255.0
ip address 10.254.2.2 255.255.255.0 secondary
425
Задания
Задание 15.4
Создать функцию parse_sh_ip_int_br, которая ожидает как аргумент имя файла, в
котором находится вывод команды show
Функция должна обрабатывать вывод команды show ip int br и возвращать такие поля:
Interface
IP-Address
Status
Protocol
Задание 15.4a
Создать функцию convert_to_dict, которая ожидает два аргумента:
Функция возвращает результат в виде списка словарей (порядок полей может быть
другой):
426
Задания
427
IV. Запись и передача данных
вывод команд
обработанный вывод команд в виде словаря, списка и подобного
информация полученная из системы мониторинга
В этой части рассматривается чтение и запись данных в форматах CSV, JSON и YAML:
Также в этой части рассматриваются базы данных. Хотя данные можно записать с
соблюдением структуры и в CSV или JSON, запрашивать нужную информацию из
файлов в этом формате не всегда удобно. Особенно, когда речь идет о более сложных
запросах, в которых указаны несколько критериев.
Для задач такого рода отлично подходят базы данных. В разделе 18 рассматривается
СУБД SQLite, а также основы языка SQL.
428
16. Unicode
Unicode
Программы, которые мы пишем, не изолированы в себе. Они скачивают данные из
Интернета, читают и записывают данные на диск, передают данные через сеть.
Поэтому очень важно понимать разницу между тем, как компьютер хранит и передает
данные, и как эти данные воспринимает человек. Мы воспринимаем текст, а
компьютер - байты.
429
Стандарт Unicode
Стандарт Юникод
Юникод - это стандарт, который описывает представление и кодировку почти всех
языков и других символов.
Кроме кода, у каждого символа есть свое уникальное имя. Например, букве "s"
соответствует код U+0073 и имя "LATIN SMALL LETTER S".
U+1F383 , "JACK-O-LANTERN" -
Кодировки
Кодировки позволяют записывать код символа в байтах.
UTF-8
UTF-16
UTF-32
H - 48
i - 69
430
Стандарт Unicode
- 01 f6 c0
- 01 f6 80
☃ - 26 03
431
Unicode в Python 3
Юникод в Python 3
В Python 3 есть:
Строки
Примеры строк:
In [11]: hi = 'привет'
In [12]: hi
Out[12]: 'привет'
In [15]: type(hi)
Out[15]: str
In [14]: beautiful
Out[14]: 'schön'
Так как строки - это последовательность кодов Юникод, можно записать строку
разными способами.
In [4]: "\u00F6"
Out[4]: 'ö'
432
Unicode в Python 3
In [21]: hi2
Out[21]: 'привет'
In [23]: len(hi2)
Out[23]: 6
In [6]: ord('ö')
Out[6]: 246
In [7]: chr(246)
Out[7]: 'ö'
Байты
Тип bytes - это неизменяемая последовательность байтов.
Байты обозначаются так же, как строки, но с добавлением буквы "b" перед строкой:
In [30]: b1 = b'\xd0\xb4\xd0\xb0'
In [31]: b2 = b"\xd0\xb4\xd0\xb0"
In [32]: b3 = b'''\xd0\xb4\xd0\xb0'''
In [36]: type(b1)
Out[36]: bytes
In [37]: len(b1)
Out[37]: 4
433
Unicode в Python 3
In [39]: bytes1
Out[39]: b'hello'
In [40]: len(bytes1)
Out[40]: 5
In [41]: bytes1.hex()
Out[41]: '68656c6c6f'
In [43]: bytes2
Out[43]: b'hello'
434
Конвертация между байтами и строками
как "зашифровать" строку в байты (str -> bytes). Используется метод encode (похож
на encrypt)
как "расшифровать" байты в строку (bytes -> str). Используется метод decode
(похож на decrypt)
encode, decode
Для преобразования строки в байты используется метод encode:
In [1]: hi = 'привет'
In [2]: hi.encode('utf-8')
Out[2]: b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
In [4]: hi_bytes
Out[4]: b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
In [5]: hi_bytes.decode('utf-8')
Out[5]: 'привет'
str.encode, bytes.decode
Метод encode есть также в классе str (как и другие методы работы со строками):
435
Конвертация между байтами и строками
In [6]: hi
Out[6]: 'привет'
In [8]: hi_bytes
Out[8]: b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
В этих методах кодировка может указываться как ключевой аргумент (примеры выше)
или как позиционный:
In [10]: hi_bytes
Out[10]: b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
436
Конвертация между байтами и строками
437
Примеры конвертации
subprocess
Модуль subprocess возвращает результат команды в виде байт:
In [3]: result.stdout
Out[3]: b'PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_se
q=1 ttl=43 time=59.4 ms\n64 bytes from 8.8.8.8: icmp_seq=2 ttl=43 time=54.4 ms\n64 byt
es from 8.8.8.8: icmp_seq=3 ttl=43 time=55.1 ms\n\n--- 8.8.8.8 ping statistics ---\n3
packets transmitted, 3 received, 0% packet loss, time 2002ms\nrtt min/avg/max/mdev = 5
4.470/56.346/59.440/2.220 ms\n'
Если дальше необходимо работать с этим выводом, надо сразу конвертировать его в
строку:
In [5]: print(output)
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=43 time=59.4 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=43 time=54.4 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=43 time=55.1 ms
438
Примеры конвертации
In [7]: result.stdout
Out[7]: 'PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_seq
=1 ttl=43 time=55.5 ms\n64 bytes from 8.8.8.8: icmp_seq=2 ttl=43 time=54.6 ms\n64 byte
s from 8.8.8.8: icmp_seq=3 ttl=43 time=53.3 ms\n\n--- 8.8.8.8 ping statistics ---\n3 p
ackets transmitted, 3 received, 0% packet loss, time 2003ms\nrtt min/avg/max/mdev = 53
.368/54.534/55.564/0.941 ms\n'
In [8]: print(result.stdout)
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=43 time=55.5 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=43 time=54.6 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=43 time=53.3 ms
telnetlib
В зависимости от модуля, преобразование между строками и байтами может
выполняться автоматически, а может требоваться явно.
import telnetlib
import time
t = telnetlib.Telnet('192.168.100.1')
t.read_until(b'Username:')
t.write(b'cisco\n')
t.read_until(b'Password:')
t.write(b'cisco\n')
t.write(b'sh ip int br\n')
time.sleep(5)
output = t.read_very_eager().decode('utf-8')
print(output)
439
Примеры конвертации
pexpect
Модуль pexpect как аргумент ожидает строку, а возвращает байты:
In [11]: output
Out[11]: b'total 8\r\n4 drwxr-xr-x 2 vagrant vagrant 4096 Aug 28 12:16 concurrent_futu
res\r\n4 drwxr-xr-x 2 vagrant vagrant 4096 Aug 3 07:59 iterator_generator\r\n'
In [12]: output.decode('utf-8')
Out[12]: 'total 8\r\n4 drwxr-xr-x 2 vagrant vagrant 4096 Aug 28 12:16 concurrent_futur
es\r\n4 drwxr-xr-x 2 vagrant vagrant 4096 Aug 3 07:59 iterator_generator\r\n'
In [14]: output
Out[14]: 'total 8\r\n4 drwxr-xr-x 2 vagrant vagrant 4096 Aug 28 12:16 concurrent_futur
es\r\n4 drwxr-xr-x 2 vagrant vagrant 4096 Aug 3 07:59 iterator_generator\r\n'
Работа с файлами
До сих пор при работе с файлами использовалась такая конструкция:
with open(filename) as f:
for line in f:
print(line)
Но на самом деле, при чтении файла происходит конвертация байт в строки. И при
этом использовалась кодировка по умолчанию:
In [2]: locale.getpreferredencoding()
Out[2]: 'UTF-8'
440
Примеры конвертации
In [2]: f = open('r1.txt')
In [3]: f
Out[3]: <_io.TextIOWrapper name='r1.txt' mode='r' encoding='UTF-8'>
При работе с файлами лучше явно указывать кодировку, так как в разных ОС она
может отличаться:
Выводы
Эти примеры показаны тут для того, чтобы показать, что разные модули могут по-
разному подходить к вопросу конвертации между строками и байтами. И разные
функции и методы этих модулей могут ожидать аргументы и возвращать значения
разных типов. Однако все эти вещи написаны в документации.
441
Ошибки при конвертации
In [33]: hi_unicode.encode('ascii')
---------------------------------------------------------------------------
UnicodeEncodeError Traceback (most recent call last)
<ipython-input-33-ec69c9fd2dae> in <module>()
----> 1 hi_unicode.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not
in range(128)
In [36]: hi_bytes.decode('ascii')
---------------------------------------------------------------------------
UnicodeDecodeError Traceback (most recent call last)
<ipython-input-36-aa0ada5e44e9> in <module>()
----> 1 hi_bytes.decode('ascii')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 0: ordinal not in
range(128)
Еще один вариант ошибки, когда используются разные кодировки для преобразований:
442
Ошибки при конвертации
In [39]: utf_16.decode('utf-8')
---------------------------------------------------------------------------
UnicodeDecodeError Traceback (most recent call last)
<ipython-input-39-4b4c731e69e4> in <module>()
----> 1 utf_16.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start
byte
Но на самом деле, предыдущие ошибки - это хорошо. Они явно говорят, в чем
проблема.
In [42]: hi_bytes
Out[42]: b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
In [43]: hi_bytes.decode('utf-16')
Out[43]: '뿐胑룐닐뗐苑'
Обработка ошибок
У методов encode и decode есть режимы обработки ошибок, которые указывают, как
реагировать на ошибку преобразования.
Вместо этого режима можно использовать replace, чтобы заменить символ знаком
вопроса:
443
Ошибки при конвертации
Если изменить режим на ignore, как и в encode, символы будут просто игнорироваться:
In [52]: de_hi_utf8
Out[52]: b'gr\xc3\xbcezi'
444
Ошибки при конвертации
445
Дополнительные материалы
Дополнительные материалы
Документация Python:
What’s New In Python 3: Text Vs. Data Instead Of Unicode Vs. 8-bit
Unicode HOWTO
Статьи:
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know
About Unicode and Character Sets (No Excuses!)
The Unicode Consortium
Unicode (Wikipedia)
UTF-8 (Wikipedia)
446
17. Работа с файлами в формате CSV, JSON, YAML
Кроме того, Python позволяет записывать объекты самого языка (этот аспект в курсе
не рассматривается, но, если Вам интересно, посмотрите на модуль Pickle).
Для каждого из этих форматов в Python есть модуль, который существенно упрощает
работу с ними.
447
CSV
В этом формате каждая строка файла - это строка таблицы. Но, несмотря на название
формата, разделителем может быть не только запятая.
hostname,vendor,model,location
sw1,Cisco,3750,London
sw2,Cisco,3850,Liverpool
sw3,Cisco,3650,Liverpool
sw4,Cisco,3650,London
Чтение
Пример чтения файла в формате CSV (файл csv_read.py):
import csv
with open('sw_data.csv') as f:
reader = csv.reader(f)
for row in reader:
print(row)
$ python csv_read.py
['hostname', 'vendor', 'model', 'location']
['sw1', 'Cisco', '3750', 'London']
['sw2', 'Cisco', '3850', 'Liverpool']
['sw3', 'Cisco', '3650', 'Liverpool']
['sw4', 'Cisco', '3650', 'London']
448
CSV
Чаще всего заголовки столбцов удобней получить отдельным объектом. Это можно
сделать таким образом (файл csv_read_headers.py):
import csv
with open('sw_data.csv') as f:
reader = csv.reader(f)
headers = next(reader)
print('Headers: ', headers)
for row in reader:
print(row)
449
CSV
import csv
with open('sw_data.csv') as f:
reader = csv.DictReader(f)
for row in reader:
print(row)
print(row['hostname'], row['model'])
$ python csv_read_dict.py
OrderedDict([('hostname', 'sw1'), ('vendor', 'Cisco'), ('model', '3750'), ('location',
'London')])
sw1 3750
OrderedDict([('hostname', 'sw2'), ('vendor', 'Cisco'), ('model', '3850'), ('location',
'Liverpool')])
sw2 3850
OrderedDict([('hostname', 'sw3'), ('vendor', 'Cisco'), ('model', '3650'), ('location',
'Liverpool')])
sw3 3650
OrderedDict([('hostname', 'sw4'), ('vendor', 'Cisco'), ('model', '3650'), ('location',
'London')])
sw4 3650
Запись
Аналогичным образом с помощью модуля csv можно и записать файл в формате CSV
(файл csv_write.py):
450
CSV
import csv
with open('sw_data_new.csv') as f:
print(f.read())
$ python csv_write.py
hostname,vendor,model,location
sw1,Cisco,3750,"London, Best str"
sw2,Cisco,3850,"Liverpool, Better str"
sw3,Cisco,3650,"Liverpool, Better str"
sw4,Cisco,3650,"London, Best str"
Так получилось из-за того, что во всех строках последнего столбца есть запятая. И
кавычки указывают на то, что именно является целой строкой. Когда запятая
находятся в кавычках, модуль csv не воспринимает её как разделитель.
Иногда лучше, чтобы все строки были в кавычках. Конечно, в данном случае
достаточно простой пример, но когда в строках больше значений, то кавычки
позволяют указать, где начинается и заканчивается значение.
Модуль csv позволяет управлять этим. Для того, чтобы все строки записывались в
файл csv с кавычками, надо изменить скрипт таким образом (файл
csv_write_quoting.py):
451
CSV
import csv
with open('sw_data_new.csv') as f:
print(f.read())
$ python csv_write_quoting.py
"hostname","vendor","model","location"
"sw1","Cisco","3750","London, Best str"
"sw2","Cisco","3850","Liverpool, Better str"
"sw3","Cisco","3650","Liverpool, Better str"
"sw4","Cisco","3650","London, Best str"
Теперь все значения с кавычками. И, так как номер модели задан как строка в
изначальном списке, тут он тоже в кавычках.
Кроме метода writerow, поддерживается метод writerows. Ему можно передать любой
итерируемый объект.
import csv
with open('sw_data_new.csv') as f:
print(f.read())
452
CSV
DictWriter
С помощью DictWriter можно записать словари в формат csv.
В целом DictWriter работает так же, как writer, но так как словари не упорядочены, надо
указывать явно в каком порядке будут идти столбцы в файле. Для этого используется
параметр fieldnames (файл csv_write_dict.py):
import csv
Указание разделителя
Иногда в качестве разделителя используются другие значения. В таком случае должна
быть возможность подсказать модулю, какой именно разделитель использовать.
hostname;vendor;model;location
sw1;Cisco;3750;London
sw2;Cisco;3850;Liverpool
sw3;Cisco;3650;Liverpool
sw4;Cisco;3650;London
453
CSV
import csv
with open('sw_data2.csv') as f:
reader = csv.reader(f, delimiter=';')
for row in reader:
print(row)
454
JSON
Как и в случае с CSV, в Python есть модуль, который позволяет легко записывать и
читать данные в формате JSON.
Чтение
Файл sw_templates.json:
{
"access": [
"switchport mode access",
"switchport access vlan",
"switchport nonegotiate",
"spanning-tree portfast",
"spanning-tree bpduguard enable"
],
"trunk": [
"switchport trunk encapsulation dot1q",
"switchport mode trunk",
"switchport trunk native vlan 999",
"switchport trunk allowed vlan"
]
}
json.load()
Чтение файла в формате JSON в объект Python (файл json_read_load.py):
455
JSON
import json
with open('sw_templates.json') as f:
templates = json.load(f)
$ python json_read_load.py
{'access': ['switchport mode access', 'switchport access vlan', 'switchport nonegotiat
e', 'spanning-tree portfast', 'spanning-tree bpduguard enable'], 'trunk': ['switchport
trunk encapsulation dot1q', 'switchport mode trunk', 'switchport trunk native vlan 99
9', 'switchport trunk allowed vlan']}
access
switchport mode access
switchport access vlan
switchport nonegotiate
spanning-tree portfast
spanning-tree bpduguard enable
trunk
switchport trunk encapsulation dot1q
switchport mode trunk
switchport trunk native vlan 999
switchport trunk allowed vlan
json.loads()
Считывание строки в формате JSON в объект Python (файл json_read_loads.py):
import json
with open('sw_templates.json') as f:
file_content = f.read()
templates = json.loads(file_content)
print(templates)
Запись
456
JSON
Для записи информации в формате JSON в модуле json также два метода:
json.dumps()
Преобразование объекта в строку в формате JSON (json_write_dumps.py):
import json
with open('sw_templates.json') as f:
print(f.read())
Метод json.dumps() подходит для ситуаций, когда надо вернуть строку в формате
JSON. Например, чтобы передать ее API.
json.dump()
Запись объекта Python в файл в формате JSON (файл json_write_dump.py):
457
JSON
import json
with open('sw_templates.json') as f:
print(f.read())
458
JSON
import json
with open('sw_templates.json') as f:
print(f.read())
{
"access": [
"switchport mode access",
"switchport access vlan",
"switchport nonegotiate",
"spanning-tree portfast",
"spanning-tree bpduguard enable"
],
"trunk": [
"switchport trunk encapsulation dot1q",
"switchport mode trunk",
"switchport trunk native vlan 999",
"switchport trunk allowed vlan"
]
}
459
JSON
In [3]: print(type(trunk_template))
<class 'tuple'>
In [7]: type(templates)
Out[7]: list
In [8]: print(templates)
['switchport trunk encapsulation dot1q', 'switchport mode trunk', 'switchport trunk na
tive vlan 999', 'switchport trunk allowed vlan']
Так происходит из-за того, что в JSON используются другие типы данных и не для всех
типов данных Python есть соответствия.
Python JSON
dict object
str string
int, float number
True true
False false
None null
460
JSON
JSON Python
object dict
array list
string str
null None
Кроме того, в JSON ключами словаря могут быть только строки. Но, если в словаре
Python использовались числа, ошибки не будет. Вместо этого выполнится конвертация
чисел в строки:
461
JSON
In [29]: json.dumps(d)
Out[29]: '{"1": 100, "2": 200}'
462
YAML
YAML более приятен для восприятия человеком, чем JSON, поэтому его часто
используют для описания сценариев в ПО. Например, в Ansible.
Синтаксис YAML
Как и Python, YAML использует отступы для указания структуры документа. Но в YAML
можно использовать только пробелы и нельзя использовать знаки табуляции.
Список
Список может быть записан в одну строку:
Когда список записан таким блоком, каждая строка должна начинаться с - (минуса и
пробела), и все строки в списке должны быть на одном уровне отступа.
Словарь
Словарь также может быть записан в одну строку:
Или блоком:
463
YAML
vlan: 100
name: IT
Строки
Строки в YAML не обязательно брать в кавычки. Это удобно, но иногда всё же следует
использовать кавычки. Например, когда в строке используется какой-то специальный
символ (специальный для YAML).
Такую строку, например, нужно взять в кавычки, чтобы она была корректно воспринята
YAML:
Комбинация элементов
Словарь, в котором есть два ключа: access и trunk. Значения, которые соответствуют
этим ключам - списки команд:
access:
- switchport mode access
- switchport access vlan
- switchport nonegotiate
- spanning-tree portfast
- spanning-tree bpduguard enable
trunk:
- switchport trunk encapsulation dot1q
- switchport mode trunk
- switchport trunk native vlan 999
- switchport trunk allowed vlan
Список словарей:
464
YAML
- BS: 1550
IT: 791
id: 11
name: Liverpool
to_id: 1
to_name: LONDON
- BS: 1510
IT: 793
id: 12
name: Bristol
to_id: 1
to_name: LONDON
- BS: 1650
IT: 892
id: 14
name: Coventry
to_id: 2
to_name: Manchester
Модуль PyYAML
Для работы с YAML в Python используется модуль PyYAML. Он не входит в
стандартную библиотеку модулей, поэтому его нужно установить:
Чтение из YAML
Попробуем преобразовать данные из файла YAML в объекты Python.
Файл info.yaml:
465
YAML
- BS: 1550
IT: 791
id: 11
name: Liverpool
to_id: 1
to_name: LONDON
- BS: 1510
IT: 793
id: 12
name: Bristol
to_id: 1
to_name: LONDON
- BS: 1650
IT: 892
id: 14
name: Coventry
to_id: 2
to_name: Manchester
import yaml
from pprint import pprint
with open('info.yaml') as f:
templates = yaml.load(f)
pprint(templates)
Результат:
466
YAML
$ python yaml_read.py
[{'BS': 1550,
'IT': 791,
'id': 11,
'name': 'Liverpool',
'to_id': 1,
'to_name': 'LONDON'},
{'BS': 1510,
'IT': 793,
'id': 12,
'name': 'Bristol',
'to_id': 1,
'to_name': 'LONDON'},
{'BS': 1650,
'IT': 892,
'id': 14,
'name': 'Coventry',
'to_id': 2,
'to_name': 'Manchester'}]
Формат YAML очень удобен для хранения различных параметров, особенно, если они
заполняются вручную.
Запись в YAML
Запись объектов Python в YAML (файл yaml_write.py):
import yaml
with open('sw_templates.yaml') as f:
print(f.read())
467
YAML
access: [switchport mode access, switchport access vlan, switchport nonegotiate, spann
ing-tree
portfast, spanning-tree bpduguard enable]
trunk: [switchport trunk encapsulation dot1q, switchport mode trunk, switchport trunk
native vlan 999, switchport trunk allowed vlan]
import yaml
with open('sw_templates.yaml') as f:
print f.read()
access:
- switchport mode access
- switchport access vlan
- switchport nonegotiate
- spanning-tree portfast
- spanning-tree bpduguard enable
trunk:
- switchport trunk encapsulation dot1q
- switchport mode trunk
- switchport trunk native vlan 999
- switchport trunk allowed vlan
468
YAML
469
Дополнительные материалы
Дополнительные материалы
В этом разделе рассматривались только базовые операции чтения и записи, без
дополнительных параметров. Подробнее можно почитать в документации модулей.
CSV
JSON
YAML
Кроме того, на сайте PyMOTW очень хорошо расписываются все модули Python,
которые входят в стандартную библиотеку (устанавливаются вместе с самим Python):
CSV
JSON
470
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 17.1
В этом задании нужно:
Функция parse_sh_version:
ожидает аргумент output в котором находится вывод команды sh version (не имя
файла)
обрабатывает вывод, с помощью регулярных выражений
возвращает кортеж из трёх элементов:
ios - в формате "12.4(5)T"
image - в формате "flash:c2800-advipservicesk9-mz.124-5.T.bin"
uptime - в формате "5 days, 3 hours, 3 minutes"
Функция write_to_csv:
471
Задания
Остальное содержимое скрипта может быть в скрипте, а может быть в ещё одной
функции.
Скрипт должен:
В скрипте, с помощью модуля glob, создан список файлов, имя которых начинается на
sh_vers. Вы можете раскомментировать строку print(sh_version_files), чтобы
посмотреть содержимое списка.
Кроме того, создан список заголовков (headers), который должен быть записан в CSV.
import glob
sh_version_files = glob.glob('sh_vers*')
#print(sh_version_files)
Задание 17.2
Создать функцию parse_sh_cdp_neighbors, которая обрабатывает вывод команды show
cdp neighbors.
Функция ожидает, как аргумент, вывод команды одной строкой (не имя файла).
472
Задания
При этом интерфейсы могут быть записаны с пробелом Fa 0/0 или без Fa0/0.
Задание 17.2a
С помощью функции parse_sh_cdp_neighbors из задания 17.2, обработать вывод
команды sh cdp neighbor из файлов:
sh_cdp_n_sw1.txt
sh_cdp_n_r1.txt
sh_cdp_n_r2.txt
sh_cdp_n_r3.txt
sh_cdp_n_r4.txt
sh_cdp_n_r5.txt
sh_cdp_n_r6.txt
При этом интерфейсы могут быть записаны с пробелом Fa 0/0 или без Fa0/0.
Задание 17.2b
Переделать функциональность скрипта из задания 17.2a, в функцию
generate_topology_from_cdp.
473
Задания
neighbor
save_to_file - этот параметр управляет тем, будет ли записан в файл, итоговый
словарь
значение по умолчанию - True
topology_filename - имя файла, в который сохранится топология.
по умолчанию, должно использоваться имя topology.yaml.
топология сохраняется только, если аргумент save_to_file указан равным True
sh_cdp_n_sw1.txt
sh_cdp_n_r1.txt
sh_cdp_n_r2.txt
sh_cdp_n_r3.txt
sh_cdp_n_r4.txt
sh_cdp_n_r5.txt
sh_cdp_n_r6.txt
Задание 17.2c
С помощью функции draw_topology из файла draw_network_graph.py сгенерировать
топологию, которая соответствует описанию в файле topology.yaml
474
Задания
При этом:
Для выполнения этого задания, должен быть установлен graphviz: apt-get install
graphviz
475
18. Работа с базами данных
476
SQL
SQL
SQL (structured query language) - используется для описания структуры БД,
управления данными (добавление, изменение, удаление, получение), управления
правами доступа к БД и ее объектам, управления транзакциями.
DDL
CREATE - создание новой таблицы, СУБД, схемы
ALTER - изменение существующей таблицы, колонки
DROP - удаление существующих объектов из СУБД
DML
SELECT - выбор данных
INSERT - добавление новых данных
UPDATE - обновление существующих данных
DELETE - удаление данных
DCL
GRANT - предоставление пользователям разрешения на чтение/запись
определенных объектов в СУБД
REVOKE - отзыв ранее предоставленных разрешений
TCL
COMMIT Transaction - применение транзакции
ROLLBACK Transaction - откат всех изменений, сделанных в текущей
транзакции
SQL и Python
Для работы с реляционной СУБД в Python можно использовать два подхода:
477
SQL
работы с БД
Например, SQLAlchemy
478
SQLite
SQLite
SQLite — встраиваемая в процесс реализация SQL-машины.
Слово SQL-сервер здесь не используем, потому что как таковой сервер там не
нужен — весь функционал, который встраивается в SQL-сервер, реализован
внутри библиотеки (и, соответственно, внутри программы, которая её использует).
SQLite CLI
В комплекте поставки SQLite идёт также утилита для работы с SQLite в командной
строке. Утилита представлена в виде исполняемого файла sqlite3 (sqlite3.exe для
Windows), и с ее помощью можно вручную выполнять команды SQL.
С помощью этой утилиты очень удобно проверять правильность команд SQL, а также
в целом знакомиться с языком SQL.
Если Вы используете Linux или Mac OS, то, скорее всего, sqlite3 установлен. Если
Вы используете Windows, то можно скачать sqlite3 тут.
Для того, чтобы создать БД (или открыть уже созданную), надо просто вызвать sqlite3
таким образом:
$ sqlite3 testDB.db
SQLite version 3.8.7.1 2014-10-29 13:59:56
Enter ".help" for usage hints.
sqlite>
Внутри sqlite3 можно выполнять команды SQL или так называемые метакоманды (или
dot-команды).
Метакоманды
К метакомандам относятся несколько специальных команд для работы с SQLite. Они
относятся только к утилите sqlite3, а не к SQL языку. В конце этих команд ; ставить
не нужно.
479
SQLite
Примеры метакоманд:
Примеры выполнения:
sqlite> .help
.backup ?DB? FILE Backup DB (default "main") to FILE
.bail ON|OFF Stop after hitting an error. Default OFF
.databases List names and files of attached databases
...
sqlite> .databases
seq name file
--- -------- ----------------------------------
0 main /home/nata/py_for_ne/db/db_article/testDB.db
480
Основы SQL (в sqlite3 CLI)
Если Вы знакомы с базовым синтаксисом SQL, этот раздел можно пропустить и сразу
перейти к разделу Модуль sqlite3.
481
Основы SQL (в sqlite3 CLI)
CREATE
Оператор create позволяет создавать таблицы.
sqlite> create table switch (mac text not NULL primary key, hostname text, model text,
location text);
Кроме того, поле mac является первичным ключом. Это автоматически значит, что:
В этом примере это вполне логично, так как MAC-адрес должен быть уникальным.
482
Основы SQL (в sqlite3 CLI)
DROP
Оператор DROP удаляет таблицу вместе со схемой и всеми данными.
483
Основы SQL (в sqlite3 CLI)
INSERT
Оператор insert используется для добавления данных в таблицу.
Если Вы удалили таблицу, выполнив drop, надо ее заново создать: create table
switch (mac text not NULL primary key, hostname text, model text, location text);
Если указываются значения для всех полей, добавить запись можно таким образом
(порядок полей должен соблюдаться):
sqlite> INSERT into switch values ('0010.A1AA.C1CC', 'sw1', 'Cisco 3750', 'London, Gre
en Str');
Если нужно указать не все поля или указать их в произвольном порядке, используется
такая запись:
484
Основы SQL (в sqlite3 CLI)
SELECT
Оператор select позволяет запрашивать информацию в таблице.
Например:
select * означает, что нужно вывести все поля таблицы. Следом указывается, из
sqlite> .headers ON
sqlite> SELECT * from switch;
mac|hostname|model|location
0010.A1AA.C1CC|sw1|Cisco 3750|London, Green Str
0020.A2AA.C2CC|sw2|Cisco 3850|London, Green Str
При желании можно выставить и ширину колонок. Для этого используется команда
.width . Например, попробуйте выставить .width 20 .
485
Основы SQL (в sqlite3 CLI)
.headers on
.mode column
486
Основы SQL (в sqlite3 CLI)
WHERE
Оператор WHERE используется для уточнения запроса. С помощью этого оператора
можно указывать определенные условия, по которым отбираются данные. Если
условие выполнено, возвращается соответствующее значение из таблицы, если нет -
не возвращается.
Чтобы в таблице было больше записей, надо создать еще несколько строк. В SQLite
есть метакоманда .read, которая позволяет загружать команды SQL из файла.
INSERT into switch values ('0030.A3AA.C1CC', 'sw3', 'Cisco 3750', 'London, Green Str')
;
INSERT into switch values ('0040.A4AA.C2CC', 'sw4', 'Cisco 3850', 'London, Green Str')
;
INSERT into switch values ('0050.A5AA.C3CC', 'sw5', 'Cisco 3850', 'London, Green Str')
;
INSERT into switch values ('0060.A6AA.C4CC', 'sw6', 'C3750', 'London, Green Str');
INSERT into switch values ('0070.A7AA.C5CC', 'sw7', 'Cisco 3650', 'London, Green Str')
;
487
Основы SQL (в sqlite3 CLI)
488
Основы SQL (в sqlite3 CLI)
489
Основы SQL (в sqlite3 CLI)
ALTER
Оператор ALTER позволяет менять существующую таблицу: добавлять новые колонки
или переименовывать таблицу.
490
Основы SQL (в sqlite3 CLI)
UPDATE
Оператор UPDATE используется для изменения существующей записи таблицы.
491
Основы SQL (в sqlite3 CLI)
sqlite> UPDATE switch set mngmt_ip = '10.255.1.2', mngmt_vid = 255 WHERE hostname = 's
w2';
sqlite> SELECT * from switch;
mac hostname model location mngmt_ip mngmt_vid
-------------- ---------- ---------- ----------------- ---------- ----------
0010.A1AA.C1CC sw1 Cisco 3750 London, Green Str 10.255.1.1 255
0020.A2AA.C2CC sw2 Cisco 3850 London, Green Str 10.255.1.2 255
0030.A3AA.C1CC sw3 Cisco 3750 London, Green Str
0040.A4AA.C2CC sw4 Cisco 3850 London, Green Str
0050.A5AA.C3CC sw5 Cisco 3850 London, Green Str
0060.A6AA.C4CC sw6 C3750 London, Green Str
0070.A7AA.C5CC sw7 Cisco 3650 London, Green Str
UPDATE switch set mngmt_ip = '10.255.1.3', mngmt_vid = 255 WHERE hostname = 'sw3';
UPDATE switch set mngmt_ip = '10.255.1.4', mngmt_vid = 255 WHERE hostname = 'sw4';
UPDATE switch set mngmt_ip = '10.255.1.5', mngmt_vid = 255 WHERE hostname = 'sw5';
UPDATE switch set mngmt_ip = '10.255.1.6', mngmt_vid = 255 WHERE hostname = 'sw6';
UPDATE switch set mngmt_ip = '10.255.1.7', mngmt_vid = 255 WHERE hostname = 'sw7';
Теперь предположим, что sw1 был заменен с модели 3750 на модель 3850.
Соответственно, изменилось не только поле модель, но и поле MAC-адрес.
Внесение изменений:
sqlite> UPDATE switch set model = 'Cisco 3850', mac = '0010.D1DD.E1EE' WHERE hostname
= 'sw1';
492
Основы SQL (в sqlite3 CLI)
493
Основы SQL (в sqlite3 CLI)
REPLACE
Оператор REPLACE используется для добавления или замены данных в таблице.
Если были указаны не все поля, в новой записи будут только те поля, которые
были указаны. Это связано с тем, что replace сначала удаляет существующую
запись.
494
Основы SQL (в sqlite3 CLI)
495
Основы SQL (в sqlite3 CLI)
DELETE
Оператор delete используется для удаления записей.
496
Основы SQL (в sqlite3 CLI)
ORDER BY
Оператор ORDER BY используется для сортировки вывода по определенному полю,
по возрастанию или убыванию. Для этого он добавляется к оператору SELECT.
497
Основы SQL (в sqlite3 CLI)
498
Основы SQL (в sqlite3 CLI)
AND
Оператор AND позволяет группировать несколько условий:
sqlite> select * from switch where model = 'Cisco 3750' and ip LIKE '10.0.%';
mac hostname model location ip vlan
-------------- ---------- ---------- ----------------- ---------- ----------
0010.A11A.C1CC sw1 Cisco 3750 London, Green Str 10.0.255.1 255
0020.A22A.C2CC sw2 Cisco 3750 London, Green Str 10.0.255.2 255
sqlite> select * from switch where model LIKE '%3750%' and ip LIKE '10.0.%';
mac hostname model location ip vlan
-------------- ---------- ---------- ----------------- ---------- ----------
0010.A11A.C1CC sw1 Cisco 3750 London, Green Str 10.0.255.1 255
0020.A22A.C2CC sw2 Cisco 3750 London, Green Str 10.0.255.2 255
OR
Оператор OR:
sqlite> select * from switch where model = 'Cisco 3750' or model = 'Cisco 3850';
mac hostname model location ip vlan
-------------- ---------- ---------- ----------------- ---------- ----------
0040.A4AA.C2CC sw4 Cisco 3850 London, Green Str 10.255.1.4 255
0050.A5AA.C3CC sw5 Cisco 3850 London, Green Str 10.255.1.5 255
0010.A11A.C1CC sw1 Cisco 3750 London, Green Str 10.0.255.1 255
0020.A22A.C2CC sw2 Cisco 3750 London, Green Str 10.0.255.2 255
0030.A3AA.C1CC sw3 Cisco 3850 London, Green Str 10.255.1.3 255
IN
Оператор IN:
NOT
Оператор NOT:
499
Основы SQL (в sqlite3 CLI)
sqlite> select * from switch where model not in ('Cisco 3750', 'C3750');
mac hostname model location ip vlan
-------------- ---------- ---------- ----------------- ---------- ----------
0040.A4AA.C2CC sw4 Cisco 3850 London, Green Str 10.255.1.4 255
0050.A5AA.C3CC sw5 Cisco 3850 London, Green Str 10.255.1.5 255
0070.A7AA.C5CC sw7 Cisco 3650 London, Green Str 10.255.1.7 255
0030.A3AA.C1CC sw3 Cisco 3850 London, Green Str 10.255.1.3 255
sqlite> select * from switch where model LIKE '%3750%' and ip not LIKE '10.0.%';
mac hostname model location ip vlan
-------------- ---------- ---------- ----------------- ---------- ----------
0060.A6AA.C4CC sw6 C3750 London, Green Str 10.255.1.6 255
500
Модуль sqlite3
Модуль sqlite3
Для работы с SQLite в Python используется модуль sqlite3.
Connection
Объект Connection - это подключение к конкретной БД. Можно сказать, что этот
объект представляет БД.
import sqlite3
connection = sqlite3.connect('dhcp_snooping.db')
Cursor
После создания соединения надо создать объект Cursor - это основной способ работы
с БД.
connection = sqlite3.connect('dhcp_snooping.db')
cursor = connection.cursor()
501
Модуль sqlite3
раз
Метод execute
Метод execute позволяет выполнить одну команду SQL.
In [4]: cursor.execute("create table switch (mac text not NULL primary key, hostname t
ext, model text, location text)")
Out[4]: <sqlite3.Cursor at 0x1085be880>
In [5]: data = [
...: ('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str'),
...: ('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str'),
...: ('0000.AAAA.DDDD', 'sw3', 'Cisco 2960', 'London, Green Str'),
...: ('0011.AAAA.CCCC', 'sw4', 'Cisco 3750', 'London, Green Str')]
502
Модуль sqlite3
Второй аргумент, который передается методу execute, должен быть кортежем. Если
нужно передать кортеж с одним элементом, используется запись (value, ) .
Чтобы изменения были применены, нужно выполнить commit (обратите внимание, что
метод commit вызывается у соединения):
In [8]: connection.commit()
Теперь, при запросе из командной строки sqlite3, можно увидеть эти строки в таблице
switch:
$ sqlite3 sw_inventory.db
Метод executemany
Метод executemany позволяет выполнить одну команду SQL для последовательности
параметров (или для итератора).
503
Модуль sqlite3
In [9]: data2 = [
...: ('0000.1111.0001', 'sw5', 'Cisco 3750', 'London, Green Str'),
...: ('0000.1111.0002', 'sw6', 'Cisco 3750', 'London, Green Str'),
...: ('0000.1111.0003', 'sw7', 'Cisco 3750', 'London, Green Str'),
...: ('0000.1111.0004', 'sw8', 'Cisco 3750', 'London, Green Str')]
In [12]: connection.commit()
Метод executescript
Метод executescript позволяет выполнить несколько выражений SQL за один раз.
504
Модуль sqlite3
In [15]: cursor.executescript('''
...: create table switches(
...: hostname text not NULL primary key,
...: location text
...: );
...:
...: create table dhcp(
...: mac text not NULL primary key,
...: ip text,
...: vlan text,
...: interface text,
...: switch text not null references switches(hostname)
...: );
...: ''')
Out[15]: <sqlite3.Cursor at 0x10efd67a0>
505
Модуль sqlite3
Метод fetchone
Метод fetchone возвращает одну строку данных.
In [20]: cursor.fetchone()
Out[20]: ('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str')
Обратите внимание, что хотя запрос SQL подразумевает, что запрашивалось всё
содержимое таблицы, метод fetchone вернул только одну строку.
In [21]: print(cursor.fetchone())
('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str')
506
Модуль sqlite3
Метод fetchmany
Метод fetchmany возвращает список строк данных.
Синтаксис метода:
cursor.fetchmany([size=cursor.arraysize])
In [24]: print(cursor.arraysize)
1
507
Модуль sqlite3
Метод выдает нужное количество строк, а если строк осталось меньше, чем параметр
size, то оставшиеся строки.
Метод fetchall
Метод fetchall возвращает все строки в виде списка:
In [29]: cursor.fetchall()
Out[29]:
[('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str'),
('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str'),
('0000.AAAA.DDDD', 'sw3', 'Cisco 2960', 'London, Green Str'),
('0011.AAAA.CCCC', 'sw4', 'Cisco 3750', 'London, Green Str'),
('0000.1111.0001', 'sw5', 'Cisco 3750', 'London, Green Str'),
('0000.1111.0002', 'sw6', 'Cisco 3750', 'London, Green Str'),
('0000.1111.0003', 'sw7', 'Cisco 3750', 'London, Green Str'),
('0000.1111.0004', 'sw8', 'Cisco 3750', 'London, Green Str')]
508
Модуль sqlite3
In [31]: cursor.fetchone()
Out[31]: ('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str')
In [32]: cursor.fetchone()
Out[32]: ('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str')
In [33]: cursor.fetchall()
Out[33]:
[('0000.AAAA.DDDD', 'sw3', 'Cisco 2960', 'London, Green Str'),
('0011.AAAA.CCCC', 'sw4', 'Cisco 3750', 'London, Green Str'),
('0000.1111.0001', 'sw5', 'Cisco 3750', 'London, Green Str'),
('0000.1111.0002', 'sw6', 'Cisco 3750', 'London, Green Str'),
('0000.1111.0003', 'sw7', 'Cisco 3750', 'London, Green Str'),
('0000.1111.0004', 'sw8', 'Cisco 3750', 'London, Green Str')]
509
Модуль sqlite3
При использовании методов execute возвращается курсор. А, так как курсор можно
использовать как итератор, можно использовать его, например, в цикле for:
510
Модуль sqlite3
con = sqlite3.connect('sw_inventory2.db')
con.close()
$ python create_sw_inventory_ver1.py
('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str')
('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str')
('0000.AAAA.DDDD', 'sw3', 'Cisco 2960', 'London, Green Str')
('0011.AAAA.CCCC', 'sw4', 'Cisco 3750', 'London, Green Str')
511
Модуль sqlite3
512
Модуль sqlite3
Обработка исключений
Посмотрим на пример использования метода execute при возникновении ошибки.
В таблице switch поле mac должно быть уникальным. И, если попытаться записать
пересекающийся MAC-адрес, возникнет ошибка:
In [38]: query = "INSERT into switch values ('0000.AAAA.DDDD', 'sw7', 'Cisco 2960', 'L
ondon, Green Str')"
In [39]: con.execute(query)
------------------------------------------------------------
IntegrityError Traceback (most recent call last)
<ipython-input-56-ad34d83a8a84> in <module>()
----> 1 con.execute(query)
In [40]: try:
...: con.execute(query)
...: except sqlite3.IntegrityError as e:
...: print("Error occured: ", e)
...:
Error occured: UNIQUE constraint failed: switch.mac
513
Модуль sqlite3
При этом:
con = sqlite3.connect('sw_inventory3.db')
con.execute('''create table switch
(mac text not NULL primary key, hostname text, model text, location tex
t)''')
try:
with con:
query = 'INSERT into switch values (?, ?, ?, ?)'
con.executemany(query, data)
except sqlite3.IntegrityError as e:
print('Error occured: ', e)
con.close()
514
Модуль sqlite3
Для проверки этого функционала надо записать в таблицу данные, в которых MAC-
адрес повторяется. Но прежде, чтобы не повторять части кода, лучше разнести код в
файле create_sw_inventory_ver2.py по функциям (файл
create_sw_inventory_ver2_functions.py):
def create_connection(db_name):
'''
Функция создает соединение с БД db_name
и возвращает его
'''
connection = sqlite3.connect(db_name)
return connection
515
Модуль sqlite3
* connection - соединение с БД
* query - запрос, который нужно выполнить
if __name__ == '__main__':
con = create_connection('sw_inventory3.db')
print('Создание таблицы...')
schema = '''create table switch
(mac text primary key, hostname text, model text, location text)'''
con.execute(schema)
con.close()
$ python create_sw_inventory_ver2_functions.py
Создание таблицы...
Запись данных в БД:
[('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str'),
('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str'),
('0000.AAAA.DDDD', 'sw3', 'Cisco 2960', 'London, Green Str'),
('0011.AAAA.CCCC', 'sw4', 'Cisco 3750', 'London, Green Str')]
Запись данных прошла успешно
Проверка содержимого БД
[('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str'),
('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str'),
('0000.AAAA.DDDD', 'sw3', 'Cisco 2960', 'London, Green Str'),
('0011.AAAA.CCCC', 'sw4', 'Cisco 3750', 'London, Green Str')]
516
Модуль sqlite3
con = dbf.create_connection('sw_inventory3.db')
print('-'*60)
print("Попытка записать данные с повторяющимся MAC-адресом:")
pprint(data2)
dbf.write_data_to_db(con, query_insert, data2)
print("\nПроверка содержимого БД")
pprint(dbf.get_all_from_db(con, query_get_all))
con.close()
517
Модуль sqlite3
$ python create_sw_inventory_ver3.py
Проверка содержимого БД
[('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str'),
('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str'),
('0000.AAAA.DDDD', 'sw3', 'Cisco 2960', 'London, Green Str'),
('0011.AAAA.CCCC', 'sw4', 'Cisco 3750', 'London, Green Str')]
Так получилось из-за того, что используется метод executemany, и в пределах одной
транзакции мы пытаемся записать все 4 строки. Если возникает ошибка с одной из них
- откатываются все изменения.
Иногда это именно то поведение, которое нужно. Если же надо, чтобы игнорировались
только строки с ошибками, надо использовать метод execute и записывать каждую
строку отдельно.
518
Модуль sqlite3
'''
Функция ожидает аргументы:
* connection - соединение с БД
* query - запрос, который нужно выполнить
* data - данные, которые надо передать в виде списка кортежей
con = dbf.create_connection('sw_inventory3.db')
print('-'*60)
print('Попытка записать данные с повторяющимся MAC-адресом:')
pprint(data2)
write_rows_to_db(con, query_insert, data2, verbose=True)
print('\nПроверка содержимого БД')
pprint(dbf.get_all_from_db(con, query_get_all))
con.close()
519
Модуль sqlite3
$ python create_sw_inventory_ver4.py
Проверка содержимого БД
[('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str'),
('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str'),
('0000.AAAA.DDDD', 'sw3', 'Cisco 2960', 'London, Green Str'),
('0011.AAAA.CCCC', 'sw4', 'Cisco 3750', 'London, Green Str'),
('0055.AAAA.CCCC', 'sw5', 'Cisco 3750', 'London, Green Str'),
('0066.BBBB.CCCC', 'sw6', 'Cisco 3780', 'London, Green Str'),
('0088.AAAA.CCCC', 'sw8', 'Cisco 3750', 'London, Green Str')]
520
Модуль sqlite3
Для этого примера достаточно создать одну таблицу, где будет храниться информация.
MAC-адрес является первичным ключом нашей таблицы, что вполне логично, так как
MAC-адрес должен быть уникальным.
Кроме того, используется выражение create table if not exists - SQLite создаст
таблицу только в том случае, если она не существует.
Теперь надо создать файл БД, подключиться к базе данных и создать таблицу (файл
create_sqlite_ver1.py):
521
Модуль sqlite3
import sqlite3
conn = sqlite3.connect('dhcp_snooping.db')
print('Creating schema...')
with open('dhcp_snooping_schema.sql', 'r') as f:
schema = f.read()
conn.executescript(schema)
print('Done')
conn.close()
Комментарии к файлу:
Выполнение скрипта:
$ python create_sqlite_ver1.py
Creating schema...
Done
522
Модуль sqlite3
import sqlite3
import re
result = []
conn = sqlite3.connect('dhcp_snooping.db')
print('Creating schema...')
with open('dhcp_snooping_schema.sql', 'r') as f:
schema = f.read()
conn.executescript(schema)
print('Done')
conn.close()
523
Модуль sqlite3
Пока что файл БД каждый раз надо удалять, так как скрипт пытается его создать
при каждом запуске.
Комментарии к скрипту:
Выполняем скрипт:
$ python create_sqlite_ver2.py
Creating schema...
Done
Inserting DHCP Snooping data
524
Модуль sqlite3
Переделаем скрипт таким образом, чтобы в нём была проверка на наличие файла
dhcp_snooping.db. Если файл БД есть, то не надо создавать таблицу, считаем, что она
уже создана.
Файл create_sqlite_ver3.py:
525
Модуль sqlite3
import os
import sqlite3
import re
data_filename = 'dhcp_snooping.txt'
db_filename = 'dhcp_snooping.db'
schema_filename = 'dhcp_snooping_schema.sql'
result = []
db_exists = os.path.exists(db_filename)
conn = sqlite3.connect(db_filename)
if not db_exists:
print('Creating schema...')
with open(schema_filename, 'r') as f:
schema = f.read()
conn.executescript(schema)
print('Done')
else:
print('Database exists, assume dhcp table does, too.')
conn.close()
Теперь есть проверка наличия файла БД, и файл dhcp_snooping.db будет создаваться
только в том случае, если его нет. Данные также записываются только в том случае,
если не создан файл dhcp_snooping.db.
526
Модуль sqlite3
$ rm dhcp_snooping.db
$ python create_sqlite_ver3.py
Creating schema...
Done
Inserting DHCP Snooping data
$ rm dhcp_snooping.db
$ python create_sqlite_ver1.py
Creating schema...
Done
$ python create_sqlite_ver3.py
Database exists, assume dhcp table does, too.
Inserting DHCP Snooping data
$ python create_sqlite_ver3.py
Database exists, assume dhcp table does, too.
Inserting DHCP Snooping data
Error occured: UNIQUE constraint failed: dhcp.mac
Error occured: UNIQUE constraint failed: dhcp.mac
Error occured: UNIQUE constraint failed: dhcp.mac
Error occured: UNIQUE constraint failed: dhcp.mac
Файл get_data_ver1.py:
527
Модуль sqlite3
db_filename = 'dhcp_snooping.db'
conn = sqlite3.connect(db_filename)
Комментарии к скрипту:
528
Модуль sqlite3
Файл get_data_ver2.py:
529
Модуль sqlite3
db_filename = 'dhcp_snooping.db'
query_dict = {'vlan': 'select mac, ip, interface from dhcp where vlan = ?',
'mac': 'select vlan, ip, interface from dhcp where mac = ?',
'ip': 'select vlan, mac, interface from dhcp where ip = ?',
'interface': 'select vlan, mac, ip from dhcp where interface = ?'}
query = query_dict[key]
result = conn.execute(query, (value,))
530
Дополнительные материалы
Дополнительные материалы
Документация:
Статьи:
531
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 18.1
На основе файла create_sqlite_ver3.py из примеров раздела, необходимо создать два
скрипта:
create_db.py
сюда должна быть вынесена функциональность по созданию БД:
должна выполняться проверка наличия файла БД
если файла нет, согласно описанию схемы БД в файле
dhcp_snooping_schema.sql, должна быть создана БД (БД отличается от
примера в разделе)
add_data.py
с помощью этого скрипта, выполняется добавление данных в БД
добавлять надо не только данные из вывода sh ip dhcp snooping binding, но и
информацию о коммутаторах
Код в скриптах должен быть разбит на функции. Какие именно функции и как
разделить код, надо решить самостоятельно. Часть кода может быть глобальной.
532
Задания
Задание 18.1a
Скопировать скрипт add_data.py из задания 18.1.
Задание 18.2
На основе файла get_data_ver1.py из раздела, создать скрипт get_data.py.
Код в скрипте должен быть разбит на функции. Какие именно функции и как разделить
код, надо решить самостоятельно. Часть кода может быть глобальной.
если скрипт был вызван без аргументов, вывести всё содержимое таблицы dhcp
отформатировать вывод в виде столбцов
если скрипт был вызван с двумя аргументами, вывести информацию из таблицы
dhcp, которая соответствует полю и значению
если скрипт был вызван с любым другим количеством аргументов, вывести
сообщение, что скрипт поддерживает только два или ноль аргументов
533
Задания
$ python get_data.py
534
Задания
Задание 18.2a
Дополнить скрипт get_data.py из задания 18.2
Задание 18.3
В прошлых заданиях информация добавлялась в пустую БД. В этом задании,
разбирается ситуация, когда в БД уже есть информация.
При создании схемы БД, было явно указано, что поле MAC-адрес, должно быть
уникальным. Поэтому, при добавлении записи с таким же MAC-адресом, возникает
ошибка.
Но, нужно каким-то образом обновлять БД, чтобы в ней хранилась актуальная
информация.
Поэтому, мы будем делать немного по-другому. Создадим новое поле active, которое
будет указывать является ли запись актуальной.
0 - означает False. И используется для того, чтобы отметить запись как неактивную
1 - True. Используется чтобы указать, что запись активна
535
Задания
Код в скрипте должен быть разбит на функции. Какие именно функции и как разделить
код, надо решить самостоятельно. Часть кода может быть глобальной.
Для проверки задания и работы нового поля, попробуйте удалить пару строк из одного
из файлов с выводом dhcp snooping. И после этого проверить, что удаление строки
отображаются в таблице как неактивные.
Задание 18.4
Обновить файл get_data из задания 18.2 или 18.2a. Добавить поддержку столбца
active, который мы добавили в задании 18.3.
Например:
536
Задания
=======================================
Inactive values:
----------------------------------------
mac : 00:09:23:34:16:18
vlan : 10
interface : FastEthernet0/4
switch : sw1
----------------------------------------
$ python get_data1.py
--------------------------------------------------------------------------------
Active values:
--------------------------------------------------------------------------------
00:09:BB:3D:D6:58 10.1.10.2 10 FastEthernet0/1 sw1 1
00:04:A3:3E:5B:69 10.1.5.2 5 FastEthernet0/10 sw1 1
00:05:B3:7E:9B:60 10.1.5.4 5 FastEthernet0/9 sw1 1
00:07:BC:3F:A6:50 10.1.10.6 10 FastEthernet0/3 sw1 1
00:09:BC:3F:A6:50 192.168.100.100 1 FastEthernet0/7 sw1 1
00:B4:A3:3E:5B:69 10.1.5.20 5 FastEthernet0/5 sw2 1
00:C5:B3:7E:9B:60 10.1.5.40 5 FastEthernet0/9 sw2 1
00:A9:BC:3F:A6:50 10.1.10.60 20 FastEthernet0/2 sw2 1
--------------------------------------------------------------------------------
Inactive values:
--------------------------------------------------------------------------------
00:A9:BB:3D:D6:58 10.1.10.20 10 FastEthernet0/7 sw2 0
Задание 18.5
Теперь в БД остается и старая информация. И, если какой-то MAC-адрес не появлялся
в новых записях, запись с ним, может оставаться в БД очень долго.
И, хотя это может быть полезно, чтобы посмотреть, где MAC-адрес находился в
последний раз, постоянно хранить эту информацию не очень полезно.
Для того, чтобы сделать такой критерий, нужно ввести новое поле, в которое будет
записываться последнее время добавления записи.
537
Задания
import datetime
now = str(datetime.datetime.today().replace(microsecond=0))
#print(now)
Задание 18.5a
После выполнения задания 18.5, в таблице dhcp есть новое поле last_active.
Обновите скрипт add_data.py, таким образом, чтобы он удалял все записи, которые
были активными более 7 дней назад.
Для того, чтобы получить такие записи, можно просто вручную обновить поле
last_active.
now = datetime.today().replace(microsecond=0)
week_ago = now - timedelta(days = 7)
#print(now)
#print(week_ago)
#print(now > week_ago)
#print(str(now) > str(week_ago))
Задание 18.6
В этом задании выложен файл parse_dhcp_snooping.py.
538
Задания
И задача этого задания в том, чтобы создать все необходимые функции, в файле
parse_dhcp_snooping_functions.py на основе информации в файле
parse_dhcp_snooping.py.
Для того, чтобы было проще начать, попробуйте создать необходимые функции в
файле parse_dhcp_snooping_functions.py и, например, просто выведите аргументы
функций, используя print.
создание БД
добавление информации о коммутаторах
добавление информации на основании вывода sh ip dhcp snooping binding из
539
Задания
файлов
выборку информации из БД (по параметру и всю информацию)
Чтобы было проще понять, как будет выглядеть вызов скрипта, ниже несколько
примеров.
$ python parse_dhcp_snooping.py -h
usage: parse_dhcp_snooping.py [-h] {create_db,add,get} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
valid subcommands
optional arguments:
-h, --help show this help message and exit
--db DB_FILE db name
-k {mac,ip,vlan,interface,switch}
host key (parameter) to search
-v VALUE value of key
-a show db content
positional arguments:
filename file(s) to add to db
optional arguments:
-h, --help show this help message and exit
--db DB_FILE db name
-s add switch data if set, else add normal data
optional arguments:
-h, --help show this help message and exit
-n NAME db filename
-s SCHEMA db schema filename
540
Задания
541
Задания
542
V. Работа с сетевым оборудованием
543
19. Подключение к оборудованию
Подключение к оборудованию
В этом разделе рассматривается как подключиться к оборудованию по протоколам:
SSH
Telnet
пользователь: cisco
пароль: cisco
пароль на режим enable: cisco
544
19. Подключение к оборудованию
SSH версии 2
IP-адреса: 192.168.100.1, 192.168.100.2, 192.168.100.3
545
Ввод пароля
Ввод пароля
При подключении к оборудованию вручную, как правило, пароль также вводится
вручную.
Как правило, один и тот же пользователь использует одинаковый логин и пароль для
подключения к оборудованию.
И, как правило, будет достаточно запросить логин и пароль при старте скрипта, а
затем использовать их для подключения на разные устройства.
Модуль getpass
Модуль getpass позволяет запрашивать пароль, не отображая вводимые символы:
In [3]: print(password)
testpass
Переменные окружения
Еще один вариант хранения пароля (а можно и пользователя) - переменные
окружения.
546
Ввод пароля
$ export SSH_USER=user
$ export SSH_PASSWORD=userpass
import os
USERNAME = os.environ.get('SSH_USER')
PASSWORD = os.environ.get('SSH_PASSWORD')
547
Pexpect
Модуль pexpect
Модуль pexpect позволяет автоматизировать интерактивные подключения, такие как:
telnet
ssh
ftp
При этом сам pexpect не реализует различные утилиты, а использует уже готовые.
функция run()
класс spawn
pexpect.run()
Функция run() позволяет легко вызвать какую-то программу и вернуть её вывод.
Например:
548
Pexpect
In [3]: print(output)
b'total 44\r\n4 -rw-r--r-- 1 vagrant vagrant 3203 Jul 14 07:15 1_pexpect.py\r\n4 -rw-r
--r-- 1 vagrant vagrant 3393 Jul 14 07:15 2_telnetlib.py\r\n4 -rw-r--r-- 1 vagrant vag
rant 3452 Jul 14 07:15 3_paramiko.py\r\n4 -rw-r--r-- 1 vagrant vagrant 3127 Jul 14 07:
15 4_netmiko.py\r\n4 -rw-r--r-- 1 vagrant vagrant 718 Jul 14 07:15 4_netmiko_telnet.p
y\r\n4 -rw-r--r-- 1 vagrant vagrant 300 Jul 8 15:31 devices.yaml\r\n4 -rw-r--r-- 1 v
agrant vagrant 413 Jul 14 07:15 netmiko_function.py\r\n4 -rw-r--r-- 1 vagrant vagrant
876 Jul 14 07:15 netmiko_multiprocessing.py\r\n4 -rw-r--r-- 1 vagrant vagrant 1147 J
ul 14 07:15 netmiko_threading_data_list.py\r\n4 -rw-r--r-- 1 vagrant vagrant 1121 Jul
14 07:15 netmiko_threading_data.py\r\n4 -rw-r--r-- 1 vagrant vagrant 671 Jul 14 07:15
netmiko_threading.py\r\n'
In [4]: print(output.decode('utf-8'))
total 44
4 -rw-r--r-- 1 vagrant vagrant 3203 Jul 14 07:15 1_pexpect.py
4 -rw-r--r-- 1 vagrant vagrant 3393 Jul 14 07:15 2_telnetlib.py
4 -rw-r--r-- 1 vagrant vagrant 3452 Jul 14 07:15 3_paramiko.py
4 -rw-r--r-- 1 vagrant vagrant 3127 Jul 14 07:15 4_netmiko.py
4 -rw-r--r-- 1 vagrant vagrant 718 Jul 14 07:15 4_netmiko_telnet.py
4 -rw-r--r-- 1 vagrant vagrant 300 Jul 8 15:31 devices.yaml
4 -rw-r--r-- 1 vagrant vagrant 413 Jul 14 07:15 netmiko_function.py
4 -rw-r--r-- 1 vagrant vagrant 876 Jul 14 07:15 netmiko_multiprocessing.py
4 -rw-r--r-- 1 vagrant vagrant 1147 Jul 14 07:15 netmiko_threading_data_list.py
4 -rw-r--r-- 1 vagrant vagrant 1121 Jul 14 07:15 netmiko_threading_data.py
4 -rw-r--r-- 1 vagrant vagrant 671 Jul 14 07:15 netmiko_threading.py
pexpect.spawn
Класс spawn поддерживает больше возможностей. Он позволяет взаимодействовать с
вызванной программой, отправляя данные и ожидая ответ.
После выполнения этой строки, подключение готово. Теперь необходимо указать какую
строку ожидать. В данном случае, надо дождаться запроса о пароле:
In [6]: ssh.expect('[Pp]assword')
Out[6]: 0
549
Pexpect
Обратите внимание как описана строка, которую ожидает pexpect: [Pp]assword . Это
регулярное выражение, которое описывает строку password или Password. То есть,
методу expect можно передавать регулярное выражение как аргумент.
Метод expect вернул число 0 в результате работы. Это число указывает, что
совпадение было найдено и что это элемент с индексом ноль. Индекс тут фигурирует
из-за того, что expect можно передавать список строк. Например, можно передать
список с двумя элементами:
Обратите внимание, что теперь возвращается 1. Это значит, что совпадением было
слово Password.
In [9]: ssh.sendline('cisco')
Out[9]: 6
In [10]: ssh.expect('[>#]')
Out[10]: 0
In [11]: ssh.sendline('enable')
Out[11]: 7
In [12]: ssh.expect('[Pp]assword')
Out[12]: 0
In [13]: ssh.sendline('cisco')
Out[13]: 6
In [14]: ssh.expect('[>#]')
Out[14]: 0
550
Pexpect
После отправки команды, pexpect надо указать до какого момента считать вывод.
Указываем, что считать надо до #:
In [16]: ssh.expect('#')
Out[16]: 0
In [17]: ssh.before
Out[17]: b'sh ip int br\r\nInterface IP-Address OK? Method Statu
s Protocol\r\nEthernet0/0 192.168.100.1 YES NVRAM up
up \r\nEthernet0/1 192.168.200.1 YES NVRAM u
p up \r\nEthernet0/2 19.1.1.1 YES NVRAM
up up \r\nEthernet0/3 192.168.230.1 YES NVRA
M up up \r\nEthernet0/3.100 10.100.0.1 YES NV
RAM up up \r\nEthernet0/3.200 10.200.0.1 YES
NVRAM up up \r\nEthernet0/3.300 10.30.0.1 YE
S NVRAM up up \r\nR1'
In [19]: print(show_output)
sh ip int br
Interface IP-Address OK? Method Status Protocol
Ethernet0/0 192.168.100.1 YES NVRAM up up
Ethernet0/1 192.168.200.1 YES NVRAM up up
Ethernet0/2 19.1.1.1 YES NVRAM up up
Ethernet0/3 192.168.230.1 YES NVRAM up up
Ethernet0/3.100 10.100.0.1 YES NVRAM up up
Ethernet0/3.200 10.200.0.1 YES NVRAM up up
Ethernet0/3.300 10.30.0.1 YES NVRAM up up
R1
In [20]: ssh.close()
551
Pexpect
Для того, чтобы, например, команда ls -ls | grep SUMMARY отработала, нужно
запустить shell таким образом:
In [3]: p.expect(pexpect.EOF)
Out[3]: 0
In [4]: print(p.before)
b'4 -rw-r--r-- 1 vagrant vagrant 3203 Jul 14 07:15 1_pexpect.py\r\n'
In [5]: print(p.before.decode('utf-8'))
4 -rw-r--r-- 1 vagrant vagrant 3203 Jul 14 07:15 1_pexpect.py
pexpect.EOF
В предыдущем примере встретилось использование pexpect.EOF.
In [6]: p.expect('nattaur')
---------------------------------------------------------------------------
EOF Traceback (most recent call last)
<ipython-input-9-9c71777698c2> in <module>()
----> 1 p.expect('nattaur')
...
Метод pexpect.expect
В pexpect.expect как шаблон может использоваться:
552
Pexpect
регулярное выражение
EOF - этот шаблон позволяет среагировать на исключение EOF
TIMEOUT - исключение timeout (по умолчанию значение timeout = 30 секунд)
compiled re
Например:
553
Pexpect
import pexpect
import getpass
import sys
COMMAND = sys.argv[1]
USER = input('Username: ')
PASSWORD = getpass.getpass()
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print('Connection to device {}'.format(IP))
with pexpect.spawn('ssh {}@{}'.format(USER, IP)) as ssh:
ssh.expect('Password:')
ssh.sendline(PASSWORD)
ssh.expect('[#>]')
ssh.sendline('enable')
ssh.expect('Password:')
ssh.sendline(ENABLE_PASS)
ssh.expect('#')
ssh.sendline('terminal length 0')
ssh.expect('#')
ssh.sendline(COMMAND)
ssh.expect('#')
print(ssh.before.decode('utf-8'))
Комментарии с скрипту:
554
Pexpect
в expect
555
Pexpect
Обратите внимание, что, так как в последнем expect указано, что надо ожидать
подстроку # , метод before показал и команду, и имя хоста.
556
Telnetlib
Модуль telnetlib
Модуль telnetlib входит в стандартную библиотеку Python. Это реализация клиента
telnet.
Подключиться по telnet можно и используя pexpect. Плюс telnetlib в том, что этот
модуль входит в стандартную библиотеку Python.
С помощью метода read_until указывается до какой строки считать вывод. При этом,
как аргумент надо передавать не обычную строку, а байты:
In [2]: telnet.read_until(b'Username')
Out[2]: b'\r\n\r\nUser Access Verification\r\n\r\nUsername'
Для передачи данных используется метод write. Ему нужно передавать байтовую
строку:
In [3]: telnet.write(b'cisco\n')
In [4]: telnet.read_until(b'Password')
Out[4]: b': cisco\r\nPassword'
In [5]: telnet.write(b'cisco\n')
Теперь можно указать, что надо считать вывод до приглашения, а затем отправить
команду:
557
Telnetlib
In [6]: telnet.read_until(b'>')
Out[6]: b': \r\nR1>'
In [8]: telnet.read_until(b'>')
Out[8]: b'sh ip int br\r\nInterface IP-Address OK? Method Status
Protocol\r\nEthernet0/0 192.168.100.1 YES NVRAM up
up \r\nEthernet0/1 192.168.200.1 YES NVRAM up
up \r\nEthernet0/2 19.1.1.1 YES NVRAM
up up \r\nEthernet0/3 192.168.230.1 YES NVRAM
up up \r\nEthernet0/3.100 10.100.0.1 YES NVR
AM up up \r\nEthernet0/3.200 10.200.0.1 YES N
VRAM up up \r\nEthernet0/3.300 10.30.0.1 YES
NVRAM up up \r\nR1>'
558
Telnetlib
In [13]: print(all_result)
sh arp
Protocol Address Age (min) Hardware Addr Type Interface
Internet 10.30.0.1 - aabb.cc00.6530 ARPA Ethernet0/3.300
Internet 10.100.0.1 - aabb.cc00.6530 ARPA Ethernet0/3.100
Internet 10.200.0.1 - aabb.cc00.6530 ARPA Ethernet0/3.200
Internet 19.1.1.1 - aabb.cc00.6520 ARPA Ethernet0/2
Internet 192.168.100.1 - aabb.cc00.6500 ARPA Ethernet0/0
Internet 192.168.100.2 124 aabb.cc00.6600 ARPA Ethernet0/0
Internet 192.168.100.3 143 aabb.cc00.6700 ARPA Ethernet0/0
Internet 192.168.100.100 160 aabb.cc80.c900 ARPA Ethernet0/0
Internet 192.168.200.1 - 0203.e800.6510 ARPA Ethernet0/1
Internet 192.168.200.100 13 0800.27ac.16db ARPA Ethernet0/1
Internet 192.168.230.1 - aabb.cc00.6530 ARPA Ethernet0/3
R1>sh clock
*19:18:57.980 UTC Fri Nov 3 2017
R1>sh ip int br
Interface IP-Address OK? Method Status Protocol
Ethernet0/0 192.168.100.1 YES NVRAM up up
Ethernet0/1 192.168.200.1 YES NVRAM up up
Ethernet0/2 19.1.1.1 YES NVRAM up up
Ethernet0/3 192.168.230.1 YES NVRAM up up
Ethernet0/3.100 10.100.0.1 YES NVRAM up up
Ethernet0/3.200 10.200.0.1 YES NVRAM up up
Ethernet0/3.300 10.30.0.1 YES NVRAM up up
R1>
559
Telnetlib
In [17]: telnet.read_until(b'>')
Out[17]: b'sh arp\r\nProtocol Address Age (min) Hardware Addr Type Inte
rface\r\nInternet 10.30.0.1 - aabb.cc00.6530 ARPA Ethernet0/3.300\
r\nInternet 10.100.0.1 - aabb.cc00.6530 ARPA Ethernet0/3.100\r\nInt
ernet 10.200.0.1 - aabb.cc00.6530 ARPA Ethernet0/3.200\r\nInternet
19.1.1.1 - aabb.cc00.6520 ARPA Ethernet0/2\r\nInternet 192.168.1
00.1 - aabb.cc00.6500 ARPA Ethernet0/0\r\nInternet 192.168.100.2
126 aabb.cc00.6600 ARPA Ethernet0/0\r\nInternet 192.168.100.3 145 a
abb.cc00.6700 ARPA Ethernet0/0\r\nInternet 192.168.100.100 162 aabb.cc80.c
900 ARPA Ethernet0/0\r\nInternet 192.168.200.1 - 0203.e800.6510 ARPA
Ethernet0/1\r\nInternet 192.168.200.100 15 0800.27ac.16db ARPA Ethernet
0/1\r\nInternet 192.168.230.1 - aabb.cc00.6530 ARPA Ethernet0/3\r\nR1>'
In [18]: telnet.read_until(b'>')
Out[18]: b'sh clock\r\n*19:20:39.388 UTC Fri Nov 3 2017\r\nR1>'
In [19]: telnet.read_until(b'>')
Out[19]: b'sh ip int br\r\nInterface IP-Address OK? Method Statu
s Protocol\r\nEthernet0/0 192.168.100.1 YES NVRAM up
up \r\nEthernet0/1 192.168.200.1 YES NVRAM u
p up \r\nEthernet0/2 19.1.1.1 YES NVRAM
up up \r\nEthernet0/3 192.168.230.1 YES NVRA
M up up \r\nEthernet0/3.100 10.100.0.1 YES NV
RAM up up \r\nEthernet0/3.200 10.200.0.1 YES
NVRAM up up \r\nEthernet0/3.300 10.30.0.1 YE
S NVRAM up up \r\nR1>'
560
Telnetlib
In [21]: telnet.read_very_eager()
Out[21]: b''
In [23]: telnet.expect([b'[>#]'])
Out[23]:
(0,
<_sre.SRE_Match object; span=(46, 47), match=b'>'>,
b'sh clock\r\n*19:35:10.984 UTC Fri Nov 3 2017\r\nR1>')
561
Telnetlib
In [26]: regex_idx
Out[26]: 0
In [27]: match.group()
Out[27]: b'>'
In [28]: match
Out[28]: <_sre.SRE_Match object; span=(46, 47), match=b'>'>
In [29]: match.group()
Out[29]: b'>'
In [30]: output
Out[30]: b'sh clock\r\n*19:37:21.577 UTC Fri Nov 3 2017\r\nR1>'
In [31]: output.decode('utf-8')
Out[31]: 'sh clock\r\n*19:37:21.577 UTC Fri Nov 3 2017\r\nR1>'
In [32]: telnet.close()
562
Telnetlib
Файл 2_telnetlib.py:
import telnetlib
import time
import getpass
import sys
COMMAND = sys.argv[1].encode('utf-8')
USER = input('Username: ').encode('utf-8')
PASSWORD = getpass.getpass().encode('utf-8')
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ').encode('utf-8')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print('Connection to device {}'.format(IP))
with telnetlib.Telnet(IP) as t:
t.read_until(b'Username:')
t.write(USER + b'\n')
t.read_until(b'Password:')
t.write(PASSWORD + b'\n')
t.write(b'enable\n')
t.read_until(b'Password:')
t.write(ENABLE_PASS + b'\n')
t.write(b'terminal length 0\n')
t.write(COMMAND + b'\n')
time.sleep(5)
output = t.read_very_eager().decode('utf-8')
print(output)
563
Telnetlib
Выполнение скрипта:
564
Telnetlib
R1#terminal length 0
R1#sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.1 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.1.10.1 YES manual up up
FastEthernet0/1.20 10.1.20.1 YES manual up up
FastEthernet0/1.30 10.1.30.1 YES manual up up
FastEthernet0/1.40 10.1.40.1 YES manual up up
FastEthernet0/1.50 10.1.50.1 YES manual up up
FastEthernet0/1.60 10.1.60.1 YES manual up up
FastEthernet0/1.70 10.1.70.1 YES manual up up
R1#
Connection to device 192.168.100.2
R2#terminal length 0
R2#sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.2 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.2.10.1 YES manual up up
FastEthernet0/1.20 10.2.20.1 YES manual up up
FastEthernet0/1.30 10.2.30.1 YES manual up up
FastEthernet0/1.40 10.2.40.1 YES manual up up
FastEthernet0/1.50 10.2.50.1 YES manual up up
FastEthernet0/1.60 10.2.60.1 YES manual up up
FastEthernet0/1.70 10.2.70.1 YES manual up up
R2#
Connection to device 192.168.100.3
R3#terminal length 0
R3#sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.3 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.3.10.1 YES manual up up
FastEthernet0/1.20 10.3.20.1 YES manual up up
FastEthernet0/1.30 10.3.30.1 YES manual up up
FastEthernet0/1.40 10.3.40.1 YES manual up up
FastEthernet0/1.50 10.3.50.1 YES manual up up
FastEthernet0/1.60 10.3.60.1 YES manual up up
FastEthernet0/1.70 10.3.70.1 YES manual up up
R3#
565
Telnetlib
566
Paramiko
Модуль paramiko
Paramiko - это реализация протокола SSHv2 на Python. Paramiko предоставляет
функциональность клиента и сервера. Мы будем рассматривать только
функциональность клиента.
Так как Paramiko не входит в стандартную библиотеку модулей Python, его нужно
установить:
import paramiko
import getpass
import sys
import time
COMMAND = sys.argv[1]
USER = input('Username: ')
PASSWORD = getpass.getpass()
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print('Connection to device {}'.format( IP ))
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.send(COMMAND + '\n')
time.sleep(2)
result = ssh.recv(5000).decode('utf-8')
print(result)
567
Paramiko
Комментарии к скрипту:
client = paramiko.SSHClient()
аутентифицирует подключение
hostname - имя хоста или IP-адрес
password - пароль
ОС. Это нужно при работе с ключами, а так как в данном случае
аутентификация выполняется по логину/паролю, это нужно отключить.
with client.invoke_shell() as ssh
568
Paramiko
R1>enable
Password:
R1#terminal length 0
R1#
sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.1 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.1.10.1 YES manual up up
FastEthernet0/1.20 10.1.20.1 YES manual up up
FastEthernet0/1.30 10.1.30.1 YES manual up up
FastEthernet0/1.40 10.1.40.1 YES manual up up
FastEthernet0/1.50 10.1.50.1 YES manual up up
FastEthernet0/1.60 10.1.60.1 YES manual up up
FastEthernet0/1.70 10.1.70.1 YES manual up up
R1#
Connection to device 192.168.100.2
R2>enable
Password:
R2#terminal length 0
R2#
sh ip int br
FastEthernet0/0 192.168.100.2 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.2.10.1 YES manual up up
FastEthernet0/1.20 10.2.20.1 YES manual up up
FastEthernet0/1.30 10.2.30.1 YES manual up up
FastEthernet0/1.40 10.2.40.1 YES manual up up
FastEthernet0/1.50 10.2.50.1 YES manual up up
FastEthernet0/1.60 10.2.60.1 YES manual up up
FastEthernet0/1.70 10.2.70.1 YES manual up up
R2#
Connection to device 192.168.100.3
R3>enable
Password:
R3#terminal length 0
R3#
sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.3 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.3.10.1 YES manual up up
FastEthernet0/1.20 10.3.20.1 YES manual up up
FastEthernet0/1.30 10.3.30.1 YES manual up up
FastEthernet0/1.40 10.3.40.1 YES manual up up
FastEthernet0/1.50 10.3.50.1 YES manual up up
569
Paramiko
Обратите внимание, что в вывод попал и процесс ввода пароля enable, и команда
terminal length.
Это связано с тем, что paramiko собирает весь вывод в буфер. И, при вызове метода
recv (например, ssh.recv(1000) ), paramiko возвращает всё, что есть в буфере. После
Поэтому, если нужно получить только вывод команды sh ip int br, то надо оставить
recv , но не делать print:
ssh.send('enable\n')
ssh.send(ENABLE_PASS + '\n')
time.sleep(1)
ssh.send(COMMAND + '\n')
time.sleep(3)
result = ssh.recv(5000).decode('utf-8')
print(result)
570
Netmiko
Модуль netmiko
Netmiko - это модуль, который позволяет упростить использование paramiko для
сетевых устройств.
import getpass
import sys
COMMAND = sys.argv[1]
USER = input('Username: ')
PASSWORD = getpass.getpass()
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print('Connection to device {}'.format(IP))
DEVICE_PARAMS = {'device_type': 'cisco_ios',
'ip': IP,
'username': USER,
'password': PASSWORD,
'secret': ENABLE_PASS}
result = ssh.send_command(COMMAND)
print(result)
571
Netmiko
В этом примере не передается команда terminal length, так как netmiko по умолчанию
выполняет эту команду.
572
Netmiko
В выводе нет никаких лишних приглашений, только вывод команды sh ip int br.
Так как netmiko наиболее удобный модуль для подключения к сетевому оборудования,
разберемся с ним подробней.
573
Netmiko
Arista vEOS
Cisco ASA
Cisco IOS
Cisco IOS-XR
Cisco SG300
HP Comware7
HP ProCurve
Juniper Junos
Linux
и другие
Подключение по SSH
ssh = ConnectHandler(**cisco_router)
Режим enable
Перейти в режим enable:
574
Netmiko
ssh.enable()
ssh.exit_enable_mode()
Отправка команд
В netmiko есть несколько способов отправки команд:
send_config_set )
send_command
Метод send_command позволяет отправить одну команду на устройство.
Например:
575
Netmiko
строки
max_loops - количество итераций, до того как метод выдаст ошибку
(исключение). По умолчанию 500
strip_prompt - удалить приглашение из вывода. По умолчанию удаляется
send_config_set
Метод send_config_set позволяет отправить команду или несколько команд
конфигурационного режима.
Пример использования:
result = ssh.send_config_set(commands)
send_config_from_file
Метод send_config_from_file отправляет команды из указанного файла в
конфигурационный режим.
Пример использования:
result = ssh.send_config_from_file('config_ospf.txt')
Дополнительные методы
576
Netmiko
ssh.config_mode()
ssh.exit_config_mode()
ssh.find_prompt()
ssh.commit()
Telnet
С версии 1.0.0 netmiko поддерживает подключения по Telnet, пока что только для Cisco
IOS устройств.
Внутри netmiko использует telnetlib для подключения по Telnet. Но, при этом,
предоставляет тот же интерфейс для работы, что и подключение по SSH.
577
Netmiko
import getpass
import sys
import time
COMMAND = sys.argv[1]
USER = input('Username: ')
PASSWORD = getpass.getpass()
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print('Connection to device {}'.format(IP))
DEVICE_PARAMS = {'device_type': 'cisco_ios_telnet',
'ip': IP,
'username':USER,
'password':PASSWORD,
'secret':ENABLE_PASS,
'verbose': True}
with ConnectHandler(**DEVICE_PARAMS) as telnet:
telnet.enable()
result = telnet.send_command(COMMAND)
print(result)
send_command_timing()
find_prompt()
send_config_set()
send_config_from_file()
check_enable_mode()
disconnect()
578
Дополнительные материалы
Дополнительные материалы
Документация:
pexpect
telnetlib
paramiko Client
paramiko Channel
netmiko
threading
multiprocessing
queue
time
datetime
getpass
Статьи:
Netmiko Library
Automate SSH connections with netmiko
Network Automation Using Python: BGP Configuration
579
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 19.1
Создать функцию send_show_command.
Параметры функции:
ключ - IP устройства
значение - результат выполнения команды
Отправить команду command на все устройства из файла devices.yaml (для этого надо
считать информацию из файла) с помощью функции send_show_command.
Задание 19.1a
Переделать функцию send_show_command из задания 19.1 таким образом, чтобы
обрабатывалось исключение, которое генерируется при ошибке аутентификации на
устройстве.
580
Задания
Задание 19.1b
Дополнить функцию send_show_command из задания 19.1a таким образом, чтобы
обрабатывалось не только исключение, которое генерируется при ошибке
аутентификации на устройстве, но и исключение, которое генерируется, когда IP-адрес
устройства недоступен.
Задание 19.2
Создать функцию send_config_commands
Параметры функции:
ключ - IP устройства
значение - вывод с выполнением команд
Задание 19.2a
Дополнить функцию send_config_commands из задания 19.2
581
Задания
Задание 19.2b
В этом задании необходимо переделать функцию send_config_commands из задания
19.2a или 19.2 и добавить проверку на ошибки.
Если при выполнении какой-то из команд возникла ошибка, функция должна выводить
сообщение на стандартный поток вывода с информацией о том, какая ошибка
возникла, при выполнении какой команды и на каком устройстве.
При этом, параметр verbose также должен работать, но теперь он отвечает за вывод
только тех команд, которые выполнились корректно.
ключ - команда
значение - вывод с выполнением команд
R1(config)#logging 0255.255.1
^
% Invalid input detected at '^' marker.
R1(config)#logging
% Incomplete command.
R1(config)#i
% Ambiguous command: "i"
582
Задания
commands = commands_with_errors+correct_commands
Задание 19.2c
Переделать функцию send_config_commands из задания 19.2b
ключ - команда
значение - вывод с выполнением команд
Задание 19.2d
В этом задании надо создать функцию send_cfg_to_devices, которая выполняет
команды на нескольких устройствах последовательно и при этом выполняет проверку
на ошибки в командах.
Параметры функции:
583
Задания
Если при выполнении какой-то из команд возникла ошибка, функция должна выводить
сообщение на стандартный поток вывода с информацией о том, какая ошибка
возникла, при выполнении какой команды и на каком устройстве.
ключ - IP устройства
значение - вложенный словарь:
ключ - команда
значение - вывод с выполнением команд
commands = commands_with_errors+correct_commands
Задание 19.3
Создать функцию send_commands (для подключения по SSH используется netmiko).
Параметры функции:
584
Задания
ключ - IP устройства
значение - вывод с выполнением команд
Задание 19.4
Создать функцию send_commands_to_devices (для подключения по SSH используется
netmiko).
Параметры функции:
585
Задания
Задание 19.4a
Дополнить функцию send_commands_to_devices таким образом, чтобы перед
подключением к устройствам по SSH, выполнялась проверка доступности устройства
pingом (можно вызвать команду ping в ОС).
Как выполнять команды ОС, описано в разделе subprocess. Там же есть пример
функции с отправкой ping.
Для удобства можно сделать отдельную функцию для проверки доступности и затем
использовать ее в функции send_commands_to_devices.
586
20. Одновременное подключение к нескольким устройствам
Одновременное подключение к
нескольким устройствам
Когда нужно опросить много устройств, выполнение подключений поочередно будет
достаточно долгим. Конечно, это будет быстрее, чем подключение вручную, но
хотелось бы получать отклик как можно быстрее.
threading
multiprocessing
587
Измерение времени выполнения скрипта
При оценке времени выполнения скрипта в данном случае не важна высокая точность.
Главное - сравнить время выполнения скрипта в разных вариантах.
time
Утилита time в Linux позволяет замерить время выполнения скрипта. Например:
Для использования утилиты time достаточно написать time перед строкой запуска
скрипта.
datetime
Второй вариант - модуль datetime. Этот модуль позволяет работать со временем и
датами в Python.
Пример использования:
588
Измерение времени выполнения скрипта
start_time = datetime.now()
print(datetime.now() - start_time)
Результат выполнения:
$ python test.py
0:00:05.004949
589
Процессы и потоки в CPython
Флаг передается либо каждые сколько-то инструкций Python, либо, например, когда
выполняются какие-то операции ввода-вывода.
Но не всё так плохо. Если в программе есть некое "ожидание": пакетов из сети,
запроса пользователя, пауза типа sleep - то в такой программе потоки будут
выполняться как будто параллельно. А всё потому, что во время таких пауз флаг (GIL)
можно передать другому потоку.
Но тут также нужно быть осторожным, так как такой результат может наблюдаться на
небольшом количестве сессий, но может ухудшиться с ростом количества сессий.
Процессы
590
Процессы и потоки в CPython
Процессы позволяют выполнять задачи на разных ядрах компьютера. Это важно для
задач, которые завязаны на операции ввода-вывода.
Для каждого процесса создается своя копия ресурсов, выделяется память, у каждого
процесса свой GIL. Это же делает процессы более тяжеловесными, по сравнению с
потоками.
591
Модуль concurrent.futures
Модуль concurrent.futures
Модуль concurrent.futures предоставляет высокоуровневый интерфейс для работы с
процессами и потоками. При этом и для потоков, и для процессов используется
одинаковый интерфейс, что позволяет легко переключаться между ними.
592
Модуль concurrent.futures
Метод map
Метод map - это самый простой вариант работы с concurrent.futures.
import yaml
from netmiko import ConnectHandler
if __name__ == '__main__':
devices = yaml.load(open('devices.yaml'))
all_done = threads_conn(connect_ssh, devices['routers'])
pprint(all_done)
Это первый ограниченный пример, так как сейчас в функции connect_ssh команда
задана вручную, а не передается как аргумент. В следующей версии это будет
исправлено.
593
Модуль concurrent.futures
тут функция function вызывается в разных потоках. При этом в разных потоках
функция будет вызываться с разными аргументами - элементами итерируемого
объекта devices.
метод map возвращает генератор. В этом генераторе содержатся результаты
выполнения функций
Обратите внимание, что функция занимает всего 4 строки, и для получения данных не
надо создавать очередь и передавать ее в функцию connect_ssh.
Результат выполнения:
$ python netmiko_threads_map_ver1.py
Connection to device: 192.168.100.1
Connection to device: 192.168.100.2
Connection to device: 192.168.100.3
[{'192.168.100.1': '*04:43:01.629 UTC Mon Aug 28 2017'},
{'192.168.100.2': '*04:43:01.648 UTC Mon Aug 28 2017'},
{'192.168.100.3': '*04:43:07.291 UTC Mon Aug 28 2017'}]
594
Модуль concurrent.futures
import yaml
from netmiko import ConnectHandler
if __name__ == '__main__':
devices = yaml.load(open('devices.yaml'))
all_done = threads_conn(connect_ssh, devices['routers'])
pprint(all_done)
Результат выполнения:
$ python netmiko_threads_map_ver2.py
===> 04:50:50.175076 Connection to device: 192.168.100.1
===> 04:50:50.175553 Connection to device: 192.168.100.2
<=== 04:50:55.582707 Received result from device: 192.168.100.2
===> 04:50:55.689248 Connection to device: 192.168.100.3
<=== 04:51:01.135640 Received result from device: 192.168.100.3
<=== 04:51:05.568037 Received result from device: 192.168.100.1
[{'192.168.100.1': '*04:51:05.395 UTC Mon Aug 28 2017'},
{'192.168.100.2': '*04:50:55.411 UTC Mon Aug 28 2017'},
{'192.168.100.3': '*04:51:00.964 UTC Mon Aug 28 2017'}]
595
Модуль concurrent.futures
Еще один момент, который тут хорошо заметен, это то, что как только одна задача
выполнилась, сразу берется следующая. То есть, ограничение в два потока влияет на
количество потоков, которые выполняются одновременно.
Теперь осталось изменить функцию таким образом, чтобы ей можно было передавать
команду как аргумент.
596
Модуль concurrent.futures
import yaml
from netmiko import ConnectHandler
if __name__ == '__main__':
devices = yaml.load(open('devices.yaml'))
all_done = threads_conn(connect_ssh,
devices['routers'],
command='sh clock')
pprint(all_done)
Функция repeat тут нужна для того, чтобы команда передавалась при каждом вызове
функции connect_ssh.
Результат выполнения:
597
Модуль concurrent.futures
$ python netmiko_threads_map_final.py
===> 05:01:08.314962 Connection to device: 192.168.100.1
===> 05:01:08.315114 Connection to device: 192.168.100.2
<=== 05:01:13.693083 Received result from device: 192.168.100.2
===> 05:01:13.799002 Connection to device: 192.168.100.3
<=== 05:01:19.363250 Received result from device: 192.168.100.3
<=== 05:01:23.685859 Received result from device: 192.168.100.1
[{'192.168.100.1': '*05:01:23.513 UTC Mon Aug 28 2017'},
{'192.168.100.2': '*05:01:13.522 UTC Mon Aug 28 2017'},
{'192.168.100.3': '*05:01:19.189 UTC Mon Aug 28 2017'}]
598
Модуль concurrent.futures
import yaml
from netmiko import ConnectHandler
if __name__ == '__main__':
devices = yaml.load(open('devices.yaml'))
all_done = threads_conn(connect_ssh,
devices['routers'],
command='sh clock')
pprint(all_done)
Результат выполнения:
599
Модуль concurrent.futures
$ python netmiko_processes_map_final.py
===> 05:26:42.974505 Connection to device: 192.168.100.1
===> 05:26:42.975733 Connection to device: 192.168.100.2
<=== 05:26:48.389420 Received result from device: 192.168.100.2
===> 05:26:48.495598 Connection to device: 192.168.100.3
<=== 05:26:54.104585 Received result from device: 192.168.100.3
<=== 05:26:58.367981 Received result from device: 192.168.100.1
[{'192.168.100.1': '*05:26:58.195 UTC Mon Aug 28 2017'},
{'192.168.100.2': '*05:26:48.218 UTC Mon Aug 28 2017'},
{'192.168.100.3': '*05:26:53.932 UTC Mon Aug 28 2017'}]
600
Модуль concurrent.futures
601
Модуль concurrent.futures
import yaml
from netmiko import ConnectHandler
if __name__ == '__main__':
devices = yaml.load(open('devices.yaml'))
all_done = threads_conn(connect_ssh,
devices['routers'],
command='sh clock')
pprint(all_done)
602
Модуль concurrent.futures
Результат выполнения:
$ python netmiko_threads_submit.py
===> 06:02:14.582011 Connection to device: 192.168.100.1
===> 06:02:14.582155 Connection to device: 192.168.100.2
<=== 06:02:20.155865 Received result from device: 192.168.100.2
===> 06:02:20.262584 Connection to device: 192.168.100.3
<=== 06:02:25.864270 Received result from device: 192.168.100.3
<=== 06:02:29.962225 Received result from device: 192.168.100.1
[{'192.168.100.2': '*06:02:19.983 UTC Mon Aug 28 2017'},
{'192.168.100.3': '*06:02:25.691 UTC Mon Aug 28 2017'},
{'192.168.100.1': '*06:02:29.789 UTC Mon Aug 28 2017'}]
Future
Чтобы посмотреть на future, в скрипт добавлены несколько строк с выводом
информации (netmiko_threads_submit_verbose.py):
603
Модуль concurrent.futures
import yaml
from netmiko import ConnectHandler
if __name__ == '__main__':
devices = yaml.load(open('devices.yaml'))
all_done = threads_conn(connect_ssh,
devices['routers'],
command='sh clock')
pprint(all_done)
Результат выполнения:
604
Модуль concurrent.futures
$ python netmiko_threads_submit_verbose.py
===> 06:16:56.059256 Connection to device: 192.168.100.1
Future: <Future at 0xb68427cc state=running> for device 192.168.100.1
===> 06:16:56.059434 Connection to device: 192.168.100.2
Future: <Future at 0xb68483ac state=running> for device 192.168.100.2
Future: <Future at 0xb6848b4c state=pending> for device 192.168.100.3
<=== 06:17:01.482761 Received result from device: 192.168.100.2
===> 06:17:01.589605 Connection to device: 192.168.100.3
Future done <Future at 0xb68483ac state=finished returned dict>
<=== 06:17:07.226815 Received result from device: 192.168.100.3
Future done <Future at 0xb6848b4c state=finished returned dict>
<=== 06:17:11.444831 Received result from device: 192.168.100.1
Future done <Future at 0xb68427cc state=finished returned dict>
{'192.168.100.1': '*06:17:11.273 UTC Mon Aug 28 2017',
'192.168.100.2': '*06:17:01.310 UTC Mon Aug 28 2017',
'192.168.100.3': '*06:17:07.055 UTC Mon Aug 28 2017'}
Так как по умолчанию используется ограничение в два потока, только два из трех future
показывают статус running. Третий находится в состоянии pending и ждет, пока до него
дойдет очередь.
Обработка исключений
Если при выполнении функции возникло исключение, оно будет сгенерировано при
получении результата
$ python netmiko_threads_submit.py
===> 06:29:40.871851 Connection to device: 192.168.100.1
===> 06:29:40.872888 Connection to device: 192.168.100.2
===> 06:29:43.571296 Connection to device: 192.168.100.3
<=== 06:29:48.921702 Received result from device: 192.168.100.3
<=== 06:29:56.269284 Received result from device: 192.168.100.1
Traceback (most recent call last):
File "/home/vagrant/venv/py3_convert/lib/python3.6/site-packages/netmiko/base_connec
tion.py", line 491, in establish_connection
self.remote_conn_pre.connect(**ssh_connect_params)
File "/home/vagrant/venv/py3_convert/lib/python3.6/site-packages/paramiko/client.py"
, line 394, in connect
look_for_keys, gss_auth, gss_kex, gss_deleg_creds, gss_host)
File "/home/vagrant/venv/py3_convert/lib/python3.6/site-packages/paramiko/client.py"
, line 649, in _auth
raise saved_exception
File "/home/vagrant/venv/py3_convert/lib/python3.6/site-packages/paramiko/client.py"
, line 636, in _auth
self._transport.auth_password(username, password)
605
Модуль concurrent.futures
File "/home/vagrant/venv/py3_convert/lib/python3.6/site-packages/paramiko/transport.
py", line 1329, in auth_password
return self.auth_handler.wait_for_response(my_event)
File "/home/vagrant/venv/py3_convert/lib/python3.6/site-packages/paramiko/auth_handl
er.py", line 217, in wait_for_response
raise e
paramiko.ssh_exception.AuthenticationException: Authentication failed.
Так как исключение возникает при получении результата, легко добавить обработку
исключений (файл netmiko_threads_submit_exception.py):
606
Модуль concurrent.futures
import yaml
from netmiko import ConnectHandler
from netmiko.ssh_exception import NetMikoAuthenticationException
if __name__ == '__main__':
devices = yaml.load(open('devices.yaml'))
all_done = threads_conn(connect_ssh,
devices['routers'],
command='sh clock')
pprint(all_done)
Результат выполнения:
607
Модуль concurrent.futures
$ python netmiko_threads_submit_exception.py
===> 06:45:56.327892 Connection to device: 192.168.100.1
===> 06:45:56.328190 Connection to device: 192.168.100.2
===> 06:45:58.964806 Connection to device: 192.168.100.3
Authentication failure: unable to connect cisco_ios 192.168.100.2:22
Authentication failed.
<=== 06:46:04.325812 Received result from device: 192.168.100.3
<=== 06:46:11.731541 Received result from device: 192.168.100.1
{'192.168.100.1': '*06:46:11.556 UTC Mon Aug 28 2017',
'192.168.100.3': '*06:46:04.154 UTC Mon Aug 28 2017'}
ProcessPoolExecutor
Так как все работает аналогичным образом и для процессов, тут приведет последний
вариант (файл netmiko_processes_submit_exception.py):
608
Модуль concurrent.futures
import yaml
from netmiko import ConnectHandler
from netmiko.ssh_exception import NetMikoAuthenticationException
if __name__ == '__main__':
devices = yaml.load(open('devices.yaml'))
all_done = processes_conn(connect_ssh,
devices['routers'],
command='sh clock')
pprint(all_done)
Результат выполнения:
609
Модуль concurrent.futures
$ python netmiko_processes_submit_exception.py
===> 06:40:43.828249 Connection to device: 192.168.100.1
===> 06:40:43.828664 Connection to device: 192.168.100.2
Authentication failure: unable to connect cisco_ios 192.168.100.2:22
Authentication failed.
===> 06:40:46.292613 Connection to device: 192.168.100.3
<=== 06:40:51.890816 Received result from device: 192.168.100.3
<=== 06:40:59.231330 Received result from device: 192.168.100.1
{'192.168.100.1': '*06:40:59.056 UTC Mon Aug 28 2017',
'192.168.100.3': '*06:40:51.719 UTC Mon Aug 28 2017'}
610
Дополнительные материалы
Дополнительные материалы
GIL
Can’t we get rid of the Global Interpreter Lock?
GIL (на русском)
Understanding the Python GIL
Python threads and the GIL
concurrent.futures
Документация Python:
Статьи:
611
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 20.1
Переделать задание 19.4a таким образом, чтобы проверка доступности устройств
выполнялась не последовательно, а параллельно.
Задание 20.1a
Переделать функцию из задания 20.1 таким образом, чтобы она позволяла
контролировать количество параллельных проверок IP.
Функция должна проверять адреса из списка таким образом, чтобы в любой момент
времени максимальное количество параллельных проверок было равным limit.
Задание 20.2
Создать функцию send_commands_threads, которая запускает функцию
send_commands из задания 19.3 на разных устройствах в параллельных потоках.
612
Задания
ключ - IP устройства
значение - вывод с выполнением команд
Задание 20.2a
Переделать функцию send_commands_threads из задания 20.2 таким образом, чтобы с
помощью аргумента limit, можно было указывать сколько подключений будут
выполняться параллельно.
613
21. Шаблоны конфигураций с Jinja2
Примеры использования:
Идея Jinja очень проста: разделение данных и шаблона. Это позволяет использовать
один и тот же шаблон, но подставлять в него разные данные.
В самом простом случае шаблон - это просто текстовый файл, в котором указаны
места подстановки значений с помощью переменных Jinja.
hostname {{name}}
!
interface Loopback255
description Management loopback
ip address 10.255.{{id}}.1 255.255.255.255
!
interface GigabitEthernet0/0
description LAN to {{name}} sw1 {{int}}
ip address {{ip}} 255.255.255.0
!
router ospf 10
router-id 10.255.{{id}}.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
614
21. Шаблоны конфигураций с Jinja2
Комментарии к шаблону:
template = Template('''
hostname {{name}}
!
interface Loopback255
description Management loopback
ip address 10.255.{{id}}.1 255.255.255.255
!
interface GigabitEthernet0/0
description LAN to {{name}} sw1 {{int}}
ip address {{ip}} 255.255.255.0
!
router ospf 10
router-id 10.255.{{id}}.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
''')
print(template.render(liverpool))
615
21. Шаблоны конфигураций с Jinja2
$ python basic_generator.py
hostname Liverpool
!
interface Loopback255
description Management loopback
ip address 10.255.11.1 255.255.255.255
!
interface GigabitEthernet0/0
description LAN to Liverpool sw1 Gi1/0/17
ip address 10.1.1.10 255.255.255.0
!
router ospf 10
router-id 10.255.11.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
616
Пример использования Jinja2
router_template.py - шаблон
routers_info.yml - в этом файле в виде списка словарей (в формате YAML)
находится информация о маршрутизаторах, для которых нужно сгенерировать
конфигурационный файл
router_config_generator.py - в этом скрипте импортируется файл с шаблоном и
считывается информация из файла в формате YAML, а затем генерируются
конфигурационные файлы маршрутизаторов
Файл router_template.py
617
Пример использования Jinja2
template_r1 = Template('''
hostname {{name}}
!
interface Loopback10
description MPLS loopback
ip address 10.10.{{id}}.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to {{name}} sw1 G0/1
!
interface GigabitEthernet0/0.1{{id}}1
description MPLS to {{to_name}}
encapsulation dot1Q 1{{id}}1
ip address 10.{{id}}.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN {{name}} to sw1 G0/2 !
interface GigabitEthernet0/1.{{IT}}
description PW IT {{name}} - {{to_name}}
encapsulation dot1Q {{IT}}
xconnect 10.10.{{to_id}}.1 {{id}}11 encapsulation mpls
backup peer 10.10.{{to_id}}.2 {{id}}21
backup delay 1 1
!
interface GigabitEthernet0/1.{{BS}}
description PW BS {{name}} - {{to_name}}
encapsulation dot1Q {{BS}}
xconnect 10.10.{{to_id}}.1 {{to_id}}{{id}}11 encapsulation mpls
backup peer 10.10.{{to_id}}.2 {{to_id}}{{id}}21
backup delay 1 1
!
router ospf 10
router-id 10.10.{{id}}.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
''')
Файл routers_info.yml
618
Пример использования Jinja2
- id: 11
name: Liverpool
to_name: LONDON
IT: 791
BS: 1550
to_id: 1
- id: 12
name: Bristol
to_name: LONDON
IT: 793
BS: 1510
to_id: 1
- id: 14
name: Coventry
to_name: Manchester
IT: 892
BS: 1650
to_id: 2
Файл router_config_generator.py
routers = yaml.load(open('routers_info.yml'))
Файл router_config_generator.py:
619
Пример использования Jinja2
$ python router_config_generator.py
Liverpool_r1.txt
Bristol_r1.txt
Coventry_r1.txt
620
Пример использования Jinja2
hostname Liverpool
!
interface Loopback10
description MPLS loopback
ip address 10.10.11.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to Liverpool sw1 G0/1
!
interface GigabitEthernet0/0.1111
description MPLS to LONDON
encapsulation dot1Q 1111
ip address 10.11.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN Liverpool to sw1 G0/2
!
interface GigabitEthernet0/1.791
description PW IT Liverpool - LONDON
encapsulation dot1Q 791
xconnect 10.10.1.1 1111 encapsulation mpls
backup peer 10.10.1.2 1121
backup delay 1 1
!
interface GigabitEthernet0/1.1550
description PW BS Liverpool - LONDON
encapsulation dot1Q 1550
xconnect 10.10.1.1 11111 encapsulation mpls
backup peer 10.10.1.2 11121
backup delay 1 1
!
router ospf 10
router-id 10.10.11.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
621
Пример использования Jinja2
hostname Bristol
!
interface Loopback10
description MPLS loopback
ip address 10.10.12.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to Bristol sw1 G0/1
!
interface GigabitEthernet0/0.1121
description MPLS to LONDON
encapsulation dot1Q 1121
ip address 10.12.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN Bristol to sw1 G0/2
!
interface GigabitEthernet0/1.793
description PW IT Bristol - LONDON
encapsulation dot1Q 793
xconnect 10.10.1.1 1211 encapsulation mpls
backup peer 10.10.1.2 1221
backup delay 1 1
!
interface GigabitEthernet0/1.1510
description PW BS Bristol - LONDON
encapsulation dot1Q 1510
xconnect 10.10.1.1 11211 encapsulation mpls
backup peer 10.10.1.2 11221
backup delay 1 1
!
router ospf 10
router-id 10.10.12.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
622
Пример использования Jinja2
hostname Coventry
!
interface Loopback10
description MPLS loopback
ip address 10.10.14.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to Coventry sw1 G0/1
!
interface GigabitEthernet0/0.1141
description MPLS to Manchester
encapsulation dot1Q 1141
ip address 10.14.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN Coventry to sw1 G0/2
!
interface GigabitEthernet0/1.892
description PW IT Coventry - Manchester
encapsulation dot1Q 892
xconnect 10.10.2.1 1411 encapsulation mpls
backup peer 10.10.2.2 1421
backup delay 1 1
!
interface GigabitEthernet0/1.1650
description PW BS Coventry - Manchester
encapsulation dot1Q 1650
xconnect 10.10.2.1 21411 encapsulation mpls
backup peer 10.10.2.2 21421
backup delay 1 1
!
router ospf 10
router-id 10.10.14.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
623
Программный интерфейс Jinja2
624
Программный интерфейс Jinja2
hostname {{name}}
!
interface Loopback10
description MPLS loopback
ip address 10.10.{{id}}.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to {{name}} sw1 G0/1
!
interface GigabitEthernet0/0.1{{id}}1
description MPLS to {{to_name}}
encapsulation dot1Q 1{{id}}1
ip address 10.{{id}}.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN {{name}} to sw1 G0/2 !
interface GigabitEthernet0/1.{{IT}}
description PW IT {{name}} - {{to_name}}
encapsulation dot1Q {{IT}}
xconnect 10.10.{{to_id}}.1 {{id}}11 encapsulation mpls
backup peer 10.10.{{to_id}}.2 {{id}}21
backup delay 1 1
!
interface GigabitEthernet0/1.{{BS}}
description PW BS {{name}} - {{to_name}}
encapsulation dot1Q {{BS}}
xconnect 10.10.{{to_id}}.1 {{to_id}}{{id}}11 encapsulation mpls
backup peer 10.10.{{to_id}}.2 {{to_id}}{{id}}21
backup delay 1 1
!
router ospf 10
router-id 10.10.{{id}}.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
625
Программный интерфейс Jinja2
- id: 11
name: Liverpool
to_name: LONDON
IT: 791
BS: 1550
to_id: 1
- id: 12
name: Bristol
to_name: LONDON
IT: 793
BS: 1510
to_id: 1
- id: 14
name: Coventry
to_name: Manchester
IT: 892
BS: 1650
to_id: 2
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('router_template.txt')
routers = yaml.load(open('routers_info.yml'))
626
Программный интерфейс Jinja2
Если шаблоны находятся в текущем каталоге, надо добавить пару строк и изменить
значение в загрузчике:
import os
curr_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(curr_dir))
627
Синтаксис шаблонов Jinja2
переменные
условия (if/else)
циклы (for)
фильтры - специальные встроенные методы, которые позволяют делать
преобразования переменных
тесты - используются для проверки, соответствует ли переменная какому-то
условию
env = Environment(loader=FileSystemLoader(TEMPLATE_DIR),
trim_blocks=True, lstrip_blocks=True)
template = env.get_template(template)
vars_dict = yaml.load(open(VARS_FILE))
print(template.render(vars_dict))
628
Синтаксис шаблонов Jinja2
Для того, чтобы посмотреть на результат, нужно вызвать скрипт и передать ему два
аргумента:
шаблон
файл с переменными в формате YAML
629
Синтаксис шаблонов Jinja2
630
Синтаксис шаблонов Jinja2
Но перед строками neighbor ... remote-as появились два пробела. Так получилось из-
за того, что перед блоком {% for ibgp in bgp.ibgp_neighbors %} стоит пробел. После
того, как был отключен лишний перевод строки, пробелы и табы перед блоком
добавляются к первой строке блока.
631
Синтаксис шаблонов Jinja2
Плюс после знака процента отключает lstrip_blocks для блока, в данном случае, только
для начала блока.
Если сделать таким образом (плюс добавлен в выражении для завершения блока):
632
Синтаксис шаблонов Jinja2
Шаблон templates/env_flags3.txt:
Обратите внимание на минус в начале второго блока. Минут удаляет все whitespace
символы, в данном случае, в начале блока.
633
Синтаксис шаблонов Jinja2
634
Синтаксис шаблонов Jinja2
635
Синтаксис шаблонов Jinja2
Переменные
Переменные в шаблоне указываются в двойных фигурных скобках:
hostname {{ name }}
interface Loopback0
ip address 10.0.0.{{ id }} 255.255.255.255
hostname {{ name }}
interface Loopback0
ip address 10.0.0.{{ id }} 255.255.255.255
vlan {{ vlans[0] }}
router ospf 1
router-id 10.0.0.{{ id }}
auto-cost reference-bandwidth 10000
network {{ ospf.network }} area {{ ospf['area'] }}
id: 3
name: R3
vlans:
- 10
- 20
- 30
ospf:
network: 10.0.1.0 0.0.0.255
area: 0
636
Синтаксис шаблонов Jinja2
так как переменная vlans это список, можно указывать, какой именно элемент из
списка нам нужен
Если передается словарь (как в случае с переменной ospf), то внутри шаблона можно
обращаться к объектам словаря, используя один из вариантов:
interface Loopback0
ip address 10.0.0.3 255.255.255.255
vlan 10
router ospf 1
router-id 10.0.0.3
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
637
Синтаксис шаблонов Jinja2
Цикл for
Цикл for позволяет проходиться по элементам последовательности.
Цикл for должен находиться внутри символов {% %} . Кроме того, нужно явно
указывать завершение цикла:
hostname {{ name }}
interface Loopback0
ip address 10.0.0.{{ id }} 255.255.255.255
router ospf 1
router-id 10.0.0.{{ id }}
auto-cost reference-bandwidth 10000
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
id: 3
name: R3
vlans:
10: Marketing
20: Voice
30: Management
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
638
Синтаксис шаблонов Jinja2
В цикле for можно проходиться как по элементам списка (например, список ospf), так и
по словарю (словарь vlans). И, аналогичным образом, по любой последовательности.
interface Loopback0
ip address 10.0.0.3 255.255.255.255
vlan 10
name Marketing
vlan 20
name Voice
vlan 30
name Management
router ospf 1
router-id 10.0.0.3
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
network 10.0.2.0 0.0.0.255 area 2
network 10.1.1.0 0.0.0.255 area 0
639
Синтаксис шаблонов Jinja2
if/elif/else
if позволяет добавлять условие в шаблон. Например, можно использовать if, чтобы
добавлять какие-то части шаблона в зависимости от наличия переменных в словаре с
данными.
{% if ospf %}
router ospf 1
router-id 10.0.0.{{ id }}
auto-cost reference-bandwidth 10000
{% endif %}
hostname {{ name }}
interface Loopback0
ip address 10.0.0.{{ id }} 255.255.255.255
{% if ospf %}
router ospf 1
router-id 10.0.0.{{ id }}
auto-cost reference-bandwidth 10000
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
{% endif %}
Выражение if ospf работает так же, как в Python: если переменная существует и не
пустая, результат будет True. Если переменной нет или она пустая, результат будет
False.
То есть, в этом шаблоне конфигурация OSPF генерируется только в том случае, если
переменная ospf существует и не пустая.
640
Синтаксис шаблонов Jinja2
id: 3
name: R3
vlans:
10: Marketing
20: Voice
30: Management
hostname R3
interface Loopback0
ip address 10.0.0.3 255.255.255.255
vlan 10
name Marketing
vlan 20
name Voice
vlan 30
name Management
id: 3
name: R3
vlans:
10: Marketing
20: Voice
30: Management
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
641
Синтаксис шаблонов Jinja2
hostname R3
interface Loopback0
ip address 10.0.0.3 255.255.255.255
vlan 10
name Marketing
vlan 20
name Voice
vlan 30
name Management
router ospf 1
router-id 10.0.0.3
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
network 10.0.2.0 0.0.0.255 area 2
network 10.1.1.0 0.0.0.255 area 0
trunks:
Fa0/1:
action: add
vlans: 10,20
Fa0/2:
action: only
vlans: 10,30
Fa0/3:
action: delete
vlans: 10
642
Синтаксис шаблонов Jinja2
vlans:
10: Marketing
20: Voice
30: Management
Результат выполнения:
643
Синтаксис шаблонов Jinja2
644
Синтаксис шаблонов Jinja2
Фильтры
В Jinja переменные можно изменять с помощью фильтров. Фильтры отделяются от
переменной вертикальной чертой (pipe | ) и могут содержать дополнительные
аргументы.
Кроме того, к переменной могут быть применены несколько фильтров. В таком случае
фильтры просто пишутся последовательно, и каждый из них отделен вертикальной
чертой.
default
Фильтр default позволяет указать для переменной значение по умолчанию. Если
переменная определена, будет выводиться переменная, если переменная не
определена, будет выводиться значение, которое указано в фильтре default.
router ospf 1
auto-cost reference-bandwidth {{ ref_bw | default(10000) }}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
645
Синтаксис шаблонов Jinja2
Результат выполнения:
Если нужно сделать так, чтобы значение по умолчанию подставлялось и в том случае,
когда переменная пустая (то есть, обрабатывается как False в Python), надо указать
дополнительный параметр boolean=true .
ref_bw: ''
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
router ospf 1
auto-cost reference-bandwidth {{ ref_bw | default(10000, boolean=true) }}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
646
Синтаксис шаблонов Jinja2
dictsort
Фильтр dictsort позволяет сортировать словарь. По умолчанию сортировка
выполняется по ключам. Но, изменив параметры фильтра, можно выполнять
сортировку по значениям.
Синтаксис фильтра:
647
Синтаксис шаблонов Jinja2
trunks:
Fa0/1:
action: add
vlans: 10,20
Fa0/2:
action: only
vlans: 10,30
Fa0/3:
action: delete
vlans: 10
join
Фильтр join работает так же, как и метод join в Python.
648
Синтаксис шаблонов Jinja2
trunks:
Fa0/1:
action: add
vlans:
- 10
- 20
Fa0/2:
action: only
vlans:
- 10
- 30
Fa0/3:
action: delete
vlans:
- 10
Результат выполнения:
649
Синтаксис шаблонов Jinja2
Тесты
Кроме фильтров, Jinja также поддерживает тесты. Тесты позволяют проверять
переменные на какое-то условие.
defined
Тест defined позволяет проверить, есть ли переменная в словаре данных.
router ospf 1
{% if ref_bw is defined %}
auto-cost reference-bandwidth {{ ref_bw }}
{% else %}
auto-cost reference-bandwidth 10000
{% endif %}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
Результат выполнения:
650
Синтаксис шаблонов Jinja2
iterable
Тест iterable проверяет, является ли объект итератором.
651
Синтаксис шаблонов Jinja2
trunks:
Fa0/1:
action: add
vlans:
- 10
- 20
Fa0/2:
action: only
vlans:
- 10
- 30
Fa0/3:
action: delete
vlans: 10
находится в списке, и фильтр join в таком случае не работает. Но, за счет теста is
iterable (в этом случае результат будет false), в этом случае шаблон уходит в ветку
else.
Результат выполнения:
652
Синтаксис шаблонов Jinja2
set
Внутри шаблона можно присваивать значения переменным. Это могут быть новые
переменные, а могут быть измененные значения переменных, которые были переданы
шаблону.
interface {{ intf }}
{% if vlans is iterable %}
{% if action == 'add' %}
switchport trunk allowed vlan add {{ vlans | join(',') }}
{% elif action == 'delete' %}
switchport trunk allowed vlan remove {{ vlans | join(',') }}
{% else %}
switchport trunk allowed vlan {{ vlans | join(',') }}
{% endif %}
{% else %}
{% if action == 'add' %}
switchport trunk allowed vlan add {{ vlans }}
{% elif action == 'delete' %}
switchport trunk allowed vlan remove {{ vlans }}
{% else %}
switchport trunk allowed vlan {{ vlans }}
{% endif %}
{% endif %}
{% endfor %}
Таким образом создаются новые переменные, и дальше используются уже эти новые
значения. Так шаблон выглядит понятней.
653
Синтаксис шаблонов Jinja2
trunks:
Fa0/1:
action: add
vlans:
- 10
- 20
Fa0/2:
action: only
vlans:
- 10
- 30
Fa0/3:
action: delete
vlans: 10
Результат выполнения:
interface Fa0/1
switchport trunk allowed vlan add 10,20
interface Fa0/2
switchport trunk allowed vlan 10,30
interface Fa0/3
switchport trunk allowed vlan remove 10
654
Синтаксис шаблонов Jinja2
include
Выражение include позволяет добавить один шаблон в другой.
Переменные, которые передаются как данные, должны содержать все данные и для
основного шаблона, и для того, который добавлен через include.
Шаблон templates/vlans.txt:
Шаблон templates/ospf.txt:
router ospf 1
auto-cost reference-bandwidth 10000
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
Шаблон templates/bgp.txt:
{% include 'vlans.txt' %}
{% include 'ospf.txt' %}
655
Синтаксис шаблонов Jinja2
vlans:
10: Marketing
20: Voice
30: Management
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
router ospf 1
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
network 10.0.2.0 0.0.0.255 area 2
network 10.1.1.0 0.0.0.255 area 0
Шаблон templates/router.txt:
{% include 'ospf.txt' %}
{% include 'bgp.txt' %}
logging {{ log_server }}
В данном случае кроме include добавлена ещё одна строка в шаблон, чтобы показать,
что выражения include могут идти вперемешку с обычным шаблоном.
656
Синтаксис шаблонов Jinja2
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
bgp:
local_as: 100
loopback: lo100
ibgp_neighbors:
- 10.0.0.2
- 10.0.0.3
ebgp_neighbors:
90.1.1.1: 500
80.1.1.1: 600
log_server: 10.1.1.1
logging 10.1.1.1
657
Наследование шаблонов
Наследование шаблонов
Наследование шаблонов - это очень мощный функционал, который позволяет
избежать повторения одного и того же в разных шаблонах.
658
Наследование шаблонов
!
{% block services %}
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
{% endblock %}
!
no ip domain lookup
!
ip ssh version 2
!
{% block ospf %}
router ospf 1
auto-cost reference-bandwidth 10000
{% endblock %}
!
{% block bgp %}
{% endblock %}
!
{% block alias %}
{% endblock %}
!
line con 0
logging synchronous
history size 100
line vty 0 4
logging synchronous
history size 100
transport input ssh
!
659
Наследование шаблонов
{% block services %}
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
{% endblock %}
!
{% block ospf %}
router ospf 1
auto-cost reference-bandwidth 10000
{% endblock %}
!
{% block bgp %}
{% endblock %}
!
{% block alias %}
{% endblock %}
{% extends "base_router.txt" %}
{% block ospf %}
{{ super() }}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
{% endblock %}
{% block alias %}
alias configure sh do sh
alias exec ospf sh run | s ^router ospf
alias exec bri show ip int bri | exc unass
alias exec id show int desc
alias exec top sh proc cpu sorted | excl 0.00%__0.00%__0.00%
alias exec c conf t
alias exec diff sh archive config differences nvram:startup-config system:running-conf
ig
alias exec desc sh int desc | ex down
{% endblock %}
{% extends "base_router.txt" %}
660
Наследование шаблонов
Именно она говорит о том, что шаблон hq_router.txt будет построен на основе шаблона
base_router.txt.
Внутри дочернего шаблона всё происходит внутри блоков. За счет блоков, которые
были определены в базовом шаблоне, дочерний шаблон может расширять
родительский шаблон.
В базовом шаблоне четыре блока: services, ospf, bgp, alias. В дочернем шаблоне
заполнены только два из них: ospf и alias.
При этом блоки ospf и alias используются по-разному. В базовом шаблоне в блоке ospf
уже была часть конфигурации:
{% block ospf %}
router ospf 1
auto-cost reference-bandwidth 10000
{% endblock %}
{% block ospf %}
{{ super() }}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
{% endblock %}
661
Наследование шаблонов
В блоке alias просто описаны нужные alias. И, даже если бы в родительском шаблоне
были какие-то настройки, они были бы затерты содержимым дочернего шаблона.
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
662
Наследование шаблонов
Обратите внимание, что в блоке ospf есть и команды из базового шаблона, и команды
из дочернего шаблона.
663
Дополнительные материалы
Дополнительные материалы
Документация:
Статьи:
664
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 21.1
Переделать скрипт cfg_gen.py в функцию generate_cfg_from_template.
путь к шаблону
файл с переменными в формате YAML
env = Environment(loader=FileSystemLoader(TEMPLATE_DIR),
trim_blocks=True, lstrip_blocks=True)
template = env.get_template(template_file)
vars_dict = yaml.load(open(VARS_FILE))
print(template.render(vars_dict))
Задание 21.1a
665
Задания
trim_blocks
lstrip_blocks
Задание 21.1b
Дополнить функцию generate_cfg_from_template из задания 21.1 или 21.1a:
YAML
JSON
словарь Python
YAML - yaml_file
JSON - json_file
словарь Python - py_dict
data_files/for.yml
data_files/for.json
словаре data_dict
data_dict = {'vlans': {
10: 'Marketing',
20: 'Voice',
30: 'Management'},
'ospf': [{'network': '10.0.1.0 0.0.0.255', 'area': 0},
{'network': '10.0.2.0 0.0.0.255', 'area': 2},
{'network': '10.1.1.0 0.0.0.255', 'area': 0}],
'id': 3,
'name': 'R3'}
666
Задания
Задание 21.1c
Переделать функцию generate_cfg_from_template из задания 21.1, 21.1a или 21.1b:
data_files/for.yml
data_files/for.json
словаре data_dict
error_message = '''
Не получилось определить формат данных.
Поддерживаются файлы с расширением .json, .yml, .yaml и словари Python
'''
data_dict = {'vlans': {
10: 'Marketing',
20: 'Voice',
30: 'Management'},
'ospf': [{'network': '10.0.1.0 0.0.0.255', 'area': 0},
{'network': '10.0.2.0 0.0.0.255', 'area': 2},
{'network': '10.1.1.0 0.0.0.255', 'area': 0}],
'id': 3,
'name': 'R3'}
Задание 21.2
На основе конфигурации config_r1.txt, создать шаблоны:
667
Задания
templates/cisco_base.txt
templates/alias.txt
templates/eem_int_desc.txt
Задание 21.3
Создайте шаблон templates/ospf.txt на основе конфигурации OSPF в файле
cisco_ospf.txt. Пример конфигурации дан, чтобы напомнить синтаксис.
False
passive-interface x
668
Задания
ip ospf hello-interval 1
Задание 21.3a
Измените шаблон templates/ospf.txt таким образом, чтобы для перечисленных
переменных были указаны значения по умолчанию, которые используются в том
случае, если переменная не задана.
Задание 21.3b
Измените шаблон templates/ospf.txt из задания 21.3a таким образом, чтобы для
перечисленных переменных были указаны значения по умолчанию, которые
используются в том случае, если переменная не задана или, если в переменной
пустое значение.
669
Задания
Задание 21.4
Создайте шаблон templates/add_vlan_to_switch.txt, который будет использоваться при
необходимости добавить VLAN на коммутатор.
Если VLAN необходимо добавить как access, то надо настроить и режим интерфейса и
добавить его в VLAN:
interface Gi0/1
switchport mode access
switchport access vlan 5
interface Gi0/10
switchport trunk allowed vlan add 5
670
22. Обработка вывода команд TextFSM
Использование TextFSM лучше, чем простая построчная обработка, так как шаблоны
дают лучшее представление о том, как вывод будет обрабатываться, и шаблонами
проще поделиться. А значит, проще найти уже созданные шаблоны и использовать их,
или поделиться своими.
671
22. Обработка вывода команд TextFSM
Value ID (\d+)
Value Hop (\d+(\.\d+){3})
Start
^ ${ID} ${Hop} -> Record
После строки Start начинается сам шаблон. В данном случае он очень простой:
import textfsm
traceroute = '''
r2#traceroute 90.0.0.9 source 33.0.0.2
traceroute 90.0.0.9 source 33.0.0.2
Type escape sequence to abort.
Tracing the route to 90.0.0.9
VRF info: (vrf in name/id, vrf out name/id)
1 10.0.12.1 1 msec 0 msec 0 msec
2 15.0.0.5 0 msec 5 msec 4 msec
3 57.0.0.7 4 msec 1 msec 4 msec
4 79.0.0.9 4 msec * 1 msec
'''
template = open('traceroute.template')
fsm = textfsm.TextFSM(template)
result = fsm.ParseText(traceroute)
print(fsm.header)
print(result)
672
22. Обработка вывода команд TextFSM
$ python parse_traceroute.py
['ID', 'Hop']
[['1', '10.0.12.1'], ['2', '15.0.0.5'], ['3', '57.0.0.7'], ['4', '79.0.0.9']]
673
Синтаксис шаблонов TextFSM
определения переменных
эти переменные описывают, какие столбцы будут в табличном представлении
определения состояний
# Определение переменных:
Value ID (\d+)
Value Hop (\d+(\.\d+){3})
Определение переменных
В секции с переменными должны идти только определения переменных.
Единственное исключение - в этом разделе могут быть комментарии.
В этом разделе не должно быть пустых строк. Для TextFSM пустая строка означает
завершение секции определения переменных.
Value - это ключевое слово, которое указывает, что создается переменная. Его
674
Синтаксис шаблонов TextFSM
Определение состояний
После определения переменных нужно описать состояния:
Зарезервированные состояния
Зарезервированы такие состояния:
675
Синтаксис шаблонов TextFSM
Start - это состояние обязательно должно быть указано. Без него шаблон не будет
работать.
End - это состояние завершает обработку входящих строк и не выполняет
состояние EOF.
EOF - это неявное состояние, которое выполняется всегда, когда обработка дошла
до конца файла. Выглядит оно таким образом:
EOF
^.* -> Record
EOF записывает текущую строку, прежде чем обработка завершается. Если это
поведение нужно изменить, надо явно в конце шаблона написать EOF:
EOF
Правила состояний
Каждое состояние состоит из одного или более правил:
В правиле:
Действия в правилах
676
Синтаксис шаблонов TextFSM
Line Actions
Line Actions:
Record Action
Record Action - опциональное действие, которое может быть указано после Line
Action. Они должны быть разделены точкой. Типы действий:
Разделять действия точкой нужно только в том случае, если нужно указать и Line,
и Record действия. Если нужно указать только одно из них, точку ставить не
нужно.
State Transition
После действия может быть указано новое состояние:
677
Синтаксис шаблонов TextFSM
Error Action
Специальное действие Error останавливает всю обработку строк, отбрасывает все
строки, которые были собраны до сих пор, и возвращает исключение.
678
Примеры использования TextFSM
import sys
import textfsm
from tabulate import tabulate
template = sys.argv[1]
output_file = sys.argv[2]
f = open(template)
output = open(output_file).read()
re_table = textfsm.TextFSM(f)
header = re_table.header
result = re_table.ParseText(output)
print(tabulate(result, headers=header))
show clock
Первый пример - разбор вывода команды sh clock (файл output/sh_clock.txt):
679
Примеры использования TextFSM
Подсказка по спецсимволам:
. - любой символ
\d - любая цифра
Start
^${Time}.* ${Timezone} ${WeekDay} ${Month} ${MonthDay} ${Year} -> Record
680
Примеры использования TextFSM
Так как в данном случае в выводе всего одна строка, можно не писать в шаблоне
действие Record. Но лучше его использовать в ситуациях, когда надо записать
значения, чтобы привыкать к этому синтаксису и не ошибиться, когда нужна
обработка нескольких строк.
Когда это регулярное выражение применяется в выводу show clock, в каждой группе
регулярного выражения будет находиться соответствующее значение:
1 группа: 15:10:44
2 группа: UTC
3 группа: Sun
4 группа: Nov
5 группа: 13
6 группа: 2016
В правиле, кроме явного действия Record, которое указывает, что запись надо
поместить в финальную таблицу, по умолчанию также используется правило Next. Оно
указывает, что надо перейти к следующей строке текста. Так как в выводе команды sh
clock только одна строка, обработка завершается.
Особенность этой команды в том, что данные находятся не в одной строке, а в разных.
681
Примеры использования TextFSM
Version :
Cisco IOS Software, C2960 Software (C2960-LANBASEK9-M), Version 12.2(55)SE9, RELEASE S
OFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2014 by Cisco Systems, Inc.
Compiled Mon 03-Mar-14 22:53 by prod_rel_team
advertisement version: 2
VTP Management Domain: ''
Native VLAN: 1
Duplex: full
Management address(es):
IP address: 10.1.1.2
-------------------------
Device ID: R1
Entry address(es):
IP address: 10.1.1.1
Platform: Cisco 3825, Capabilities: Router Switch IGMP
Interface: GigabitEthernet1/0/22, Port ID (outgoing port): GigabitEthernet0/0
Holdtime : 156 sec
Version :
Cisco IOS Software, 3800 Software (C3825-ADVENTERPRISEK9-M), Version 12.4(24)T1, RELEA
SE SOFTWARE (fc3)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2009 by Cisco Systems, Inc.
Compiled Fri 19-Jun-09 18:40 by prod_rel_team
advertisement version: 2
VTP Management Domain: ''
Duplex: full
Management address(es):
-------------------------
Device ID: R2
Entry address(es):
IP address: 10.2.2.2
Platform: Cisco 2911, Capabilities: Router Switch IGMP
Interface: GigabitEthernet1/0/21, Port ID (outgoing port): GigabitEthernet0/0
Holdtime : 156 sec
Version :
Cisco IOS Software, 2900 Software (C3825-ADVENTERPRISEK9-M), Version 15.2(2)T1, RELEAS
E SOFTWARE (fc3)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2009 by Cisco Systems, Inc.
Compiled Fri 19-Jun-09 18:40 by prod_rel_team
682
Примеры использования TextFSM
advertisement version: 2
VTP Management Domain: ''
Duplex: full
Management address(es):
Start
^${LOCAL_HOST}[>#].
^Device ID: ${DEST_HOST}
^.*IP address: ${MGMNT_IP}
^Platform: ${PLATFORM},
^Interface: ${LOCAL_PORT}, Port ID \(outgoing port\): ${REMOTE_PORT}
^.*Version ${IOS_VERSION},
683
Примеры использования TextFSM
итоговой таблицы.
Record
Так получилось из-за того, что в шаблоне не указано действие Record. И в итоге в
финальной таблице осталась только последняя строка.
Исправленный шаблон:
Start
^${LOCAL_HOST}[>#].
^Device ID: ${DEST_HOST}
^.*IP address: ${MGMNT_IP}
^Platform: ${PLATFORM},
^Interface: ${LOCAL_PORT}, Port ID \(outgoing port\): ${REMOTE_PORT}
^.*Version ${IOS_VERSION}, -> Record
Filldown
684
Примеры использования TextFSM
Start
^${LOCAL_HOST}[>#].
^Device ID: ${DEST_HOST}
^.*IP address: ${MGMNT_IP}
^Platform: ${PLATFORM},
^Interface: ${LOCAL_PORT}, Port ID \(outgoing port\): ${REMOTE_PORT}
^.*Version ${IOS_VERSION}, -> Record
Required
Дело в том, что все переменные, которые мы определили, опциональны. К тому же,
одна переменная с параметром Filldown. И, чтобы избавиться от последней строки,
нужно сделать хотя бы одну переменную обязательной с помощью параметра
Required:
685
Примеры использования TextFSM
Start
^${LOCAL_HOST}[>#].
^Device ID: ${DEST_HOST}
^.*IP address: ${MGMNT_IP}
^Platform: ${PLATFORM},
^Interface: ${LOCAL_PORT}, Port ID \(outgoing port\): ${REMOTE_PORT}
^.*Version ${IOS_VERSION}, -> Record
Start
^${INTF}\s+${ADDR}\s+\w+\s+\w+\s+${STATUS}\s+${PROTO} -> Record
686
Примеры использования TextFSM
Для маршрутов к одной и той же сети вместо нескольких строк, где будет повторяться
сеть, будет создана одна запись, в которой все доступные next-hop адреса собраны в
список.
687
Примеры использования TextFSM
Для этого примера упрощаем задачу и считаем, что маршруты могут быть только
OSPF и с обозначением только O (то есть, только внутризональные маршруты).
Start
^O +${Network}${Mask}\s\[${Distance}\/${Metric}\]\svia\s${NextHop}, -> Record
688
Примеры использования TextFSM
Всё нормально, но потерялись варианты путей для маршрута 10.4.4.4/32. Это логично,
ведь нет правила, которое подошло бы для такой строки.
List
Воспользуемся опцией List для переменной NextHop:
Start
^O +${Network}${Mask}\s\[${Distance}\/${Metric}\]\svia\s${NextHop}, -> Record
Так как перед записью маршрута, для которого есть несколько путей, надо добавить к
нему все доступные адреса NextHop, надо перенести действие Record.
^O -> Continue.Record
В ней действие Record говорит, что надо записать текущее значение переменных. А,
так как в этом правиле нет переменных, записывается то, что было в предыдущих
значениях.
Действие Continue говорит, что надо продолжить работать с текущей строкой так, как
будто совпадения не было. За счет этого сработает следующая строка.
689
Примеры использования TextFSM
^\s+\[${Distance}\/${Metric}\]\svia\s${NextHop},
Start
^O -> Continue.Record
^O +${Network}${Mask}\s\[${Distance}\/${Metric}\]\svia\s${NextHop},
^\s+\[${Distance}\/${Metric}\]\svia\s${NextHop},
690
Примеры использования TextFSM
Сложность тут в том, что порты находятся в одной строке, а в TextFSM нельзя
указывать одну и ту же переменную несколько раз в строке. Но есть возможность
несколько раз искать совпадение в строке.
Start
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +${MEMBERS}\( -> Record
Результат:
691
Примеры использования TextFSM
CHANNEL MEMBERS
--------- ----------
Po1 ['Fa0/1']
Po3 ['Fa0/11']
Пока что в выводе только первый порт, а нужно, чтобы попали все порты. В данном
случае надо продолжить обработку строки с портами после найденного совпадения. То
есть, использовать действие Continue и описать следующее выражение.
Единственная строка, которая есть в шаблоне, описывает первый порт. Надо добавить
строку, которая описывает следующий порт.
Start
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +\S+ +${MEMBERS}\( -> Record
Результат:
CHANNEL MEMBERS
--------- --------------------
Po1 ['Fa0/1', 'Fa0/2']
Po3 ['Fa0/11', 'Fa0/12']
692
Примеры использования TextFSM
Start
^\d+.* -> Continue.Record
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +\S+ +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +(\S+ +){2} +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +(\S+ +){3} +${MEMBERS}\( -> Continue
Результат обработки:
CHANNEL MEMBERS
--------- ----------------------------------------
Po1 ['Fa0/1', 'Fa0/2', 'Fa0/3']
Po3 ['Fa0/11', 'Fa0/12', 'Fa0/13', 'Fa0/14']
Шаблон предполагает, что в одной строке будет максимум четыре порта. Если
портов может быть больше, надо добавить соответствующие строки в шаблон.
693
Примеры использования TextFSM
Для того, чтобы шаблон обрабатывал и этот вариант, надо его модифицировать (файл
templates/sh_etherchannel_summary2.txt):
Start
^\d+.* -> Continue.Record
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +\S+ +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +(\S+ +){2} +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +(\S+ +){3} +${MEMBERS}\( -> Continue
^ +${MEMBERS} -> Continue
^ +\S+ +${MEMBERS} -> Continue
^ +(\S+ +){2} +${MEMBERS} -> Continue
^ +(\S+ +){3} +${MEMBERS} -> Continue
CHANNEL MEMBERS
--------- ------------------------------------------------------------
Po1 ['Fa0/1', 'Fa0/2', 'Fa0/3']
Po3 ['Fa0/11', 'Fa0/12', 'Fa0/13', 'Fa0/14', 'Fa0/15', 'Fa0/16']
694
CLI Table
Для того, чтобы ею можно было воспользоваться, надо создать файл, в котором
описаны соответствия между командами и шаблонами. В TextFSM он называется
index.
Этот файл должен находиться в каталоге с шаблонами и должен иметь такой формат:
695
CLI Table
sh_cdp_n_det.template
sh_clock.template
sh_ip_int_br.template
sh_ip_route_ospf.template
index
696
CLI Table
Сначала надо инициализировать класс, передав ему имя файла, в котором хранится
соответствие между шаблонами и командами, и указать имя каталога, в котором
хранятся шаблоны:
In [6]: cli_table.
cli_table.AddColumn cli_table.NewRow cli_table.index cli_t
able.size
cli_table.AddKeys cli_table.ParseCmd cli_table.index_file cli_t
able.sort
cli_table.Append cli_table.ReadIndex cli_table.next cli_t
able.superkey
cli_table.CsvToTable cli_table.Remove cli_table.raw cli_t
able.synchronised
cli_table.FormattedTable cli_table.Reset cli_table.row cli_t
able.table
cli_table.INDEX cli_table.RowWith cli_table.row_class cli_t
able.template_dir
cli_table.KeyValue cli_table.extend cli_table.row_index
cli_table.LabelValueTable cli_table.header cli_table.separator
697
CLI Table
In [7]: print(cli_table)
Network, Mask, Distance, Metric, NextHop
10.0.24.0, /24, 110, 20, ['10.0.12.2']
10.0.34.0, /24, 110, 20, ['10.0.13.3']
10.2.2.2, /32, 110, 11, ['10.0.12.2']
10.3.3.3, /32, 110, 11, ['10.0.13.3']
10.4.4.4, /32, 110, 21, ['10.0.13.3', '10.0.12.2', '10.0.14.4']
10.5.35.0, /24, 110, 20, ['10.0.13.3']
In [8]: print(cli_table.FormattedTable())
Network Mask Distance Metric NextHop
====================================================================
10.0.24.0 /24 110 20 10.0.12.2
10.0.34.0 /24 110 20 10.0.13.3
10.2.2.2 /32 110 11 10.0.12.2
10.3.3.3 /32 110 11 10.0.13.3
10.4.4.4 /32 110 21 10.0.13.3, 10.0.12.2, 10.0.14.4
10.5.35.0 /24 110 20 10.0.13.3
In [11]: data_rows
Out[11]:
[['10.0.24.0', '/24', '110', '20', ['10.0.12.2']],
['10.0.34.0', '/24', '110', '20', ['10.0.13.3']],
['10.2.2.2', '/32', '110', '11', ['10.0.12.2']],
['10.3.3.3', '/32', '110', '11', ['10.0.13.3']],
['10.4.4.4', '/32', '110', '21', ['10.0.13.3', '10.0.12.2', '10.0.14.4']],
['10.5.35.0', '/24', '110', '20', ['10.0.13.3']]]
In [14]: header
Out[14]: ['Network', 'Mask', 'Distance', 'Metric', 'NextHop']
698
CLI Table
import clitable
output_sh_ip_route_ospf = open('output/sh_ip_route_ospf.txt').read()
cli_table.ParseCmd(output_sh_ip_route_ospf, attributes)
print('CLI Table output:\n', cli_table)
print(header)
for row in data_rows:
print(row)
699
CLI Table
$ python textfsm_clitable.py
CLI Table output:
Network, Mask, Distance, Metric, NextHop
10.0.24.0, /24, 110, 20, ['10.0.12.2']
10.0.34.0, /24, 110, 20, ['10.0.13.3']
10.2.2.2, /32, 110, 11, ['10.0.12.2']
10.3.3.3, /32, 110, 11, ['10.0.13.3']
10.4.4.4, /32, 110, 21, ['10.0.13.3', '10.0.12.2', '10.0.14.4']
10.5.35.0, /24, 110, 20, ['10.0.13.3']
Formatted Table:
Network Mask Distance Metric NextHop
====================================================================
10.0.24.0 /24 110 20 10.0.12.2
10.0.34.0 /24 110 20 10.0.13.3
10.2.2.2 /32 110 11 10.0.12.2
10.3.3.3 /32 110 11 10.0.13.3
10.4.4.4 /32 110 21 10.0.13.3, 10.0.12.2, 10.0.14.4
10.5.35.0 /24 110 20 10.0.13.3
700
Дополнительные материалы
Дополнительные материалы
Документация:
TextFSM
Статьи:
Модуль ntc-ansible
ntc-templates
701
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 22.1
Переделать пример, который использовался в разделе TextFSM, в функцию.
template - шаблон TextFSM (это должно быть имя файла, в котором находится
шаблон)
output - вывод соответствующей команды show (строка)
Пример из раздела:
702
Задания
import sys
import textfsm
from tabulate import tabulate
template = sys.argv[1]
output_file = sys.argv[2]
Задание 22.1a
Переделать функцию parse_output из задания 22.1 таким образом, чтобы, вместо
списка списков, она возвращала один список словарей:
Задание 22.2
В этом задании нужно использовать функцию parse_output из задания 22.1. Она
используется для того, чтобы получить структурированный вывод в результате
обработки вывода команды.
Для записи вывода в CSV, нужно создать функцию list_to_csv, которая ожидает как
аргументы:
список:
первый элемент - это список с названиями заголовков
остальные элементы это списки, в котором находятся результаты обработки
вывода
имя файла, в который нужно записать данные в CSV формате
Задание 22.3
703
Задания
Сделать шаблон TextFSM для обработки вывода sh ip dhcp snooping binding. Вывод
команды находится в файле output/sh_ip_dhcp_snooping.txt.
MacAddress
IpAddress
VLAN
Interface
Задание 22.4
На основе примера из раздела clitable сделать функцию parse_command_dynamic.
Параметры функции:
Пример из раздела:
704
Задания
import clitable
output_sh_ip_route_ospf = open('output/sh_ip_route_ospf.txt').read()
cli_table.ParseCmd(output_sh_ip_route_ospf, attributes)
print(header)
for row in data_rows:
print(row)
Задание 22.4a
Переделать функцию из задания 22.4:
Задание 22.5
В этом задании соединяется функциональность TextFSM и подключение к
оборудованию.
Задача такая:
подключиться к оборудованию
выполнить команду show
полученный вывод передавать на обработку TextFSM
вернуть результат обработки
705
Задания
Задание 22.6
Это задание похоже на задание 22.5, но в этом задании подключения надо выполнять
параллельно с помощью потоков. Для параллельного подключения использовать
модуль concurrent.futures.
706
Задания
import yaml
707
VI. Ansible
Ansible
Ansible - это система управления конфигурациями. Ansible позволяет
автоматизировать и упростить настройку, обслуживание и развертывание серверов,
служб, ПО и др.
Установка Ansible
708
VI. Ansible
Если Вы хотите использовать Ansible с Python 2.7, для этого раздела лучше
переключиться на книгу для Python 2.7, так как они могут отличаться.
Параметры оборудования
В примерах раздела используются три маршрутизатора и один коммутатор. К ним нет
никаких требований, только настроенный SSH.
пользователь: cisco
пароль: cisco
пароль на режим enable: cisco
SSH версии 2
IP-адреса:
R1: 192.168.100.1
R2: 192.168.100.2
R3: 192.168.100.3
SW1: 192.168.100.100
709
VI. Ansible
710
23. Основы Ansible
Основы Ansible
Ansible:
Терминология
Control machine — управляющий хост. Сервер Ansible, с которого происходит
управление другими хостами
Manage node — управляемые хосты
Inventory — инвентарный файл. В этом файле описываются хосты, группы хостов,
а также могут быть созданы переменные
Playbook — файл сценариев
Play — сценарий (набор задач). Связывает задачи с хостами, для которых эти
задачи надо выполнить
Task — задача. Вызывает модуль с указанными параметрами и переменными
Module — модуль Ansible. Реализует определенные функции
Quick start
С Ansible очень просто начать работать. Минимум, который нужен для начала работы:
711
23. Основы Ansible
712
Инвентарный файл
Инвентарный файл
Инвентарный файл - это файл, в котором описываются устройства, к которым Ansible
будет подключаться.
Хосты и группы
В инвентарном файле устройства могут указываться используя IP-адреса или имена.
Устройства могут быть указаны по одному или разбиты на группы.
r5.example.com
[cisco-routers]
192.168.255.1
192.168.255.2
192.168.255.3
192.168.255.4
[cisco-edge-routers]
192.168.255.1
192.168.255.2
Таким образом можно применять отдельно какие-то политики для группы cisco-edge-
routers, но в то же время, когда необходимо настроить что-то, что касается всех
маршрутизаторов, можно использовать группу cisco-routers.
Но можно создавать свой инвентарный файл и использовать его. Для этого нужно,
либо указать его при запуске ansible, используя опцию -i <путь> , либо указать файл в
конфигурационном файле Ansible.
713
Инвентарный файл
inventories/
├── branch-A
│ ├── cisco-routers
│ └── cisco-switches
├── branch-B
│ ├── cisco-routers
│ └── cisco-switches
└── headquarter
├── cisco-routers
├── cisco-switches
└── juniper-routers
Если какое-то из устройств использует нестандартный порт SSH, порт можно указать
после имени или адреса устройства, через двоеточие (ниже показан пример).
[cisco-routers]
192.168.255.1:22022
192.168.255.2:22022
192.168.255.3:22022
[cisco-switches]
192.168.254.1
192.168.254.2
[cisco-routers]
192.168.255.[1:5]
714
Инвентарный файл
[cisco-routers]
router[A:D].example.com
Группа из групп
Ansible также позволяет объединять группы устройств в общую группу. Для этого
используется специальный синтаксис:
[cisco-routers]
192.168.255.1
192.168.255.2
192.168.255.3
[cisco-switches]
192.168.254.1
192.168.254.2
[cisco-devices:children]
cisco-routers
cisco-switches
Группы по-умолчанию
По-умолчанию, в Ansible существует две группы: all и ungrouped. Первая включает в
себя все хосты, а вторая, соответственно, хосты, которые не принадлежат ни одной из
групп.
715
Ad-Hoc команды
Ad Hoc команды
Ad-hoc команды - это возможность запустить какое-то действие Ansible из командной
строки.
Такой вариант используется, как правило, в тех случаях, когда надо что-то проверить,
например, работу модуля. Или просто выполнить какое-то разовое действие, которое
не нужно сохранять.
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
716
Ad-Hoc команды
-m raw -a "sh ip int br" - параметр -m raw означает, что используется модуль
raw
этот модуль позволяет отправлять команды в SSH сессии, но при этом не
загружает на хост модуль Python. То есть, этот модуль просто отправляет
указанную команду как строку и всё
плюс модуля raw в том, что он может использоваться для любой системы,
которую поддерживает Ansible
-a "sh ip int br" - параметр -a указывает, какую команду отправить
паролю, а не по ключам
Ошибка значит, что нужно установить программу sshpass. Эта особенность возникает,
только когда используется аутентификация по паролю.
Установка sshpass:
717
Ad-Hoc команды
718
Конфигурационный файл
Конфигурационный файл
Настройки Ansible можно менять в конфигурационном файле.
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
[defaults]
inventory = ./myhosts
remote_user = cisco
ask_pass = True
719
Конфигурационный файл
gathering
По умолчанию Ansible собирает факты об устройствах.
Факты - это информация о хостах, к которым подключается Ansible. Эти факты можно
использовать в playbook и шаблонах как переменные.
Но для сетевого оборудования модуль setup не подходит, поэтому сбор фактов надо
отключить. Это можно сделать в конфигурационном файле Ansible или в playbook.
gathering = explicit
host_key_checking
Параметр host_key_checking отвечает за проверку ключей при подключении по SSH.
Если указать в конфигурационном файле host_key_checking=False , проверка будет
отключена.
Чтобы проверить этот функционал, надо удалить сохраненные ключи для устройств
Cisco, к которым уже выполнялось подключение.
Если выполнить ad-hoc команду после удаления ключей, вывод будет таким:
720
Конфигурационный файл
[defaults]
inventory = ./myhosts
remote_user = cisco
ask_pass = True
host_key_checking=False
721
Конфигурационный файл
722
Конфигурационный файл
723
Модули
Модули Ansible
Вместе с установкой Ansible устанавливается также большое количество модулей
(библиотека модулей). В текущей библиотеке модулей находится порядка 200
модулей.
Модули отвечают за действия, которые выполняет Ansible. При этом каждый модуль,
как правило, отвечает за свою конкретную и небольшую задачу.
Как правило, при вызове модуля ему нужно передать аргументы. Какие-то аргументы
будут управлять поведением и параметрами модуля, а какие-то передавать, например,
команду, которую надо выполнить.
Модули Ansible, как правило, идемпотентны. Это означает, что модуль можно
выполнять сколько угодно раз, но при этом модуль будет выполнять изменения, только
если система не находится в желаемом состоянии.
724
Модули
725
Основы playbook
Основы playbooks
Playbook (файл сценариев) — это файл, в котором описываются действия, которые
нужно выполнить на какой-то группе хостов.
Внутри playbook:
play - это набор задач, которые нужно выполнить для группы хостов
task - это конкретная задача. В задаче есть как минимум:
описание (название задачи можно не писать, но очень рекомендуется)
модуль и команда (действие в модуле)
Синтаксис playbook
Playbook описываются в формате YAML.
726
Основы playbook
---
tasks:
tasks:
name: Run show commands on routers - имя сценария (play). Этот параметр
обязательно должен быть в любом сценарии
hosts: cisco-routers - сценарий будет применяться к устройствам в группе cisco-
routers
тут может быть указано и несколько групп, например, таким образом: hosts:
cisco-routers:cisco-switches . Подробнее в документации
обычно, в play надо указывать параметр remote_user. Но, так как мы указали его в
конфигурационном файле Ansible, можно не указывать его в play.
gather_facts: false - отключение сбора фактов об устройстве, так как для
сетевого оборудования надо использовать отдельные модули для сбора фактов.
в разделе конфигурационный файл рассматривалось, как отключить сбор
фактов по умолчанию. Если он отключен, то параметр gather_facts в play не
нужно указывать.
tasks: - дальше идет перечень задач
727
Основы playbook
$ ansible-playbook 1_show_commands_with_raw.yml
728
Основы playbook
Обратите внимание, что для запуска playbook используется другая команда. Для
ad-hoc команды использовалась команда ansible. А для playbook - ansible-
playbook.
Для того, чтобы убедиться, что команды, которые указаны в задачах, выполнились на
устройствах, запустите playbook с опцией -v (вывод сокращен):
$ ansible-playbook 1_show_commands_with_raw.yml -v
729
Основы playbook
Если в сценарии, например, две задачи, то сначала первая задача должна быть
выполнена для всех устройств, которые указаны в параметре hosts. Только после того,
как первая задача была выполнена для всех хостов, начинается выполнение второй
задачи.
$ ansible-playbook 1_show_commands_with_raw.yml
730
Основы playbook
В этом файле хранится имя или адрес устройства, на котором возникла ошибка. Так
выглядит файл 1_show_commands_with_raw.retry сейчас:
192.168.100.1
Создается этот файл для того, чтобы можно было перезапустить playbook заново
только для проблемного устройства (устройств). То есть, надо исправить проблему с
устройством и заново запустить playbook.
Можно было запустить playbook и так (то есть, писать не полный путь к файлу retry):
731
Основы playbook
Параметр --limit очень полезная вещь. Он позволяет ограничивать, для каких хостов
или групп будет выполняться playbook, при этом не меняя сам playbook.
Идемпотентность
Модули Ansible идемпотентны. Это означает, что модуль можно выполнять сколько
угодно раз, но при этом модуль будет выполнять изменения, только если система не
находится в желаемом состоянии.
Но, если, например, в задаче указано, что на сервер Linux надо установить пакет httpd,
то он будет установлен только в том случае, если его нет. То есть, действие не будет
повторяться снова и снова при каждом запуске, а лишь тогда, когда пакета нет.
732
Основы playbook
Переменные
Переменной может быть, например:
Имена переменных
В Ansible есть определенные ограничения по формату имен переменных:
R1:
IP: 10.1.1.1/24
DG: 10.1.1.100
R1['IP']
R1.IP
Правда, при использовании второго варианта могут быть проблемы, если название
ключа совпадает с зарезервированным словом (методом или атрибутом) в Python или
Ansible.
в инвентарном файле
в playbook
в специальных файлах для группы/устройства
в отдельных файлах, которые добавляются в playbook через include (как в Jinja2)
733
Основы playbook
Также можно использовать факты, которые были собраны про устройство, как
переменные.
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
[cisco-routers:vars]
ntp_server=192.168.255.100
log_server=10.255.100.1
Переменные в playbook
Переменные можно задавать прямо в playbook. Это может быть удобно тем, что
переменные находятся там же, где все действия.
734
Основы playbook
---
vars:
ntp_server: 192.168.255.100
log_server: 10.255.100.1
tasks:
735
Основы playbook
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
├── group_vars _
│ ├── all.yml |
│ ├── cisco-routers.yml | Каталог с переменными для групп устройств
│ └── cisco-switches.yml _|
|
├── host_vars _
│ ├── 192.168.100.1 |
│ ├── 192.168.100.2 |
│ ├── 192.168.100.3 | Каталог с переменными для устройств
│ └── 192.168.100.100 _|
|
└── myhosts | Инвентарный файл
Ниже пример содержимого файлов переменных для групп устройств и для отдельных
хостов.
---
cli:
host: "{{ inventory_hostname }}"
username: "cisco"
password: "cisco"
authorize: yes
auth_pass: "cisco"
736
Основы playbook
group_vars/cisco-routers.yml
---
log_server: 10.255.100.1
ntp_server: 10.255.100.1
users:
user1: pass1
user2: pass2
user3: pass3
group_vars/cisco-switches.yml
---
vlans:
- 10
- 20
- 30
Файл host_vars/192.168.100.1
---
hostname: london_r1
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.1
ospf_ints:
- 192.168.100.1
- 10.0.0.1
- 10.255.1.1
Файл host_vars/192.168.100.2
737
Основы playbook
---
hostname: london_r2
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.2
ospf_ints:
- 192.168.100.2
- 10.0.0.2
- 10.255.2.2
Файл host_vars/192.168.100.3
---
hostname: london_r3
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.3
ospf_ints:
- 192.168.100.3
- 10.0.0.3
- 10.255.3.3
Файл host_vars/192.168.100.100
---
hostname: london_sw1
mgmnt_int: VLAN100
mgmnt_ip: 10.0.0.100
Приоритет переменных
В этом разделе не рассматривается размещение переменных:
738
Основы playbook
739
Основы playbook
verbose
В предыдущих разделах один из способов отобразить результат выполнения команд
уже использовался - флаг verbose.
Конечно, вывод не очень удобно читать, но, как минимум, он позволяет увидеть, что
команды выполнились. Также этот флаг позволяет подробно посмотреть, какие шаги
выполняет Ansible.
ansible-playbook 1_show_commands_with_raw.yml -v
740
Основы playbook
register
Параметр register сохраняет результат выполнения модуля в переменную. Затем эта
переменная может использоваться в шаблонах, в принятии решений о ходе сценария
или для отображения вывода.
---
tasks:
Если запустить этот playbook, вывод не будет отличаться, так как вывод только
записан в переменную, но с переменной не выполняется никаких действий.
Следующий шаг - отобразить результат выполнения команды с помощью модуля
debug.
debug
Модуль debug позволяет отображать информацию на стандартный поток вывода. Это
может быть произвольная строка, переменная, факты об устройстве.
741
Основы playbook
---
tasks:
$ ansible-playbook 2_register_vars.yml
742
Основы playbook
743
Основы playbook
---
tasks:
Выполнение playbook:
$ ansible-playbook 3_register_debug_when.yml
744
Основы playbook
---
tasks:
$ ansible-playbook 3_register_debug_when.yml
745
Основы playbook
Так как команда была с ошибкой, сработало условие, которое описано в when, и
задача вывела сообщение с помощью модуля debug.
746
24. Сетевые модули
Если оборудование поддерживает API, как, например, NXOS, то для него создано
большое количество модулей, которые выполняют конкретные действия по настройке
функционала (например, для NXOS создано более 60 модулей).
Для оборудования, которое работает только через CLI, Ansible поддерживает, как
минимум, такие три типа модулей:
ios_command
ios_config
ios_facts
Dellos10
Dellos6
Dellos9
EOS
IOS
IOS XR
JUNOS
747
24. Сетевые модули
SR OS
VyOS
В этом разделе все рассматривается на примере модулей для работы с Cisco IOS:
ios_command
ios_config
ios_facts
---
748
24. Сетевые модули
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
[cisco-routers:vars]
ansible_connection=network_cli
---
ansible_connection: network_cli
Все описание и примеры относятся к модулям ios_x и могут отличаться для других
модулей.
---
ansible_connection: network_cli
ansible_network_os: ios
ansible_user: cisco
ansible_password: cisco
ansible_become: yes
ansible_become_method: enable
ansible_become_pass: cisco
749
24. Сетевые модули
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
[defaults]
inventory = ./myhosts
---
ansible_connection: network_cli
ansible_network_os: ios
ansible_user: cisco
ansible_password: cisco
ansible_become: yes
ansible_become_method: enable
ansible_become_pass: cisco
750
ios_command
Модуль ios_command
Модуль ios_command отправляет команду show на устройство под управлением IOS и
возвращает результат выполнения команды.
751
ios_command
---
tasks:
$ ansible-playbook 1_ios_command.yml
752
ios_command
753
ios_command
---
tasks:
В первой задаче указываются две команды, поэтому синтаксис должен быть немного
другим - команды должны быть указаны как список, в формате YAML.
$ ansible-playbook 2_ios_command.yml
754
ios_command
Обработка ошибок
755
ios_command
$ ansible-playbook 2_ios_command.yml
Ambiguous command
Incomplete command
wait_for
Параметр wait_for (или waitfor) позволяет указывать список условий, на которые надо
проверить вывод команды.
756
ios_command
---
tasks:
Запуск playbook:
$ ansible-playbook 3_ios_command_wait_for.yml -v
757
ios_command
$ ansible-playbook 3_ios_command_wait_for.yml -v
Такой вывод из-за того, что по умолчанию таймаут для каждого пакета 2 секунды, и за
время выполнения playbook команда еще не выполнена.
По умолчанию есть 10 попыток выполнить команду, при этом между каждыми двумя
попытками интервал - секунда. В реальной ситуации при проверке доступности адреса
лучше сделать хотя бы две попытки.
---
tasks:
$ ansible-playbook 3_ios_command_wait_for_interval.yml
758
ios_command
759
ios_facts
Модуль ios_facts
Модуль ios_facts собирает информацию с устройств под управлением IOS.
dir
show version
show memory statistics
show interfaces
show ipv6 interface
show lldp
show lldp neighbors detail
show running-config
all
hardware
dir
show version
show memory statistics
config
show version
show running-config
interfaces
dir
show version
show interfaces
show ip interface
show ipv6 interface
760
ios_facts
show lldp
show lldp neighbors detail
- ios_facts:
gather_subset: all
- ios_facts:
gather_subset:
- interfaces
- ios_facts:
gather_subset:
- "!hardware"
Использование модуля
Пример playbook 1_ios_facts.yml с использованием модуля ios_facts (собираются все
факты):
761
ios_facts
---
tasks:
- name: Facts
ios_facts:
gather_subset: all
$ ansible-playbook 1_ios_facts.yml
Для того, чтобы посмотреть, какие именно факты собираются с устройства, можно
добавить флаг -v (информация сокращена):
$ ansible-playbook 1_ios_facts.yml -v
Using /home/nata/pyneng_course/chapter15/ansible.cfg as config file
После того, как Ansible собрал факты с устройства, все факты доступны как
переменные в playbook, шаблонах и т.д.
762
ios_facts
---
tasks:
- name: Facts
ios_facts:
gather_subset: all
$ ansible-playbook 2_ios_facts_debug.yml
763
ios_facts
Сохранение фактов
В том виде, в котором информация отображается в режиме verbose, довольно сложно
понять какая информация собирается об устройствах. Для того, чтобы лучше понять,
какая информация собирается об устройствах и в каком формате, скопируем
полученную информацию в файл.
764
ios_facts
---
tasks:
- name: Facts
ios_facts:
gather_subset: all
register: ios_facts_result
- copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
Для того, чтобы перенести содержимое переменной в файл, в модуле copy вместо src
используется параметр content.
765
ios_facts
Так как в пути dest используются имена устройств, будут сгенерированы уникальные
файлы для каждого устройства.
$ ansible-playbook 3_ios_facts.yml
192.168.100.1_facts.json
192.168.100.2_facts.json
192.168.100.3_facts.json
{
"ansible_facts": {
"ansible_net_all_ipv4_addresses": [
"192.168.200.1",
"192.168.100.1",
"10.1.1.1"
],
"ansible_net_all_ipv6_addresses": [],
"ansible_net_config": "Building configuration...\n\nCurrent configuration :
...
766
ios_facts
$ ansible-playbook 3_ios_facts.yml
767
ios_config
Модуль ios_config
Модуль ios_config позволяет настраивать устройства под управлением IOS, а также
генерировать шаблоны конфигураций или отправлять команды на основании шаблона.
Параметры модуля:
768
ios_config
769
ios_config
lines (commands)
Самый простой способ использовать модуль ios_config - отправлять команды
глобального конфигурационного режима с параметром lines.
Для параметра lines есть alias commands, то есть, можно вместо lines писать
commands.
---
tasks:
$ ansible-playbook 1_ios_config_lines.yml
terminal length 0
enable
show running-config - чтобы проверить, есть ли эта команда на устройстве. Если
770
ios_config
команда есть, задача выполняться не будет. Если команды нет, задача выполнится
если команды, которая указана в задаче, нет в конфигурации:
configure terminal
service password-encryption
end
Так как модуль каждый раз проверяет конфигурацию, прежде чем применит команду,
модуль идемпотентен. То есть, если ещё раз запустить playbook, изменения не будут
выполнены:
$ ansible-playbook 1_ios_config_lines.yml
771
ios_config
---
tasks:
Результат выполнения:
$ ansible-playbook 1_ios_config_mult_lines.yml
772
ios_config
parents
Параметр parents используется, чтобы указать, в каком подрежиме применить
команды.
line vty 0 4
login local
transport input ssh
---
tasks:
$ ansible-playbook 2_ios_config_parents_basic.yml
773
ios_config
policy-map OUT_QOS
class class-default
shape average 100000000 1000000
---
tasks:
774
ios_config
Отображение обновлений
В этом разделе рассматриваются варианты отображения информации об
обновлениях, которые выполнил модуль ios_config.
Playbook 2_ios_config_parents_basic.yml:
---
tasks:
Для того, чтобы playbook что-то менял, нужно сначала отменить команды - либо
вручную, либо изменив playbook. Например, на маршрутизаторе 192.168.100.1
вместо строки transport input ssh вручную прописать строку transport input all.
$ ansible-playbook 2_ios_config_parents_basic.yml -v
775
ios_config
Обратите внимание, что команда login local не отправлялась, так как она настроена.
Поле updates в выводе есть только в том случае, когда есть изменения.
---
tasks:
Изменения в playbook:
Если запустить повторно playbook, когда изменений не было, задача Show config
updates пропускается:
$ ansible-playbook 3_ios_config_debug.yml
776
ios_config
$ ansible-playbook 3_ios_config_debug.yml
Теперь второе задание отображает информацию о том, какие именно изменения были
внесены на маршрутизаторе.
777
ios_config
778
ios_config
save_when
Параметр save_when позволяет указать, нужно ли сохранять текущую конфигурацию
в стартовую.
always - всегда сохранять конфигурацию (в этом случае флаг modified будет равен
True)
never (по умолчанию) - не сохранять конфигурацию
modified - в этом случае конфигурация сохраняется только при наличии изменений
Playbook 4_ios_config_save_when.yml:
779
ios_config
---
tasks:
Выполнение playbook:
$ ansible-playbook 4_ios_config_save_when.yml
780
ios_config
781
ios_config
backup
Параметр backup указывает, нужно ли делать резервную копию текущей
конфигурации устройства перед внесением изменений. Файл будет копироваться в
каталог backup относительно каталога, в котором находится playbook (если каталог не
существует, он будет создан).
Playbook 5_ios_config_backup.yml:
---
tasks:
Теперь каждый раз, когда выполняется playbook (даже если не нужно вносить
изменения в конфигурацию), в каталог backup будет копироваться текущая
конфигурация:
$ ansible-playbook 5_ios_config_backup.yml -v
782
ios_config
В каталоге backup теперь находятся файлы такого вида (при каждом запуске playbook
они перезаписываются):
192.168.100.1_config.2016-12-10@10:42:34
192.168.100.2_config.2016-12-10@10:42:34
192.168.100.3_config.2016-12-10@10:42:34
При работе с Python 3, может возникнуть ошибка "RuntimeError: dictionary changed size
during iteration". Ее можно исправить вручную. Для этого надо запустить playbook с
опцией -vvv и посмотреть где находится модуль ios_config. В выводе также будет
информация о том в какой строке ошибка.
Пример ошибки:
File "/home/vagrant/venv/py3_convert/lib/python3.6/site-packages/ansible/plugins/actio
n/ios_config.py", line 57, in run
for key in result.keys():
RuntimeError: dictionary changed size during iteration
Для исправления, надо в указанной строке сменить for key in result.keys(): на for
key in list(result.keys()): .
783
ios_config
defaults
Параметр defaults указывает, нужно ли собирать всю информацию с устройства, в том
числе и значения по умолчанию. Если включить этот параметр, модуль будет собирать
текущую конфигурацию с помощью команды sh run all. По умолчанию этот параметр
отключен, и конфигурация проверяется командой sh run.
Этот параметр полезен в том случае, если в настройках указывается команда, которая
не видна в конфигурации. Например, такое может быть, когда указан параметр,
который и так используется по умолчанию.
Происходит это потому, что Ansible каждый раз вначале проверяет наличие команд в
соответствующем режиме. Если команд нет, то соответствующая задача выполняется.
---
tasks:
Если добавить параметр defaults: yes , изменения уже не будут внесены, если не
хватало только команды ip mtu 1500 (playbook 6_ios_config_defaults.yml):
784
ios_config
---
tasks:
Запуск playbook:
$ ansible-playbook 6_ios_config_defaults.yml
785
ios_config
after
Параметр after указывает, какие команды выполнить после команд в списке lines (или
commands).
Но, если написать команду no shutdown в списке after, то она будет применена только
в том случае, если нужно вносить изменения (согласно списка lines).
---
tasks:
$ ansible-playbook 7_ios_config_after.yml -v
786
ios_config
$ ansible-playbook 7_ios_config_after.yml -v
---
tasks:
787
ios_config
$ ansible-playbook 7_ios_config_after_save.yml -v
788
ios_config
before
Параметр before указывает, какие действия выполнить до команд в списке lines.
При этом, как и after, параметр before не влияет на то, какие команды сравниваются с
конфигурацией. То есть, по-прежнему сравниваются только команды в списке lines.
Playbook 8_ios_config_before.yml:
---
tasks:
Таким образом, в ACL всегда находятся только те строки, которые заданы в списке
lines.
$ ansible-playbook 8_ios_config_before.yml -v
789
ios_config
$ ansible-playbook 8_ios_config_before.yml -v
790
ios_config
match
Параметр match указывает, как именно нужно сравнивать команды (что считается
изменением):
match: line
Режим match: line используется по умолчанию.
В этом режиме модуль проверяет только наличие строк, перечисленных в списке lines
в соответствующем режиме. При этом не проверяется порядок строк.
---
tasks:
791
ios_config
$ ansible-playbook 9_ios_config_match_line.yml -v
Обратите внимание, что в списке updates только две из трёх строк ACL. Так как в
режиме lines модуль сравнивает команды независимо друг от друга, он обнаружил, что
не хватает только двух команд из трех.
То есть, порядок команд поменялся. И хотя в этом случае это не важно, иногда это
может привести совсем не к тем результатам, которые ожидались.
match: exact
Пример, в котором порядок команд важен.
ACL на маршрутизаторе:
792
ios_config
---
tasks:
$ ansible-playbook 9_ios_config_match_exact.yml -v
Можно добавить к этому playbook параметр before и сначала удалить ACL, а затем
применять команды:
793
ios_config
---
tasks:
$ ansible-playbook 9_ios_config_match_exact.yml -v
794
ios_config
И, соответственно, на маршрутизаторе:
Модуль проверил, каких команд не хватает в ACL (так как режим по умолчанию
match: line),
обнаружил, что не хватает команды permit icmp any any , и добавил её
Но, так как в playbook ACL сначала удаляется, а затем применяется список команд
lines, получилось, что в итоге в ACL одна строка.
---
tasks:
$ ansible-playbook 9_ios_config_match_exact.yml -v
795
ios_config
То есть, теперь ACL выглядит точно так же, как и строки в списке lines, и в том же
порядке.
И для того, чтобы окончательно разобраться с параметром match: exact , ещё один
пример.
796
ios_config
---
tasks:
То есть, последние 4 строки выглядят так, как нужно, и в том порядке, котором нужно.
Но, при этом, есть лишняя строка. Для варианта match: exact - это уже несовпадение.
В таком варианте, playbook будет выполняться каждый раз и пытаться применить все
команды из списка lines, что не будет влиять на содержимое ACL:
$ ansible-playbook 9_ios_config_match_exact.yml -v
797
ios_config
Это значит, что при использовании match:exact важно, чтобы был какой-то способ
удалить конфигурацию, если она не соответствует тому, что должно быть (или чтобы
команды перезаписывались). Иначе эта задача будет выполняться каждый раз при
запуске playbook.
match: strict
Вариант match: strict не требует, чтобы объект был в точности как указано в задаче,
но команды, которые указаны в списке lines, должны быть в том же порядке.
Если указан список parents, команды в списке lines должны идти сразу за командами
parents.
Playbook 9_ios_config_match_strict.yml:
798
ios_config
---
tasks:
Выполнение playbook:
$ ansible-playbook 9_ios_config_match_strict.yml -v
match: none
Использование match: none отключает идемпотентность задачи: каждый раз при
выполнении playbook будут отправляться команды, которые указаны в задаче.
799
ios_config
---
tasks:
$ ansible-playbook 9_ios_config_match_none.yml -v
800
ios_config
replace
Параметр replace указывает, как именно нужно заменять конфигурацию:
replace: line
Режим replace: line - это режим работы по умолчанию. В этом режиме, если были
обнаружены изменения, отправляются только недостающие строки.
---
tasks:
Выполнение playbook:
801
ios_config
$ ansible-playbook 10_ios_config_replace_line.yml -v
В данном случае модуль проверил, каких команд не хватает в ACL (так как режим по
умолчанию match: line), обнаружил, что не хватает команды deny ip any any , и
добавил её. Но, так как ACL сначала удаляется, а затем применяется список команд
lines, получилось, что у нас теперь ACL с одной строкой.
replace: block
В режиме replace: block отправляются все команды из списка lines (и parents), если
на устройстве нет хотя бы одной из этих команд.
ACL на маршрутизаторе:
Playbook 10_ios_config_replace_block.yml:
802
ios_config
---
tasks:
Выполнение playbook:
$ ansible-playbook 10_ios_config_replace_block.yml -v
803
ios_config
804
ios_config
src
Параметр src позволяет указывать путь к файлу конфигурации или шаблону
конфигурации, которую нужно загрузить на устройство.
Этот параметр взаимоисключающий с lines (то есть, можно указывать или lines, или
src). Он заменяет модуль ios_template, который скоро будет удален.
Конфигурация
Пример playbook 11_ios_config_src.yml:
---
tasks:
$ ansible-playbook 11_ios_config_src.yml -v
805
ios_config
Если запустить playbook ещё раз, то никаких изменений не будет, так как этот
параметр также идемпотентен:
$ ansible-playbook 11_ios_config_src.yml -v
Шаблон Jinja2
В параметре src можно указывать шаблон Jinja2.
806
ios_config
router ospf 1
router-id {{ mgmnt_ip }}
ispf
auto-cost reference-bandwidth 10000
{% for ip in ospf_ints %}
network {{ ip }} 0.0.0.0 area 0
{% endfor %}
В каталоге host_vars нужно создать такие файлы (если они ещё не созданы):
Файл host_vars/192.168.100.1:
---
hostname: london_r1
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.1
ospf_ints:
- 192.168.100.1
- 10.0.0.1
- 10.255.1.1
Файл host_vars/192.168.100.2:
---
hostname: london_r2
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.2
ospf_ints:
- 192.168.100.2
- 10.0.0.2
- 10.255.2.2
Файл host_vars/192.168.100.3:
807
ios_config
---
hostname: london_r3
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.3
ospf_ints:
- 192.168.100.3
- 10.0.0.3
- 10.255.3.3
---
tasks:
Так как Ansible сам найдет переменные в каталоге host_vars, их не нужно указывать.
Можно сразу запускать playbook:
$ ansible-playbook 11_ios_config_src_jinja.yml -v
808
ios_config
router ospf 1
router-id 10.0.0.3
ispf
auto-cost reference-bandwidth 10000
network 10.0.0.3 0.0.0.0 area 0
network 10.255.3.3 0.0.0.0 area 0
network 192.168.100.3 0.0.0.0 area 0
809
ios_config
$ ansible-playbook 11_ios_config_src_jinja.yml -v
backup
config
defaults
save (но у самого save в Ansible 2.2 проблемы с работой)
810
ntc_ansible
ntc-ansible
ntc-ansible - это модуль для работы с сетевым оборудованием, который не только
выполняет команды на оборудовании, но и обрабатывает вывод команд и преобразует
с помощью TextFSM.
Этот модуль не входит в число core модулей Ansible, поэтому его нужно установить.
Но прежде нужно указать Ansible, где искать сторонние модули. Указывается путь в
файле ansible.cfg:
[defaults]
inventory = ./myhosts
remote_user = cisco
ask_pass = True
library = ./library
[~/pyneng_course/chapter15/library]
$ git clone https://github.com/networktocode/ntc-ansible --recursive
Cloning into 'ntc-ansible'...
remote: Counting objects: 2063, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 2063 (delta 1), reused 0 (delta 0), pack-reused 2058
Receiving objects: 100% (2063/2063), 332.15 KiB | 334.00 KiB/s, done.
Resolving deltas: 100% (1157/1157), done.
Checking connectivity... done.
Submodule 'ntc-templates' (https://github.com/networktocode/ntc-templates) registered
for path 'ntc-templates'
Cloning into 'ntc-templates'...
remote: Counting objects: 902, done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 902 (delta 16), reused 0 (delta 0), pack-reused 868
Receiving objects: 100% (902/902), 161.11 KiB | 0 bytes/s, done.
Resolving deltas: 100% (362/362), done.
Checking connectivity... done.
Submodule path 'ntc-templates': checked out '89c57342b47c9990f0708226fb3f268c6b8c1549'
811
ntc_ansible
Так как в текущей версии Ansible уже есть модули, которые работают с сетевым
оборудованием и позволяют выполнять команды, из всех возможностей ntc-ansible
наиболее полезной будет отправка команд show и получение структурированного
вывода. За это отвечает модуль ntc_show_command.
ntc_show_command
Модуль использует netmiko для подключения к оборудованию (netmiko должен быть
установлен) и, после выполнения команды, преобразует вывод команды show с
помощью TextFSM в структурированный вывод (список словарей).
Преобразование будет выполняться в том случае, если в файле index была найдена
команда, и для команды был найден шаблон.
connection - тут возможны два варианта: ssh (подключение netmiko) или offline
(чтение из файла для тестовых целей)
platform - платформа, которая существует в index файле (library/ntc-ansible/ntc-
templates/templates/index)
command - команда, которую нужно выполнить на устройстве
host - IP-адрес или имя устройства
username - имя пользователя
password - пароль
template_dir - путь к каталогу, в котором находятся шаблоны (в текущем варианте
установки они находятся в каталоге library/ntc-ansible/ntc-templates/templates
812
ntc_ansible
---
tasks:
- debug: var=result
$ ansible-playbook 1_ntc-ansible.yml
813
ntc_ansible
814
ntc_ansible
Start
^${INTF}\s+${IPADDR}\s+\w+\s+\w+\s+${STATUS}\s+${PROTO} -> Record
Для того, чтобы получить вывод про первый интерфейс, можно поменять вывод
модуля debug таким образом:
- debug: var=result.response[0]
---
tasks:
Результат выполнения:
815
ntc_ansible
$ ansible-playbook 2_ntc-ansible_save.yml
816
ntc_ansible
[
{
"intf": "Ethernet0/0",
"ipaddr": "192.168.100.1",
"proto": "up",
"status": "up"
},
{
"intf": "Ethernet0/1",
"ipaddr": "192.168.200.1",
"proto": "up",
"status": "up"
},
{
"intf": "Ethernet0/2",
"ipaddr": "unassigned",
"proto": "down",
"status": "administratively down"
},
{
"intf": "Ethernet0/3",
"ipaddr": "unassigned",
"proto": "up",
"status": "up"
},
{
"intf": "Loopback0",
"ipaddr": "10.1.1.1",
"proto": "up",
"status": "up"
}
]
Шаблоны Jinja2
Для Cisco IOS в ntc-ansible есть такие шаблоны:
817
ntc_ansible
cisco_ios_dir.template
cisco_ios_show_access-list.template
cisco_ios_show_aliases.template
cisco_ios_show_archive.template
cisco_ios_show_capability_feature_routing.template
cisco_ios_show_cdp_neighbors_detail.template
cisco_ios_show_cdp_neighbors.template
cisco_ios_show_clock.template
cisco_ios_show_interfaces_status.template
cisco_ios_show_interfaces.template
cisco_ios_show_interface_transceiver.template
cisco_ios_show_inventory.template
cisco_ios_show_ip_arp.template
cisco_ios_show_ip_bgp_summary.template
cisco_ios_show_ip_bgp.template
cisco_ios_show_ip_int_brief.template
cisco_ios_show_ip_ospf_neighbor.template
cisco_ios_show_ip_route.template
cisco_ios_show_lldp_neighbors.template
cisco_ios_show_mac-address-table.template
cisco_ios_show_processes_cpu.template
cisco_ios_show_snmp_community.template
cisco_ios_show_spanning-tree.template
cisco_ios_show_standby_brief.template
cisco_ios_show_version.template
cisco_ios_show_vlan.template
cisco_ios_show_vtp_status.template
ls -ls library/ntc-ansible/ntc-templates/templates/
818
ntc_ansible
819
Подробнее об Ansible
Подробнее об Ansible
Мы рассмотрели основные аспекты Ansible, которые нужны для работы с сетевым
оборудованием. Их достаточно, чтобы начать работать с Ansible, но, скорее всего, в
процессе работы Вам понадобится больше информации.
Например, как сделать так, чтобы не нужно было повторять одни и те же задачи или
сценарии снова и снова, или как организовывать более сложные playbook.
Всё это Ansible позволяет сделать, но это выходит за рамки этого курса. Эта
информация вынесена в отдельный курс Ansible для сетевых инженеров. Основы,
которые рассматриваются тут, в том курсе повторяются, поэтому, если Вы прочитали
весь раздел Ansible в этом курсе, можете начать сразу с четвертого раздела Playbook.
820
Дополнительные материалы
Дополнительные материалы
Ansible без привязки к сетевому оборудованию
У Ansible очень хорошая документация
Очень хорошая серия видео с транскриптом и хорошими ссылками
Примеры использования Ansible
Примеры Playbook с демонстрацией различных возможностей
Networking Support
Network Modules
Network Debug and Troubleshooting Guide
ios_command
ios_facts
ios_config
Модуль ntc-ansible
ntc-templates
Статьи:
https://pynet.twb-tech.com/blog/ansible/ansible-cfg-template.html
https://pynet.twb-tech.com/blog/ansible/ansible-cfg-template-p2.html
https://pynet.twb-tech.com/blog/ansible/ansible-cfg-template-p3.html
821
Дополнительные материалы
http://networkop.github.io/blog/2015/06/24/ansible-intro/
http://networkop.github.io/blog/2015/07/03/parser-modules/
http://networkop.github.io/blog/2015/07/10/test-verification/
http://networkop.github.io/blog/2015/07/17/tdd-quickstart/
http://networkop.github.io/blog/2015/08/14/automating-legacy-networks/
http://networkop.github.io/blog/2015/08/26/automating-network-build-p1/
http://networkop.github.io/blog/2015/09/03/automating-bgp-config/
http://networkop.github.io/blog/2015/11/13/automating-flexvpn-config/
http://jedelman.com/home/ansible-for-networking/
http://jedelman.com/home/network-automation-with-ansible-dynamically-configuring-
interface-descriptions/
http://www.packetgeek.net/2015/08/using-ansible-to-push-cisco-ios-configurations/
822
Задания
Задания
Все задания и вспомогательные файлы можно скачать в репозитории. Если в заданиях
раздела есть задания с буквами (например, 5.2a), то лучше выполнить сначала
задания без букв, а затем с буквами. Задания с буквами, как правило, немного
сложнее заданий без букв и развивают или усложняют идею в соответствующем
задании без буквы.
Например, в разделе есть задания 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала лучше
выполнить задания 5.1, 5.2, 5.3, а затем 5.2a, 5.2b, 5.3a
Задание 24.1
Создайте playbook task_24_1.yml, который выполняет такие задачи:
Задание 24.1a
Создайте playbook task_24_1a.yml, который выполняет такие задачи:
Задание 24.1b
Создайте playbook task_24_1b.yml, который выполняет такие задачи:
823
Задания
Задание 24.1c
Создайте playbook task_24_1c.yml, который выполняет такие задачи:
Вторая и третья задачи должны отображать вывод команды в виде списка строк.
Задание 24.2
Создайте playbook task_24_2.yml, который выполняет такие задачи:
Задание 24.2a
Создайте playbook task_24_2a.yml, который выполняет такие задачи:
Задание 24.2b
Создайте playbook task_24_2b.yml, который выполняет такие задачи:
824
Задания
Задание 24.3
В playbook task_15_3.yml описана одна задача.
Попробуйте выполнить его, как минимум, два раза. Обратите внимание, что изменения
вносились каждый раз.
Измените playbook таким образом, чтобы изменения вносились только в том случае,
когда настройка логирования на устройстве не соответствует указанной команде.
Задание 24.4
Создайте playbook task_24_4.yml, который выполняет такие задачи:
825
Задания
Задание 24.4a
Проверьте работу playbook из задания 24.4, в ситуации, когда в ACL добавлена ещё
одна строка.
Задание 24.4b
Добавьте в playbook из задания 24.4a ещё одну задачу:
Задание 24.4c
Измените playbook из задания 24.4b таким образом, чтобы имя интерфейса, который
указывается в задаче, указывалось как переменная outside_intf.
826
Дополнительная информация
Дополнительная информация
В этом разделе собрана информация, которая не вошла в основные разделы курса, но
которая, тем не менее, может быть полезна.
827
Соглашение об именах
Соглашение об именах
В Python есть определенные соглашения об именовании объектов.
Имена переменных
Имена переменных не должны пересекаться с операторами и названиями модулей
или других зарезервированных значений.
DB_NAME = 'dhcp_snooping.db'
TESTING = True
db_name = 'dhcp_snooping.db'
testing = True
Модули могут использовать подчеркивания между словами для того, чтобы имена
были более понятными. Для пакетов лучше выбирать короткие имена.
Имена функций
Имена функций задаются маленькими буквами, с подчеркиваниями между словами.
828
Соглашение об именах
ignore_command = False
Имена классов
Имена классов задаются словами с заглавными буквами, без пробелов.
class CiscoSwitch:
829
Подчеркивание в именах
Подчеркивание в именах
В Python подчеркивание в начале или в конце имени указывает на специальные
имена. Чаще всего это всего лишь договоренность, но иногда это действительно
влияет на поведение объекта.
Например, если из строки line надо получить MAC-адрес, IP-адрес, VLAN и интерфейс
и отбросить остальные поля, можно использовать такой вариант:
Такая запись говорит о том, что нам не нужны третий и четвертый элементы.
Подчеркивание в интерпретаторе
В интерпретаторе python и ipython подчеркивание используется для получения
результата последнего выражения
830
Подчеркивание в именах
In [7]: _
Out[7]: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
In [8]: a = _
In [9]: a
Out[9]: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Одно подчеркивание
Например, если одно подчеркивание указано в имени функции или метода, это
означает, что этот объект является внутренней особенностью реализации и не стоит
его использовать напрямую.
Но, кроме того, при импорте вида from module import * не будут импортироваться
db_name = 'dhcp_snooping.db'
_path = '/home/nata/pyneng/'
def func1(arg):
print arg
def _func2(arg):
print arg
831
Подчеркивание в именах
In [8]: db_name
Out[8]: 'dhcp_snooping.db'
In [9]: _path
...
NameError: name '_path' is not defined
In [10]: func1(1)
1
In [11]: _func2(1)
...
NameError: name '_func2' is not defined
Пример:
Два подчеркивания
Такое преобразование выполняется только в том случае, если в конце менее двух
подчеркиваний или нет подчеркиваний.
832
Подчеркивание в именах
In [15]: dir(Switch)
Out[15]:
['_Switch__configure', '_Switch__quantity', ...]
In [17]: dir(CiscoSwitch)
Out[17]:
['_CiscoSwitch__configure', '_CiscoSwitch__quantity', '_Switch__configure', '_Switch__
quantity', ...]
833
Подчеркивание в именах
return a * b
if __name__ == '__main__':
print(multiply(3, 5))
import os
print('__file__', __file__)
print(os.path.abspath(__file__))
__file__ example2.py
/home/vagrant/repos/tests/example2.py
Кроме того, таким образом в Python обозначаются специальные методы. Эти методы
вызываются при использовании функций и операторов Python и позволяют
реализовать определенный функционал.
Как правило, такие методы не нужно вызывать напрямую. Но, например, при создании
своего класса может понадобиться описать такой метод, чтобы объект поддерживал
какие-то операции в Python.
Например, для того, чтобы можно было получить длину объекта, он должен
поддерживать метод __len__ .
Ещё один специальный метод __str__ вызывается, когда используется оператор print
или вызывается функция str(). Если необходимо, чтобы при этом отображение было в
определенном виде, надо создать этот метод в классе:
834
Подчеркивание в именах
In [12]: sw1.set_name('sw1')
In [14]: str(sw1)
Out[14]: 'Switch sw1'
Таких специальных методов в Python очень много. Несколько полезных ссылок, где
можно почитать про конкретный метод:
документация
Dive Into Python 3
835
Полезные функции
lambda
map
filter
836
Функция lambda
Стандартная функция:
In [2]: sum_arg(1,2)
Out[2]: 3
In [4]: sum_arg(1,2)
Out[4]: 3
Обратите внимание, что в определении lambda нет оператора return, так как в этой
функции может быть только одно выражение, которое всегда возвращает значение и
завершает работу функции.
Например, в функции sorted lambda можно использовать для указания ключа для
сортировки:
837
Функция lambda
838
Функция map
Функция map
Функция map применяет функцию к каждому элементу последовательности и
возвращает итератор с результатами.
Конвертация в числа:
Если функция, которую использует map(), ожидает два аргумента, то передаются два
списка:
839
Функция map
Как правило, вместо map можно использовать list comprehension. Чаще всего, вариант
с list comprehension более понятный, а в некоторых случаях даже быстрее.
Но map может быть эффективней в том случае, когда надо сгенерировать большое
количество элементов, так как map - итератор, а list comprehension генерирует список.
Конвертация в числа:
Форматирование строк:
840
Функция filter
Функция filter
Функция filter() применяет функцию ко всем элементам последовательности и
возвращает итератор с теми объектами, для которых функция вернула True.
In [1]: list_of_strings = ['one', 'two', 'list', '', 'dict', '100', '1', '50']
In [4]: list(filter(lambda x: not x%2, [10, 111, 102, 213, 314, 515]))
Out[4]: [10, 102, 314]
Из списка слов оставить только те, у которых количество букв больше двух:
841
Функция filter
In [7]: list_of_strings = ['one', 'two', 'list', '', 'dict', '100', '1', '50']
Нечетные/четные числа:
Из списка слов оставить только те, у которых количество букв больше двух:
842
Основы threading и multiprocessing
843
Модуль threading
Модуль threading
Модуль threading может быть полезен для таких задач:
Так как для работы с threading удобнее использовать функции, код изменен:
Файл netmiko_function.py:
844
Модуль threading
import sys
import yaml
from netmiko import ConnectHandler
#COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
result = ssh.send_config_set(commands)
print(result)
commands_to_send = ['logg 10.1.12.3', 'ip access-li ext TESST2', 'permit ip any any']
routers:
- device_type: cisco_ios
ip: 192.168.100.1
username: cisco
password: cisco
secret: cisco
- device_type: cisco_ios
ip: 192.168.100.2
username: cisco
password: cisco
secret: cisco
- device_type: cisco_ios
ip: 192.168.100.3
username: cisco
password: cisco
secret: cisco
845
Модуль threading
import sys
import yaml
import threading
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for th in threads:
th.join()
846
Модуль threading
...
real 0m2.229s
user 0m0.408s
sys 0m0.068s
Время почти в три раза меньше. Но надо учесть, что такая ситуация не будет
повторяться при большом количестве подключений.
метод join выполняется для каждого потока в списке. Таким образом, основная
программа завершится, только когда завершат работу все потоки
по умолчанию join ждет завершения работы потока бесконечно. Но можно
ограничить время ожидания, передав join время в секундах. В таком случае
join завершится после указанного количества секунд.
В Python есть модуль queue, который позволяет создавать разные типы очередей.
847
Модуль threading
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for th in threads:
th.join()
results = []
# Берем результаты из очереди и добавляем их в список results
for t in threads:
results.append(q.get())
return results
848
Модуль threading
Очередь лучше тем, что она поддерживает только две операции по изменению
содержимого:
849
Модуль threading
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for th in threads:
th.join()
return q
850
Модуль multiprocessing
Модуль multiprocessing
Модуль multiprocessing использует интерфейс, подобный модулю threading. Поэтому
перенести код с использования потоков на использование процессов обычно
достаточно легко.
Каждому процессу выделяются свои ресурсы. Кроме того, у каждого процесса свой
GIL, а значит, нет тех проблем, которые были с потоками, и код может выполняться
параллельно и задействовать ядра/процессоры компьютера.
851
Модуль multiprocessing
import multiprocessing
import sys
import yaml
from pprint import pprint
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for p in processes:
p.join()
results = []
for p in processes:
results.append(queue.get())
return results
Если проверить время исполнения этого скрипта, аналогичного для модуля threading и
последовательного подключения, то получаем такую картину:
852
Модуль multiprocessing
последовательное: 5.833s
threading: 2.225s
multiprocessing: 2.365s
Время выполнения для модуля multiprocessing немного больше. Но это связано с тем,
что на создание процессов уходит больше времени, чем на создание потоков. Если бы
скрипт был сложнее и выполнялось больше задач, или было бы больше подключений,
тогда бы multiprocessing начал бы существенно выигрывать у модуля threading.
853
Дополнительные материалы
Дополнительные материалы
Документация:
threading
multiprocessing
queue
time
datetime
GIL
Can’t we get rid of the Global Interpreter Lock?
GIL (на русском)
Understanding the Python GIL
Python threads and the GIL
854
Отличия Python 2.7 и Python 3
Кроме отличий, которые сделаны из-за изменений в Python 3, в книге также обновлены
многие разделы.
Unicode
В Python 2.7 было два типа строк: str и unicode:
В Python 3 строка - это тип str, но, кроме этого, в Python 3 появился тип bytes:
In [4]: line.encode('utf-8')
Out[4]: b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82'
Функция print
В Python 2.7 print был оператором:
855
Отличия Python 2.7 и Python 3
В Python 2.7 можно брать аргументы в скобки, но от этого print не становится функцией
и, кроме того, print возвращает другой результат (кортеж):
In [11]: number
Out[11]: '55'
In [13]: number
Out[13]: '55'
856
Отличия Python 2.7 и Python 3
In [14]: range(5)
Out[14]: [0, 1, 2, 3, 4]
In [15]: xrange(5)
Out[15]: xrange(5)
In [16]: list(xrange(5))
Out[16]: [0, 1, 2, 3, 4]
In [17]: range(5)
Out[17]: range(0, 5)
In [18]: list(range(5))
Out[18]: [0, 1, 2, 3, 4]
Методы словарей
Несколько изменений произошло в методах словарей.
In [20]: d.
d.clear d.get d.iteritems d.keys d.setdefault d.viewitems
d.copy d.has_key d.iterkeys d.pop d.update d.viewkeys
d.fromkeys d.items d.itervalues d.popitem d.values d.viewvalues
И в Python 3:
857
Отличия Python 2.7 и Python 3
In [22]: d.
clear() get() pop() update()
copy() items() popitem() values()
fromkeys() keys() setdefault()
Распаковка переменных
В Python 3 появилась возможность использовать * при распаковке переменных:
In [24]: a
Out[24]: 1
In [25]: b
Out[25]: [2, 3, 4]
In [26]: c
Out[26]: 5
858
Отличия Python 2.7 и Python 3
subprocess.run
В версии Python 3.5 в модуле subprocess появилась новая функция - run. Она
предоставляет более удобный интерфейс для работы с модулем и получения вывода
команд.
Jinja2
В модуле Jinja2 больше не нужно использовать такой код, так как кодировка по
умолчанию и так utf-8:
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
Мелочи
Название модуля Queue сменилось на queue
С версии Python 3.6 объект csv.DictReader возвращает OrderedDict вместо
обычного словаря.
859
Отличия Python 2.7 и Python 3
Дополнительная информация
Ниже приведены ссылки на ресурсы с информацией об изменениях в Python 3.
Документация:
Статьи:
The key differences between Python 2.7.x and Python 3.x with examples
Supporting Python 3: An in-depth guide
860
Продолжение обучения
Продолжение обучения
Как правило, информацию тяжело усвоить с первого раза. Особенно, новую
информацию.
Книга дает лишь основы Python и поэтому надо обязательно продолжать учиться и
повторять уже пройденные темы и изучать новое. И тут есть множество вариантов:
Тут ресурсы перечислены выборочно, с учетом того, что Вы уже прочитали книгу.
Но, кроме этого, я сделала подборку ресурсов в которой можно найти и другие
материалы.
Но зная Python лучше, те же задачи можно решать, как правило, намного проще.
Поэтому лучше не останавливаться и учиться дальше.
861
Продолжение обучения
Kirk Byers
Jason Edelman
Matt Oswalt
Michael Kashin
Henry Ölsner
Mat Wood
Проекты:
862
Продолжение обучения
more
Курсы:
Сайты с задачами:
Talk Python To Me
Best Python Podcasts
Документация:
863
Продолжение обучения
864
Отзывы
Вот и пролетели 3 месяца с первого занятия и пора писать отзыв. Как только я
услышал про этот курс и что его ведет та самая Наташа Самойленко, я подумал вот
оно! Как было бы здорово у нее поучится и ожидания оправдались на 200%!
Что бы чему-то научиться нужно идти к лучшим, людям страстно одержимым своим
делом, на наше счастье Наташа одинаково увлечена и программированием, и
желанием научить нас, огромное тебе спасибо за это!!!
Сергей Забабурин
865
Отзывы
Осталось три темы и я закончу воркшоп "Питон для сетевых инженеров" от Наташи
Самойленко. Курс шел с сентября по ноябрь, 13 недель.
Я как всегда отжег: оплатил, ничего не делал и начал заниматься, когда группа прошла
половину. Однако, начав, уже не смог оторваться и топлю до финала. Наташа, если
вдруг не знаете, написала чуть меньше чем весь сетевой раздел XGU.ru ,
автоматизировала проект CCIE за год и написала для него первую "большую лабу".
Послушать ее чарующий голос можно в клубе 256-TTL
В курсе сам Python, базы SQL, форматы YAML, JSON, шаблоны Jinja2 и, конечно,
Ansible. 50 часов видео и 114 заданий для самостоятельной работы.
И наконец, что действительно важно. Наташа заявляет: питон это просто и после курса
Вы будете его использовать в повседневной работе. Так и происходит. Начиная с азов
и включая серьезные вещи (например потоки, производительность кода) шаг за шагом
она выращивает в участнике навыки и знания с которыми можно идти и работать. Это
курс для практиков, примеры из реальной жизни, материал подобран и сверстан так,
866
Отзывы
что все нужное и всего достаточно. Если Вам требуется эффективно, в понятные сроки
и за умеренную плату (сейчас это 250$) закрыть вопрос с питоном - то Вам сюда:
natenka.github.io/pyneng-online
ЗЫ: Если Вам не нужно быстро и Вы ходите как я два года сидеть и сожалеть на тему
"как плохо не уметь писать скрипты для Juniper" то совсем даром на сайте выложена
книга и Git c примерами и готовыми виртуалками для самостоятельных штудий.
Александр Романов
Когда у меня стали отнимать слишком много времени рутинные задачи. Когда
коэффициент подошёл к восьмидесяти процентам я понял что нужно что то менять. Я
пробовал изучать сам , но из за недостатка времени у меня не получалось. И в один
прекрасный день я натолкнулся на курс который предлагала Наташа Самойленко. С
первых занятий я понял что этот курс ни в какой сравнение с теми которые у меня
бывали раньше. Имея большой опыт в администрировании сетей показывала
подходящие примеры для быстрейшего усвоения нами материалов и применения их в
работе. И всегда старалась консультировать и отвечать на вопросы в отличии от
других преподавателей на курсах которых я проходил ранее. Думаю этот курс прочно
займет первое место среди всех курсов которые я проходил (наверное пока Наташа не
выпустит что то новое). Думаю после этого запатентовать бренд Natasha Samoylenko
как будущий сертификат качества.
Денис Карпушин
Все что Вы хотели знать о Python, но боялись спросить.
Все обучение построено в очень удобной форме, и каждый найдет для себя
подходящий вариант. Доступны живые лекции, записи, книга, непосредственно
общение с преподавателем. Можно комбинировать как Вам удобно, чтобы обучение
было в радость. Атмосфера почти домашная (Спасибо Наташе).
Евгений Овчинников
867
Отзывы
В октябре 2016 года я решил стать программистом и начал учиться. Покупал платные
курсы от одного очень навязчивого своей рекламой образовательного российского
интернет-ресурса. После нескольких месяцев обучения стало понятно, что прогресс
хоть и есть, но довольно скромный.
Ключевой проблемой было то, что изучаемые материалы либо были слабо применимы
к моей работе (инженер техподдерки в небольшом провайдере), либо неприменимы
совсем. Оставалось только время в вечернее время и выходные дни. Постепенно я
уставал и терял мотивацию что-то делать. Самовнушение не помогало. Вероятно я
ошибся с выбором направления (веб-разработка), или не сумел выделить нормальное
количество времени на самостоятельную работу. На все это накладывалась сильная
загруженность на новой работе - необходимо было быстро усваивать массу
материала. К лету 2017 процесс стал совсем - я пытался что-то делать, но уже не
было сил и желания. Пройдя примерно половину курса по специальности Веб-
разработчик я понял, что больше не хочу этим заниматься и забил. Было грустно
В конце августа начались занятия. Наташа очень аккуратно подводила нас к мысли,
что Питона бояться не надо. Что она за нас уже все инструменты подготовила. Что на
сайте курса есть расписание курса, ссылки на задания по курсу, ссылки на материалы
по занятиям в книге, ссылки на презентации по курсу, ссылки на репозиторий курса,
ссылки на статьи о правильном обучении, ссылки на полезные ссылки в интернете... В
общем студенту осталось только сесть перед монитором в назначенное время.
Которое Наташа обозначила в календаре Google и предложила всем себе его
добавить.
868
Отзывы
Домашняя работа. Тут Наташа тоже подошла необычно: есть необходимый минимум
заданий, которые нужно выполнить для получения сертификата. И есть максимум,
который студенты делают по желанию. В любом случае практика программирования у
студентов набирается достаточно большой. А уж если Вы осилите максимум, то честь
Вам и почет! Я пока еще не осилил. Надежда тает с каждым днем, но пока есть :)
Все остальные материалы мне понравились. Может быть надо можно углубить Jinja и
TextFSM. Материала по ним немало, но мне показалось, что можно сделать побольше,
т.к. шаблонизация в современном программировании очень важная вещь.
ЗЫ. Курсы с того образовательного портала надо бы пройти до конца - оплачено ведь.
Но так неохота и лень
Олег Востриков
869
Отзывы
Лично мне курс пришелся как нельзя кстати, уже к середине курса я начал пытаться
автоматизировать рабочие задачи. К окончанию курса это вылилось в настройку
десятков тысяч устройств. Думаю не трудно представить, сколько времени
потребовалось бы, чтобы повторить это вручную.
Эмиль Гарипов
Чтобы понять почему именно этот курс и почему именно с Наташей мне надо
упомянуть о себе любимом. Я не разу не программист и никогда им не был.
Программисты всегда вызывали у меня уважение и долю здоровой зависти, когда за
пару часов работы могли реализовать все твои хотелки. У меня всегда возникала ужас,
когда дело касалось программирования в школе, в университете или уже позже, когда
я сам или с помощью других пытался освоить азы программирования вплоть до того
момента как Наташа предложила пройти мне её курс.
Скажу честно, этот курс заставил мои мозги напрячься, но я давно не получал такого
удовлетворения от процесса и самое главное избавился от фобии программирования.
Рекомендую пройти этот курс и если есть какие-то сомнения, отбросить их, Вы не
870
Отзывы
Для большинства из нас это было первое знакомство с python. Но благодаря отличной
подаче материала, а так же заданиям с разным уровнем сложности, обучение
проходило весьма интересно и продуктивно. К сожалению, не все темы нашли
применение в нашей работе, но главная цель была достигнута - мы начали создавать
систему автоматизированного тестирования. Причем эти знания пригодились не
только для одной конкретной задачи, но также позволили решить множество рутинных
задач. А из некоторых скриптов выросли отдельные проекты.
871
Отзывы
непонимания как я могу это применить. В курсе же практически на каждую тему есть
задачи, по которым Вы видите практическое применение того или иного объекта
языка.
После курса в сознании очень четко выстраивается картина того, куда еще расти и на
какие темы стоит обратить внимание. Область применения средств автоматизации не
просто обширна, а необъятна, и всегда есть, что еще изучить, но если Вы хотели
познакомиться с Python или автоматизацией в целом, но не знали, откуда
подступиться – настоятельно рекомендую
872
Отзывы
За это спасибо лабораторкам в курсе и тестам после лекции, где задач на регулярные
выражения было с избытком. Вообще практика построена очень удачным образом -
она опирается на то, что было в лекциях, но добавляет существенные детали.
Получается, что выполняя задания, не только практикуешь известную теорию, но и с
неожиданной стороны видишь то, что казалось понятным. Лабораторки с изюминкой.
Лекции тоже важны. Несмотря на то, что они чётко идут по галавам книги, в них
огромное количество отступлений, комментариев и фирменного наташиного юмора.
Один совет: не стесняйтесь задавать вопросы. Лектору очень важно понимать, что
аудитория, тем более онлайн, следит за ним.
873
Отзывы
Уже по прошествии 6-и глав (а это буквально пару недель занятий) , мне подвернулись
две рабочих задачи, которые были решены в кратчайшие сроки:
1. Перенос конфигурации NAT для более 1500 трансляций с Cisco IOS на FortiGate
(т.е. абсолютно другой формат конфига).
2. И проверка работы системы фильтрации web-запросов.
В комплекте с курсом идёт тёплый приём в команде в Slack, где всегда можно
спросить совета или поделиться своими наработками.
Разумеется один курс и 2 месяца вашей жизни не сделают из Вас Киану Ривза в
Python, но его более чем достаточно, чтобы понять, какие возможности он может Вам
дать. И как по мне, это безумно интересно =)
Да, наверное, можно было бы или ещё меньше написать или наоборот "воды налить",
но я считаю, что человеку которому это нужно для конкретных задач и который уже с
чем-то подобным был знаком ранее это хороший вариант и отличный старт познания
Python'a. Для меня эта книга сейчас как шпаргалка. Всего в голове не удержишь, а так
я точно знаю, где и что быстро найти.
874
Отзывы
875