Xgu.ru теперь в Контакте  — приходите и подключайтесь.
Пока мы работаем над следующими видео, вы можете подключиться в Контакте. Познакомимся и обсудим новые страницы и ролики.

Vk-big.pngYoutube-big.jpeg

Python для сетевых инженеров

Материал из Xgu.ru

Перейти к: навигация, поиск
stub.png
Данная страница находится в разработке.
Эта страница ещё не закончена. Информация, представленная здесь, может оказаться неполной или неверной.

Если вы считаете, что её стоило бы доработать как можно быстрее, пожалуйста, скажите об этом.

Автор: Наташа Самойленко

Python для сетевых инженеров - это курс в котором рассматриваются:

  • Основы программирования на языке Python
  • Использование Python для автоматизации базовых задач
  • Практические примеры использования в более сложных задачах
  • Полезные модули, утилиты, ресурсы

Курс не ставит своей целью глубокое рассмотрение языка Python. Задача курса, объяснить понятным языком основы Python и дать минимально необходимые инструменты для его практического использования.

Icon-caution.gif

Желательно всё, что происходит в командной строке и в интерпретаторе, повторять.

Многие вещи будут очень базовые, но все же лучше не просто читать статью, а и делать все это параллельно.


На этой странице, по мере написания, будут выкладываться последовательно части курса.


Содержание

[править] Подготовка к работе

[править] ОС и редактор

Для того чтобы начать работать с Python, надо определиться с несколькими вещами:

  • Какая OS будет использоваться
  • Какой редактор будет использоваться
  • Какая версия Python будет использоваться

Можно выбрать любую ОС и любой редактор. Но, желательно использовать Python версии 2.7, так как в этом курсе будет использоваться эта версия.

В курсе используется:

  • Debian Linux
  • vi (редактор не имеет принципиального значения, лучше выбрать наиболее удобный)
  • Python 2.7

Note-icon.gif

Установка Python 2.7, если его нет в ОС, выполняется самостоятельно.

Если вы не знаете какой редактор выбрать, попробуйте первый из списка для вашей ОС (vi/vim не указаны):

  • Linux:
    • gEdit
    • nano
    • Sublime Text
    • geany
  • Mac OS
    • TextWrangler
    • TextMate
  • Windows:
    • Notepad++

Note-icon.gif

Далее выводы команд, интерпретатора, скриптов, выполняются на Debian Linux.

В других ОС вывод может незначительно отличаться.

[править] Система управления пакетами pip

Для установки пакетов Python, которые понадобятся в курсе, будем использовать pip.

pip - это инструмент для установки пакетов из Python Package Index. А точнее, система управления пакетами.

PyPi (Python Package Index) это репозиторий пакетов Python. Он похож на RubyGems (Ruby), CPAN (Perl).

Скорее всего, если у вас уже установлен Python, то установлен и pip.

Проверяем pip:

nata@lab1:~$ pip --version
pip 1.1 from /usr/lib/python2.7/dist-packages (python 2.7)

Если команда выдала ошибку, значит pip не установлен. Установить его можно так:

apt-get install python-pip

Пока что, pip нам нужен будет, в первую очередь, для того чтобы поставить virtualenv.

Note-icon.gif

По ссылке можно посмотреть самые популярные пакеты PyPi: http://pypi-ranking.info/alltime

[править] virtualenv, virtualenvwrapper

Для того чтобы то, что мы будем делать на курсе, не повлияло на вашу систему в целом, мы будем использовать virtualenv.

virtualenv это инструмент, который позволяет создавать виртуальные окружения.

Виртуальные окружения:

  • позволяют изолировать различные проекты
  • зависимости, которых требуют разные проекты, находятся в разных местах
    • Например, если в проекте 1 требуется пакет версии 1.0, а в проекте 2 требуется тот же пакет, но версии 3.1
  • пакеты, которые установлены в виртуальных окружениях, не перебивают глобальные пакеты

Мы будем использовать virtualenvwrapper: он позволяет немного проще работать с virtualenv.

Устанавливаем virtualenvwrapper с помощью pip:

pip install virtualenvwrapper

В .bashrc надо добавить несколько строк (WORKON_HOME расположение виртуальных окружений; вторая строка - где находится скрипт, установленный с пакетом virtualenvwrapper):

export WORKON_HOME=~/venv

. /usr/local/bin/virtualenvwrapper.sh

[править] Работа с виртуальными окружениями

Создание нового виртуального окружения:

nata@lab1:~$ mkvirtualenv Py_for_NetEng
New python executable in Py_for_NetEng/bin/python
Installing distribute........................done.
Installing pip...............done.
(Py_for_NetEng)nata@lab1:~$ 

Теперь в скобках перед стандартным приглашением пишется имя проекта (виртуального окружения). То есть, мы не только создали новое виртуальное окружение, но и перешли в него.

Note-icon.gif

В virtualenvwrapper по Tab работает автопродолжение имени виртуального окружения.

Это особенно удобно в тех случаях, когда виртуальных окружений много.

Теперь в том каталоге, который был указан в WORKON_HOME, создан каталог Py_for_NetEng:

(Py_for_NetEng)nata@lab1:~$ ls -ls venv
total 52
....
4 -rwxr-xr-x 1 nata nata   99 Sep 30 16:41 preactivate
4 -rw-r--r-- 1 nata nata   76 Sep 30 16:41 predeactivate
4 -rwxr-xr-x 1 nata nata   91 Sep 30 16:41 premkproject
4 -rwxr-xr-x 1 nata nata  130 Sep 30 16:41 premkvirtualenv
4 -rwxr-xr-x 1 nata nata  111 Sep 30 16:41 prermvirtualenv
4 drwxr-xr-x 6 nata nata 4096 Sep 30 16:42 Py_for_NetEng

Выйти из виртуального окружения:

(Py_for_NetEng)nata@lab1:~$ deactivate 
nata@lab1:~$ 

Теперь для перехода в созданное виртуальное окружение надо выполнить команду workon:

nata@lab1:~$ workon Py_for_NetEng
(Py_for_NetEng)nata@lab1:~$ 

Если необходимо перейти из одного виртуального окружения в другое, то необязательно делать deactivate, можно перейти сразу через workon:

nata@lab1:~$ workon Test
(Test)nata@lab1:~$ workon Py_for_NetEng
(Py_for_NetEng)nata@lab1:~$ 

Если виртуальное окружение нужно удалить, используется команда rmvirtualenv:

nata@lab1:~$ rmvirtualenv Test
Removing Test...
nata@lab1:~$ 

Посмотреть какие пакеты установлены в виртуальном окружении:

(Py_for_NetEng)nata@lab1:~$ 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

Установим, к примеру, в нашем виртуальном окружении какой-то пакет (тут на примере simplejson). Теперь если перейти в ipython (рассматривается ниже) и импортировать simplejson, то он доступен и никаких ошибок нет:

(Py_for_NetEng)nata@lab1:~$ pip install simplejson
...
Successfully installed simplejson
Cleaning up...

(Py_for_NetEng)nata@lab1:~$ ipython

In [1]: import simplejson

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

Но, если выйти из виртуально окружения, и попытаться сделать то же самое, то такого модуля нет:

(Py_for_NetEng)nata@lab1:~$ deactivate 

nata@lab1:~$ ipython

In [1]: import simplejson
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-ac998a77e3e2> in <module>()
----> 1 import simplejson

ImportError: No module named simplejson

[править] Система управления версиями (Mercurial, Git, SVN)

[править] Интерпретатор Python (проверка)

Проверяем перед началом работы, что при вызове интерпретатора Python, вывод будет таким:

nata@lab1:~$ python
Python 2.7.3 (default, Mar 13 2014, 11:03:55) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Вывод показывает, что установлен Python 2.7. Приглашение >>> это стандартное приглашение интерпретатора Python.

Вызов интерпретатора выполняется по команде python, чтобы выйти нужно набрать quit() либо нажать Ctrl+D.

[править] Начало работы с Python

[править] Интерпретатор Python. iPython

Многие базовые вещи будут выполняться в интерпретаторе. Он позволяет получать моментальный отклик на выполненные действия. И, можно сказать, что работает похоже на командную строку сетевых устройств:

  • Каждая команда будет выполняться сразу после нажатия enter
    • Исключение это более сложные объекты (например, циклы), которые выполняются, только после выхода из подрежима настройки

В предыдущем разделе мы проверили установку Python, вызвали интерпретатор.

Но, кроме стандартного интерпретатора Python, есть также усовершенствованный интерпретатор iPython.

iPython позволяет намного больше, чем стандартный интерпретатор, который вызывается по команде python. Но, на базовом уровне, для нас будут такие преимущества в его использовании:

  • автопродолжение команд по Tab или подсказка, если вариантов команд несколько
  • более структурированный и понятный вывод команд
  • автоматические отступы в циклах и других объектах

Далее как интерпретатор будет использоваться iPython.

Note-icon.gif

Установить iPython можно с помощью pip:

pip install ipython

Для знакомства с интерпретатором можно попробовать его использовать как калькулятор:

In [1]: 1 + 2
Out[1]: 3

In [2]: 22*45
Out[2]: 990

In [3]: 2**3
Out[3]: 8

В iPython ввод и вывод подписаны:

  • In - это то что написал пользователь
  • Out - это вывод команды (если он есть)
  • Числа после In и Out это нумерация выполненных команд в текущей сессии iPython

Пример вывода строки:

In [4]: print 'Hello!'
Hello!

Когда в интерпретаторе создается, например цикл (будет рассматриваться позже), то внутри цикла приглашение меняется на .... Для выполнения цикла и выхода из этого подрежима, необходимо дважды нажать Enter:

In [5]: for i in range(5):
   ...:     print i
   ...:     
0
1
2
3
4

[править] print

Оператор print позволяет вывести информацию на стандартный поток вывода. Если надо вывести строку, то ее нужно обязательно заключить в кавычки (двойные или одинарные). Если же нужно вывести, например, результат вычисления или просто число, то кавычки не нужны:

In [6]: print 'Hello!'
Hello!

In [7]: print 5*5
25

Если нужно вывести несколько значений, можно перечислить их через запятую:

In [8]: print 1*5, 2*5, 3*5, 4*5
5 10 15 20

In [9]: print 'one', 'two', 'three'
one two three

По умолчанию, в конце выражения будет перевод строки. Если необходимо, чтобы после вывода выражения не было перевода строки, то в конце команды ставить запятая (обратите внимание, что в интепретаторе это незаметно. Поэтому лучше это попробовать в файле).

Допустим есть файл file4.py (обратите внимание на запятую после первой строки):

print 'Hello!',
print 'Hello!'

Тогда при выполнении скрипта, вывод будет выглядеть так (после первого Hello нет перевода строки, но есть пробел):

Hello! Hello!

[править] dir

Команда dir() может использоваться для того, чтобы посмотреть какие атрибуты и методы есть у объекта.

Например, для числа вывод будет таким (обратите внимание на различные методы, которые позволяют делать арифметические операции):

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']

Если выполнить команду без передачи значение, то она показывает существующие методы, атрибуты и переменные, определенные в текущей сессии интерпретатора:

In [12]: dir()
Out[12]: 
[ '__builtin__',
 '__builtins__',
 '__doc__',
 '__name__',
 '_dh',
 ...
 '_oh',
 '_sh',
 'exit',
 'get_ipython',
 'i',
 'quit']

Пример после создания переменной a и функции test:

In [13]: a = 'hello'

In [14]: def test():
   ....:     print 'test'
   ....:     

In [15]: dir()
Out[15]: 
 ...
 'exit',
 'get_ipython',
 'i',
 'quit',
 'test']

[править] Создание базовых скриптов

Если говорить в целом, то скрипт это обычный файл. В этом файле хранится последовательность команд, которые необходимо выполнить.

Начнем с базового скрипта. Попробуем просто вывести на стандартный поток вывода несколько строк. Для этого надо создать файл file1.py с таким содержимым:

print "----------------------"
print "Hello!"
print "----------------------"

После этого, сохраняем его и переходим в командную строку.

Запускаем скрипт:

nata@lab1:~$ python file1.py
----------------------
Hello!
----------------------

Note-icon.gif

Ставить расширение .py у файла не обязательно.

Но, если вы используете windows, то это желательно делать, так как Windows использует расширение файла, для определения того как обрабатывать файл.

Но в курсе все скрипты, которые будут создаваться, используют расширение .py.

Можно сказать, что это "хороший тон", создавать скрипты Python с таким расширением.

[править] Кодировка

Теперь попробуем напечатать текст, который мы набрали кириллицей (file2.py):

print "Привет"
print 'Пойду почитаю xgu.ru :)'

При попытке запустить скрипт получаем такую ошибку:

nata@lab1:~$ python file2.py
  File "file3.py", line 2
SyntaxError: Non-ASCII character '\xd0' in file file3.py on line 2, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

Для того чтобы не было такой ошибки, необходимо добавить в начале файла такую строку:

# -*- coding: utf-8 -*-

Тогда скрипт file2.py может выглядеть так:

# -*- coding: utf-8 -*-

print "Привет"
print 'Пойду почитаю xgu.ru :)'

Теперь ошибки нет:

nata@lab1:~$ python file3.py
Привет
Пойду почитаю xgu.ru :)

[править] Ввод информации пользователем

Теперь попробуем сделать так, чтобы скрипт задавал вопросы пользователю и затем использовал этот ответ (пока что для вывода в стандартный поток вывода):

# -*- coding: utf-8 -*-

print raw_input('Твой любимый протокол маршрутизации? ' )

Выглядеть это будет так:

nata@lab1:~$ python file3.py
Твой любимый протокол маршрутизации? OSPF
OSPF

Ввод от пользователя может понадобиться, например, для того, чтобы ввести пароль.

Для получения информации от пользователя используется функция raw_input(). В данном случае, информация просто тут же выводится пользователю, но кроме этого, информацию, которую ввел пользователь может быть сохранена в какую-то переменную. И может использоваться далее в скрипте.

В скобках обычно пишется какой-то вопрос, который уточняет, какую информацию нужно ввести.

Текст в скобках, в принципе, писать не обязательно. И можно сделать такой же вывод с помощью оператора print. Тогда файл будет выглядеть так:

# -*- coding: utf-8 -*-
 
print 'Твой любимый протокол маршрутизации? ',
print raw_input()

Но, как правило, нагляднее писать текст в самой функции raw_print().

Note-icon.gif

Если Вы знаете о том, что существует еще функция input() и запутались какая разница между raw_input() и input(), посмотрите эту ссылку:

http://stackoverflow.com/questions/4915361/whats-the-difference-between-raw-input-and-input-in-python3-x

[править] Передача аргументов скрипту (argv)

Очень часто скрипт решает какую-то общую задачу. Например, скрипт обрабатывает как-то файл конфигурации. Конечно в таком случае не хочется каждый раз руками в скрипте править название файла.

Гораздо лучше будет передавать имя файла как аргумент скрипта и затем использовать уже указанный файл.

В Python в модуле sys есть очень простой и удобный способ для работы с аргументами -- argv

Рассмотрим на простом примере. У нас есть скрипт file5.py:

# -*- coding: utf-8 -*-

from sys import argv

script, file = argv
print 'Наш скрипт ', script, 'будет работать с файлом ', file

Проверяем работу скрипта:

nata@lab1:~$ python file5.py file2.py 
Наш скрипт file5.py будет работать с файлом file2.py

Тут надо пояснить несколько моментов:

  • argv это список (будем рассматривать дальше)
  • argv содержит не только аргументы, которые передали скрипту, но и название самого скрипта

Строка script, file = argv, вероятно выглядит не очень понятно.

В Python есть возможность за раз присвоить значения нескольким переменным. Простой пример:

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

А, если вместо чисел список, как в случае с argv:

In [21]: arg = ['file1.py', 'file2.py']
In [22]: script, arg1 = arg

In [23]: script
Out[23]: 'file1.py'

In [24]: arg1
Out[24]: 'file2.py'

[править] Основы Python

[править] Переменные

Переменные в Python:

  • не требуют объявления типа переменной (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

Обратите внимание, что нам не требовалось указать что 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

В этом примере видно, что все три имени ссылаются на одну и ту же область памяти. В некоторых случая это можно быть опасно, а в других полезно.

Note-icon.gif

Имена переменных не должны пересекаться с операторами и названиями модулей или других зарезервированных значений.

[править] Типы данных в Python

В Python есть несколько стандартных типов данных:

  • Numbers (числа)
  • Strings (строки)
  • Lists (списки)
  • Dictionary (словари)
  • Tuples (кортежи)
  • Sets (множества)
  • Boolean

Эти типы данных можно, в свою очередь, классифицировать по нескольким признакам:

  • Изменяемые:
    • Списки
    • Словари
    • Множества
  • Неизменяемые
    • Числа
    • Строки
    • Кортежи
  • Упорядоченные:
    • Списки
    • Кортежи
    • Строки
  • Неупорядоченные:
    • Словари
    • Множества

[править] Числа

Пример различных типов числовых значений:

  • int (40, -80, 0x0800)
  • float (1.5, -30.7)
  • long (52934861L)

Конечно же с числами можно выполнять различные математические операции.

[править] Строки (Strings)

Строки это неизменяемый, упорядоченный тип данных.

Строка в Python это последовательность символов, заключенная в кавычки.

Примеры строк:

In [9]: 'Hello'
Out[9]: 'Hello'
In [10]: "Hello"
Out[10]: 'Hello'

In [11]: 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
   ....: '''

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 profile 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

То, что строки являются упорядоченным типом данных позволяет нам обращаться к символам в строке по номеру, начиная с нуля:

In [14]: string1 = 'interface FastEthernet1/0'

In [15]: string1[0]
Out[15]: 'i'

In [16]: string1[1]
Out[16]: 'n'

In [17]: string1[-1]
Out[17]: '0'

Нумерация всех символов в строке идет с нуля. Но, если нужно обратиться к какому-то по счету символу, начиная с конца, то можно указывать отрицательные значения (на этот раз с единицы).

Кроме обращения к конкретному символу, можно делать срезы строки, указав диапазон номеров (срез выполняется по второе число, не включая его):

In [18]: string1[0:9]
Out[18]: 'interface'

In [19]: string1[10:22]
Out[19]: 'FastEthernet'

Если не указывается второе число, то срез будет до конца строки:

In [20]:  string1[10:]
Out[20]: 'FastEthernet1/0'

Срезать три последних символа строки:

In [21]: string1[-3:]
Out[21]: '1/0'

Строка в обратном порядке:

In [22]: a = '0123456789'

In [23]: a[::]
Out[23]: '0123456789'

In [24]: a[::-1]
Out[24]: '9876543210'

[править] Полезные методы для работы со строками

По сути, конфигурационный файл это просто текстовый файл и поэтому, при автоматизации, очень часто надо будет работать со строками. Знание различных методов (то есть, действий), которые можно применять к строкам, очень сильно облегчает жизнь.

Конечно, чаще всего, эти методы будут применяться в циклах, при обработке файла. Но уже тут должно быть понятно, что многие вещи можно сделать довольно просто.

Например, преобразование регистра строки (методы upper(), lower(), swapcase(), capitalize()):

In [25]: string1 = 'FastEthernet'

In [26]: string1.upper()
Out[26]: 'FASTETHERNET'

In [27]: string1.lower()
Out[27]: 'fastethernet'

In [28]: string1.swapcase()
Out[28]: 'fASTeTHERNET'

In [29]: string2 = 'tunnel 0'

In [30]: string2.capitalize()
Out[30]: 'Tunnel 0'

Очень важно обращать внимание на то, что часто методы возвращают преобразованную строку. И, значит, надо не забыть присвоить ее какой-то переменной (можно той же).

In [31]: string1 = string1.upper()

In [32]: print string1
FASTETHERNET

Метод count() используется для подсчета того, сколько раз символ или подстрока, встречаются в строке:

In [33]: string1 = 'Hello, hello, hello, hello'

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() можно передать подстроку или символ и он покажет на какой позиции находится первый символ подстроки (для первого совпадения):

In [37]: string1 = 'interface FastEthernet0/1'

In [38]: string1.find('Fast')
Out[38]: 10

In [39]: string1[string1.find('Fast')::]
Out[39]: 'FastEthernet0/1'

Проверка на то начинается (или заканчивается) ли строка на определенные символы (методы startswith(), endswith()):

In [40]: string1 = 'FastEthernet0/1'

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()):

In [45]: string1 = 'FastEthernet0/1'

In [46]: string1.replace('Fast', 'Gigabit')
Out[46]: 'GigabitEthernet0/1'

Часто, при обработки файла, файл открывается построчно. Но в конце каждый строки, как правило есть какие-то спецсимволы (а могут быть и вначале). Например, перевод строки.

Для того чтобы избавиться от них, очень удобно использовать метод strip():

In [47]: string1 = '\n\tinterface FastEthernet0/1\n'

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'

Наверняка Вы обратили внимание, что метод strip() убрал спецсимволы и вначале и в конце. Если необходимо убрать символы только слева или только справа, можно использовать, соответственно, методы lstrip() и rstrip().

Еще один очень полезный метод, это метод split():

In [51]: string1 = 'switchport trunk allowed vlan 10,20,30,100-200'

In [52]: string1.split()
Out[52]: ['switchport', 'trunk', 'allowed', 'vlan', '10,20,30,100-200']

Мы еще не изучали списки, но, я думаю, что вывод, тем не менее, понятен.

Метод split() разбивает строку на части, используя как разделитель какой-то символ (или символы). По умолчанию, в качестве разделителя используется пробел. Но в скобках можно указать любой разделитель, который нужен.

В итоге строка будет разбита на части и представлена в виде частей, которые содержатся в списке.

Еще один пример:

In [53]: string1 = ' switchport trunk allowed vlan 10,20,30,100-200\n'

In [54]: commands = string1.strip().split()

In [55]: print commands
['switchport', 'trunk', 'allowed', 'vlan', '10,20,30,100-200']

In [56]: vlans = commands[-1].split(',')

In [57]: print vlans
['10', '20', '30', '100-200']

В начальной строке был символ пробела в начале и символ перевода строки в конце. Нам эти символы будут мешать, поэтому мы их убираем методом strip(). Метод strip() возвращает строку без символов, а мы ее обрабатываем следом методом split() и разделаем строку на части, используя пробел, как разделитель, и присваиваем результат переменной commands.

Теперь нам нужно получить список VLAN отдельным объектом. Использую тот же способ, как и со строками, мы берем последний объект в списке, а затем применяем к нему метод split(), но на этот раз внутри скобок указывается другой разделитель -- запятая. В итоге, отдельным объектом мы получили список VLAN.

[править] Список (List)

Список это изменяемый упорядоченный тип данных.

Список в Python это последовательность элементов, разделенных между собой запятой и заключенных в квадратные скобки.

Примеры списков:

In [1]: list1 = [10,20,30,77]
In [2]: list2 = ['one', 'dog', 'seven']
In [3]: list3 = [1, 20, 4.0, 'word']

Так как список, это упорядоченный тип данных, то, как и в строках, в списках можно обращаться к элементу по номеру, делать срезы:

In [4]: list3 = [1, 20, 4.0, 'word']

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]

Перевернуть список наоборот, можно и с помощью метода reverse():

In [10]: vlans = ['10', '15', '20', '30', '100-200']

In [11]: vlans.reverse()

In [12]: vlans
Out[12]: ['100-200', '30', '20', '15', '10']


Так как списки изменяемые, элементы списка можно менять:

In [13]: list3
Out[13]: [1, 20, 4.0, 'word']

In [14]: list3[0] = 'test'

In [15]: list3
Out[15]: ['test', 20, 4.0, 'word']

[править] Полезные методы для работы со списками

При рассмотрении полезных методов для строк, мы остановились на том, что получили итоговый список vlans:

In [16]: vlans = ['10', '20', '30', '100-200']

Теперь, допустим, что после необходимых операций с списком VLAN(отбросим последний диапазон), нужно опять записать их файл. То есть надо сделать так, чтобы теперь все значений списка были собраны в одну строку, но записаны через запятую (сделать операцию обратную split()).

Метод join() позволяет собрать список строк в одну строку с разделителем, который указан в join():

In [16]: vlans = ['10', '20', '30', '100-200']

In [17]: ','.join(vlans[:-1])
Out[17]: '10,20,30'

Метод append() позволяет добавить в конец списка указанные элемент:

In [18]: vlans = ['10', '20', '30', '100-200']

In [19]: vlans.append('300')

In [20]: vlans
Out[20]: ['10', '20', '30', '100-200', '300']

Если нужно объединить два списка, то можно использовать два способа. Метод extend() и операцию сложения:

In [21]: vlans = ['10', '20', '30', '100-200']

In [22]: vlans2 = ['300', '400', '500']

In [23]: vlans.extend(vlans2)

In [24]: vlans
Out[24]: ['10', '20', '30', '100-200', '300', '400', '500']

In [25]: vlans + vlans2
Out[25]: ['10', '20', '30', '100-200', '300', '400', '500', '300', '400', '500']

In [26]: vlans
Out[26]: ['10', '20', '30', '100-200', '300', '400', '500']

То есть, метод extend() расширяет список "на месте", а при операции сложения, выводится итоговый суммарный список, но исходные списки не меняются.

Метод pop() позволяет удалить элемент, который соответствует указанному номеру. Но, что важно, при этом метод выводит этот элемент:

In [28]: vlans = ['10', '20', '30', '100-200']

In [29]: vlans.pop(-1)
Out[29]: '100-200'

In [30]: vlans
Out[30]: ['10', '20', '30']

Метод remove() похож чем-то на pop(), но в нем надо указывать сам элемент, который надо удалить, а не его номер в списке. Кроме того, remove() не отображает удаленный элемент:

In [31]: vlans = ['10', '20', '30', '100-200']

In [32]: vlans.remove(-1)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-32-f4ee38810cb7> in <module>()
----> 1 vlans.remove(-1)

ValueError: list.remove(x): x not in list

In [33]: vlans.remove('20')

In [34]: vlans
Out[34]: ['10', '30', '100-200']

Метод index() используется для того, чтобы проверить под каким номером в списке хранится элемент:

In [35]: vlans = ['10', '20', '30', '100-200']

In [36]: vlans.index('30')
Out[36]: 2

Вставить элемент на определенное место в списке:

In [37]: vlans = ['10', '20', '30', '100-200']

In [38]: vlans.insert(1,'15')

In [39]: vlans
Out[39]: ['10', '15', '20', '30', '100-200']

[править] Варианты создания списка

Создание списка с помощью литерала:

In [1]: vlans = [10, 20, 30, 50]

Создание списка с помощью функции list():

In [2]: list1 = list('router')

In [3]: print list1
['r', 'o', 'u', 't', 'e', 'r']

Генераторы списков:

In [4]: list2 = ['FastEthernet0/'+ str(i) for i in range(10)]

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']

[править] Словарь (Dictionary)

Словари -- это изменяемый, неупорядоченный тип данных (к слову, в модуле collections доступны упорядоченные объекты, внешне идентичные словарям OrderedDict).

Словарь (ассоциативный массив, хеш-таблица):

  • данные в словаре это пары "ключ:значение"
  • доступ к значениям осуществляется по ключу, а не по номеру, как в списках
  • словари неупорядоченны, поэтому не стоит полагаться на порядок элементов словаря
  • так как словари изменяемы, то элементы словаря можно менять, добавлять, удалять
  • ключ должен быть объектом неизменяемого типа:
    • число
    • строка
    • кортеж
  • значение может быть данными любого типа

Пример словаря:

london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco', 'model': '4451', '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'
}

Для того чтобы получить значение из словаря, надо обратиться по ключу, таким же образом, как это было в списках, только вместо номера, будет использоваться ключ:

In [1]: london = {'name': 'London1', 'location': 'London Str'}

In [2]: london['name']
Out[2]: 'London1'

In [3]: london['location']
Out[3]: 'London Str'

Аналогичным образом можно добавить новую пару ключ:значение:

In [4]: london['vendor'] = 'Cisco'

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'
	}
}

Получить значения из вложенного словаря можно так:

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'


[править] Полезные методы для работы со словарями

Метод clear() позволяет очистить словарь:

In [1]: london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco', 'model': '4451', 'IOS': '15.4'}

In [2]: london.clear()

In [3]: london
Out[3]: {}

Метод copy() позволяет создать полную копию словаря.

Для начала проверим что будет есть сделать так:

In [4]: london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco'}

In [5]: london2 = london

In [6]: id(london)
Out[6]: 25489072

In [7]: id(london2)
Out[7]: 25489072

In [8]: london['vendor'] = 'Juniper'

In [9]: london2['vendor']
Out[9]: 'Juniper'

В такой случае london2 это всего лишь еще одно имя, которое ссылается на словарь. И когда мы меняем словарь london, так как london2 по сути это тот же объект, получаем изменный словарь london2.

Поэтому, если нужно сделать копию словаря, надо использовать метод copy():

In [10]: london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco'}

In [11]: london2 = london.copy()

In [12]: id(london)
Out[12]: 25524512

In [13]: id(london2)
Out[13]: 25563296

In [14]: london['vendor'] = 'Juniper'

In [15]: london2['vendor']
Out[15]: 'Cisco'

Метод get().

Если при обращении к словарю указывается ключ, которого нет в словаре, мы получаем ошибку:

In [16]: london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco'}

In [17]: london['IOS']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-17-b4fae8480b21> in <module>()
----> 1 london['IOS']

KeyError: 'IOS'

Метод get() позволяет запросить значение, но если его нет, вместо ошибки возвращается указанное значение (по умолчанию возвращается None):

In [18]: london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco'}

In [19]: print london.get('IOS')
None

In [20]: print london.get('IOS', 'Ooops')
Ooops

Методы keys(), values(), items():

In [24]: london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco'}

In [25]: london.keys()
Out[25]: ['vendor', 'name', 'location']

In [26]: london.values()
Out[26]: ['Cisco', 'London1', 'London Str']

In [27]: london.items()
Out[27]: [('vendor', 'Cisco'), ('name', 'London1'), ('location', 'London Str')]

Удалить ключ и значение:

In [28]: london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco'}

In [29]: del(london['name'])

In [30]: london
Out[30]: {'location': 'London Str', 'vendor': 'Cisco'}

[править] Варианты создания словаря

Словарь можно создать с помощью литерала:

In [1]: r1 = {'model': '4451', 'IOS': '15.4'}

Конструктор dict позволяет создавать словарь несколькими способами.

Если в роли ключей используются строки, можно использовать такой вариант создания словаря:

In [2]: r1 = dict(model='4451', IOS='15.4')

In [3]: r1
Out[3]: {'IOS': '15.4', 'model': '4451'}

Второй вариант создания словаря с помощью dict:

In [4]: r1 = dict([('model','4451'), ('IOS','15.4')])

In [5]: r1
Out[5]: {'IOS': '15.4', 'model': '4451'}

В ситуации, когда надо создать словарь с известными ключами, но, пока что, пустыми значениями (или одинаковыми значениями), очень удобен метод fromkeys():

In [5]: d_keys = ['hostname', 'location', 'vendor', 'model', 'IOS', 'IP']

In [6]: r1 = dict.fromkeys(d_keys, None)

In [7]: r1
Out[7]: 
{'IOS': None,
 'IP': None,
 'hostname': None,
 'location': None,
 'model': None,
 'vendor': None}

И последний метод создания словаря - генераторы словарей. Сгенерируем словарь с нулевыми значениями, как в предыдущем примере:

In [16]: d_keys = ['hostname', 'location', 'vendor', 'model', 'IOS', 'IP']

In [17]: d = {x: None for x in d_keys}

In [18]: d
Out[18]: 
{'IOS': None,
 'IP': None,
 'hostname': None,
 'location': None,
 'model': None,
 'vendor': None}
[править] Словарь из двух списков (advanced)

Ранее мы рассматривали вариант создания словаря с помощью dict():

In [4]: r1 = dict([('model','4451'), ('IOS','15.4')])

In [5]: r1
Out[5]: {'IOS': '15.4', 'model': '4451'}

То есть, в данном случае, конструктору dict передается список кортежей.

Как правило, такой способ создания используется не в том случае, когда мы пишем словарь руками в текстовом файле, а когда мы создаем словарь из каких-то других промежуточных объектов.

Воспользуемся таким способом создания словаря, чтобы объединить два списка одинаковой длины в словарь.

Для этого воспользуемся функцией zip(), которая как раз возвращает список кортежей:

In [1]: a = [1,2,3]

In [2]: b = [100,200,300]

In [3]: zip(a,b)
Out[3]: [(1, 100), (2, 200), (3, 300)]

Теперь на более полезном примере:

In [4]: d_keys = ['hostname', 'location', 'vendor', 'model', 'IOS', 'IP']
In [5]: d_values = ['london_r1', '21 New Globe Walk', 'Cisco', '4451', '15.4', '10.255.0.1']

In [6]: 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'}

В примере словарей, вначале этого раздела, был пример, в котором у нас был словарь, со вложенными словарями. В нем сразу бросалось в глаза, что ключи в каждом вложенном словаре одинаковые и конечно же было бы странно писать их каждый раз вручную.

В примере ниже у нас есть отдельный список, в котором хранятся ключи, и словарь, в котором хранится в виде списка (чтобы сохранить порядок) информация о каждом устройстве.

Соберем их в словарь с ключами из списка и информацией из словаря data:

In [10]: d_keys = ['hostname', 'location', 'vendor', 'model', 'IOS', 'IP']

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 [13]: for k in data.keys():
   ....:     london_co[k] = dict(zip(d_keys,data[k]))
   ....:     

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'}}

[править] Кортеж (Tuple)

Кортеж это неизменяемый упорядоченный тип данных.

Кортеж в Python это последовательность элементов, разделенные между собой запятой и заключенные в скобки.

Грубо говоря, кортеж это список, который нельзя изменить. То есть, у нас есть только права на чтение. Это может быть защитой от случайных изменений.

То что кортеж неизменяем, может быть очень полезно в некоторых случаях.

Создать пустой кортеж:

In [1]: tuple1 = tuple()

In [2]: print tuple1
()

Кортеж из одного элемента (обратите внимание на запятую):

In [3]: tuple2 = ('password',)

Кортеж из списка:

In [4]: list_keys = ['hostname', 'location', 'vendor', 'model', 'IOS', 'IP']

In [5]: tuple_keys = tuple(list_keys)

In [6]: tuple_keys
Out[6]: ('hostname', 'location', 'vendor', 'model', 'IOS', 'IP')

К объектам в кортеже можно обращаться как и к объектам списка, по порядковому номеру:

In [7]: tuple_keys[0]
Out[7]: 'hostname'

Но так как кортеж неизменяем, присвоить новое значение нельзя:

In [8]: tuple_keys[1] = 'test'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-1c7162cdefa3> in <module>()
----> 1 tuple_keys[1] = 'test'

TypeError: 'tuple' object does not support item assignment

[править] Множество (Set)

Множество это изменяемый неупорядоченный тип данных. В множестве всегда содержатся только уникальные элементы.

Множество в Python это последовательность элементов, разделенные между собой запятой и заключенные в фигурные скобки.

С помощью множества можно легко убрать повторяющиеся элементы:

In [1]: vlans = [10, 20, 30, 40, 100, 10]

In [2]: set(vlans)
Out[2]: {10, 20, 30, 40, 100}

In [3]: set1 = set(vlans)

In [4]: print set1
set([40, 100, 10, 20, 30])

[править] Полезные методы для работы с множествами

Метод add() добавляет элемент в множество:

In [1]: set1 = {10,20,30,40}

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 [8]: set1 = {10,20,30,40}

In [9]: set1.clear()

In [10]: set1
Out[10]: set()

[править] Операции с множествами

Множества полезны тем, что с ними можно делать различные операции и находить объединение множеств, пересечение и так далее.

Объединение множеств можно получить с помощью метода union() или оператора |:

In [1]: vlans1 = {10,20,30,50,100}
In [2]: vlans2 = {100,101,102,102,200}

In [3]: vlans1.union(vlans2)
Out[3]: {10, 20, 30, 50, 100, 101, 102, 200}

In [4]: vlans1 | vlans2
Out[4]: {10, 20, 30, 50, 100, 101, 102, 200}

Пересечение множеств можно получить с помощью метода intersection() или оператора &:

In [5]: vlans1 = {10,20,30,50,100}
In [6]: vlans2 = {100,101,102,102,200}

In [7]: vlans1.intersection(vlans2)
Out[7]: {100}

In [8]: vlans1 & vlans2
Out[8]: {100}

[править] Варианты создания множества

Нельзя создать пустое множество с помощью литерала (так как в таком случае это будет не множество, а словарь):

In [1]: set1 = {}

In [2]: type(set1)
Out[2]: dict

Множество из строки:

In [3]: set('long long long long string')
Out[3]: {' ', 'g', 'i', 'l', 'n', 'o', 'r', 's', 't'}

Множество из списка:

In [4]: set([10,20,30,10,10,30])
Out[4]: {10, 20, 30}

Генератор множеств:

In [5]: set2 = {i + 100 for i in range(10)}

In [6]: set2
Out[6]: {100, 101, 102, 103, 104, 105, 106, 107, 108, 109}

In [7]: print set2
set([100, 101, 102, 103, 104, 105, 106, 107, 108, 109])

[править] Control structures

[править] if/elif/else

Конструкция if/elif/else дает возможность выполнять различные действия в зависимости от условий.

В этой конструкции только if является обязательным, elif и else опциональны:

  • Проверка if всегда идет первой. После оператора if должен быть какой-то тест, который в случае результата True, приводит к выполнению действий в этом блоке.
  • С помощью elif можно сделать несколько разветвлений. То есть, проверять входящие данные на разные условия.
  • Блок else выполняется в том случае, если ни одно из условий if или elif не было истинным.

Пример конструкции:

In [1]: a = 9

In [2]: if a == 0:
   ...:     print 'a равно 0'
   ...: elif a < 10:
   ...:     print 'a меньше 10'
   ...: else:
   ...:     print 'a больше 10'
   ...:     
a меньше 10

В Python:

  • True (истина)
    • любое ненулевое число
    • любая ненулевая строка
    • любой ненулевой объект
  • False (ложь)
    • 0
    • None
    • пустая строка
    • пустой объект

[править] Трехместное выражение (Ternary expressions)

Ternary expressions -- достаточно часто удобнее использовать тернарный оператор, нежели развернутую форму:

s = [1, 2, 3, 4]
result='true' if len(s)>5 else 'false'

[править] Операторы

Операторы сравнения, которые могут использоваться в тестах:

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. Он позволяет выполнять проверку на наличие элемента в списке или подстроки в строке:

In [8]: 'ello' in 'Hello'
Out[8]: True

In [9]: 'elle' in 'Hello'
Out[9]: False

In [10]: vlan = [10, 20, 30, 40]

In [11]: 10 in vlan
Out[11]: True

In [12]: 50 in vlan
Out[12]: False

При использовании со словарями условие in выполняет проверку по ключам словаря:

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

В условиях могут также использоваться логические операторы and, or, not:

In [19]: 5 > 1 and 'lo' in 'hello'
Out[19]: True

In [20]: 5 > 1 and 'la' in 'hello'
Out[20]: False

In [21]: 5 > 1 or 'la' in 'hello'
Out[21]: True

In [22]: not 5 > 10
Out[22]: True

In [23]: 'la' not in 'Hello'
Out[23]: True

[править] Пример использования конструкции if/elif/else

Пример скрипта file7.py, который проверяет длину пароля и есть ли в пароле имя пользователя:

# -*- coding: utf-8 -*-

username = raw_input('Введите имя пользователя: ' )
password = raw_input('Введите пароль: ' )

if len(password) < 8:
    print 'Пароль слишком короткий'
elif username in password:
    print 'Пароль содержит имя пользователя'
else:
    print 'Пароль для пользователя %s установлен' % username

Проверка скрипта:

(test)nata@lab1:~$ python file7.py
Введите имя пользователя: nata
Введите пароль: nata1234
Пароль содержит имя пользователя

(test)nata@lab1:~$ python file7.py
Введите имя пользователя: nata 
Введите пароль: 123nata123
Пароль содержит имя пользователя

(test)nata@lab1:~$ python file7.py
Введите имя пользователя: nata
Введите пароль: 1234
Пароль слишком короткий

(test)nata@lab1:~$ python file7.py
Введите имя пользователя: nata
Введите пароль: 123456789
Пароль для пользователя nata установлен

[править] for

Цикл for проходится по указанной последовательности и выполняет действия, которые указаны в блоке for.

Примеры последовательностей, по которым может проходиться цикл for:

  • строка
  • список
  • словарь
  • функция range() или итератор xrange()
  • любой другой итератор (например, sorted(), enumerate())

Цикл for проходится по строке:

In [1]: for letter in 'Test string':
   ...:     print letter
   ...:     
T
e
s
t
 
s
t
r
i
n
g

В цикле мы использовали переменную с именем letter. Хотя имя может быть любое, удобно, когда имя подсказывает через какие объекты проходит цикл.

Пример цикла for с функцией range():

In [2]: for i in xrange(10):
   ...:     print 'interface FastEthernet0/' + str(i)
   ...:     
interface FastEthernet0/0
interface FastEthernet0/1
interface FastEthernet0/2
interface FastEthernet0/3
interface FastEthernet0/4
interface FastEthernet0/5
interface FastEthernet0/6
interface FastEthernet0/7
interface FastEthernet0/8
interface FastEthernet0/9

В этом цикле мы использовали итератор xrange(). Этот итератор генерирует числа в диапазоне от нуля, до указанного числа, не включая его.

Note-icon.gif

В Python есть функция range() и итератор xrange().

В реальной жизни лучше использовать xrange(), так как в этом случае генерируется не весь список чисел сразу, как в range(), а значения выдаются циклу по мере обращения.

В этом примере, мы проходим по списку VLAN, поэтому переменную можно назвать vlan:

In [3]: vlans = [10, 20, 30, 40, 100]
In [4]: for vlan in vlans:
   ...:     print 'vlan %d' % vlan
   ...:     print ' name VLAN_%d' % vlan
   ...:     
vlan 10
 name VLAN_10
vlan 20
 name VLAN_20
vlan 30
 name VLAN_30
vlan 40
 name VLAN_40
vlan 100
 name VLAN_100

Когда цикл идет по словарю, то фактически он проходится по ключам:

In [5]: r1 = {
 'IOS': '15.4',
 'IP': '10.255.0.1',
 'hostname': 'london_r1',
 'location': '21 New Globe Walk',
 'model': '4451',
 'vendor': 'Cisco'}

In [12]: for k in r1:
   ....:     print k
   ....:     
vendor
IP
hostname
IOS
location
model

Если необходимо выводить пары ключ-значение в цикле:

In [6]: for key in r1:
   ....:     print key + ' => ' + r1[key]
   ....:     
vendor => Cisco
IP => 10.255.0.1
hostname => london_r1
IOS => 15.4
location => 21 New Globe Walk
model => 4451

[править] Вложенные for

Циклы for можно вкладывать друг у друга.

В этом примере в списке commands хранятся команды, которые надо выполнить для каждого из интерфейсов в списке fast_int:

In [7]: commands = ['switchport mode access', 'spanning-tree portfast', 'spanning-tree bpduguard enable']
In [8]: fast_int = ['0/1','0/3','0/4','0/7','0/9','0/10','0/11']

In [9]: for int in fast_int:
   ...:     print 'interface FastEthernet ' + int
   ...:     for command in commands:
   ...:         print ' %s' % command
   ...:         
interface FastEthernet 0/1
 switchport mode access
 spanning-tree portfast
 spanning-tree bpduguard enable
interface FastEthernet 0/3
 switchport mode access
 spanning-tree portfast
 spanning-tree bpduguard enable
interface FastEthernet 0/4
 switchport mode access
 spanning-tree portfast
 spanning-tree bpduguard enable
...

Первый цикл for проходится по интерфейсам в списке fast_int, а второй по командам в списке commands.

Тут явно не хватает добавления номера VLAN для access-порта, но этим мы займемся ниже.

[править] Совмещение for и if

На примере выше стало понятно, что в настройках не хватает добавления порта в определенный VLAN.

Добавим эту возможность.

Соответственно, в списке, в котором хранились команды, добавилась еще одна команда и в ней не хватает только номера VLAN.

Вместо списка интерфейсов, теперь мы используем словарь.

В словаре будет две категории интерфейсов: access и trunk (пока что только access). В словаре fast_int по ключу access также находится словарь, в котором ключ - это номер интерфейса, а значение - это номер VLAN.

Файл file8.py выглядит так:

access_template = ['switchport mode access', 'switchport access vlan', 'spanning-tree portfast', 'spanning-tree bpduguard enable']

fast_int = {'access':{'0/12':'10','0/14':'11','0/16':'17','0/17':'150'}}

for int in fast_int['access']:
    print 'interface FastEthernet' + int
    for command in access_template:
        if command.endswith('access vlan'):
            print ' %s %s' % (command, fast_int['access'][int])
        else:
            print ' %s' % command

Комментарии к коду:

  • В первом цикле for мы перебираем ключи во вложенном словаре fast_int['access']
  • Ключ, который мы берем на данный момент цикла, будет храниться в переменной int
  • Как и в прошлом примере печатаем строку interface FastEthernet и добавляем к ней номер интерфейса
  • Во втором цикле for мы перебираем команды из списка access_template
  • Но, так как теперь надо надо добавить номер VLAN к команде switchport access vlan:
    • мы внутри второго цикла for проверяем команды
    • если команда заканчивается на access vlan
      • мы печатаем команду и добавляем к ней номер VLAN, обратившись в вложенному словарю по текущему ключу int
    • во всех остальных случаях, просто печатаем команду

В итоге мы получаем такой вывод:

(test)nata@lab1:~$ python file8.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

Note-icon.gif

Задание: Сделать аналогичный генератор конфигурации для портов trunk.

Контейнер с портами теперь выглядит так: 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']} }

Access-порты мы уже разобрали, теперь надо сделать trunk. В транках ситуация усложняется тем, что VLAN может быть много инадо понимать, что с ним делать. Поэтому в соответствии каждому порту стоит список и первый (нулевой) элемент списка указывает как воспринимать номера VLAN, который идут дальше:

  • add - значит VLANы надо будет добавить
  • del - значит VLANы надо удалить из списка разрешенных
  • only - значит, что на интерфейсе должны остаться разрешенными только указанные VLANы

trunk_template = ['switchport trunk encapsulation dot1q', 'switchport mode trunk', 'switchport trunk allowed vlan']

[править] Итератор enumerate()

Иногда, при переборе объектов в цикле for, нужно не только получить сам объект, но и его порядковый номер. Это можно сделать, создав дополнительную переменную, которая будет расти на единицу с каждым прохождением цикла.

Но, гораздо удобнее это делать с помощью итератора enumerate().

Базовый пример:

In [1]: list1 = ['str1', 'str2', 'str3']

In [2]: for position, string in enumerate(list1):
   ...:     print position, string
   ...:     
0 str1
1 str2
2 str3

enumerate() умеет считать не только с нуля, но и с любого значение, которое ему указали после объекта:

In [1]: list1 = ['str1', 'str2', 'str3']

In [2]: for position, string in enumerate(list1, 100):
   ...:     print position, string
   ...:     
100 str1
101 str2
102 str3
[править] Пример использования enumerate() для EEM (advanced)

Вероятно, вы знаете о такой вещи в Cisco, как EEM. Если в двух словах, то EEM позволяет выполнять какие-то действия (action) в ответ на событие (event).

Выглядит applet EEM так (подробнее в статье EEM):

event manager applet Fa0/1_no_shut
 event syslog pattern "Line protocol on Interface FastEthernet0/0, changed state to down"
 action 1 cli command "enable"
 action 2 cli command "conf t"
 action 3 cli command "interface fa0/1"
 action 4 cli command "no sh"

В EEM, в ситуации, когда действий выполнить нужно много, довольно быстро надоедает набирать action x cli command. Плюс, чаще всего, уже есть готовый кусок конфигурации, который должен выполнить EEM.

Посмотрим как можно с помощью скрипта сгенерировать команды EEM, на основании существующего списка команд (файл eem.py):

import sys

config = sys.argv[1]

with open(config, 'r') as file:
    for (i, command) in enumerate(file, 1):
        print 'action %04d cli command "%s"' % (i, command.rstrip())

В данном примере команды считываются из файла (конструкцию with и как работать с файлами мы рассмотрим позже), а затем мы обрабатываем каждую строку и добавляем к ней приставку, которая нужна для EEM.

Файл с командами выглядит так (r1_config):

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

Вывод будет таким:

(test)nata@lab1:~$ python eem.py r1_config 
action 0001 cli command "en"
action 0002 cli command "conf t"
action 0003 cli command "no int Gi0/0/0.300"
action 0004 cli command "no int Gi0/0/0.301"
action 0005 cli command "no int Gi0/0/0.302"
action 0006 cli command "int range gi0/0/0-2"
action 0007 cli command " channel-group 1 mode active"
action 0008 cli command "interface Port-channel1.300"
action 0009 cli command " encapsulation dot1Q 300"
action 0010 cli command " vrf forwarding Management"
action 0011 cli command " ip address 10.16.19.35 255.255.255.248"

Упростим, и уберем считывание файла, чтобы было проще понять.

Теперь команды хранятся в списке:

 
In [1]: list1 = ['en\n',
   ...:  'conf t\n',
   ...:  'no int Gi0/0/0.300 \n',
   ...:  'no int Gi0/0/0.301 \n',
   ...:  'no int Gi0/0/0.302 \n',
   ...:  'int range gi0/0/0-2\n',
   ...:  ' channel-group 1 mode active\n',
   ...:  'interface Port-channel1.300\n',
   ...:  ' encapsulation dot1Q 300\n',
   ...:  ' vrf forwarding Management\n',
   ...:  ' ip address 10.16.19.35 255.255.255.248\n']

Повторяем цикл из файла:

In [2]: for (i, command) in enumerate(list1, 1):
   ...:     print 'action %04d cli command "%s"' % (i, command.rstrip())
   ...:     
action 0001 cli command "en"
action 0002 cli command "conf t"
action 0003 cli command "no int Gi0/0/0.300"
action 0004 cli command "no int Gi0/0/0.301"
action 0005 cli command "no int Gi0/0/0.302"
action 0006 cli command "int range gi0/0/0-2"
action 0007 cli command " channel-group 1 mode active"
action 0008 cli command "interface Port-channel1.300"
action 0009 cli command " encapsulation dot1Q 300"
action 0010 cli command " vrf forwarding Management"
action 0011 cli command " ip address 10.16.19.35 255.255.255.248"

[править] while

Цикл while это еще одна разновидность цикла в Python. В то же время, он чем-то похож на конструкцию if.

В цикле while мы пишем выражение, как и в if. И, если это выражение истина, то действия внутри этого блока выполняются. Но, в отличии от if, while возвращается после выполнения в начало цикла.

При использовании цикла while главная опасность заключается в том, что он может выполняться бесконечно. Конечно, условно бесконечно, но все же стоит, при использовании циклов while, обращать внимание на то, будет ли такое состояние, при котором условие в цикла перейдет в состояние ложь.

Как обычно, для начала рассмотрим простой пример:

In [1]: a = 5

In [2]: while a > 0:
   ...:     print a
   ...:     a -= 1
   ...:     
5
4
3
2
1

Вначале мы создали переменную а, со значением 5. Мы ее инициировали.

Затем, в цикле while указано условие a>0. То есть, пока значение а больше 0, будут выполняться какие-то действия. В данном случае, будет выводиться значение переменной а.

Кроме того, в теле цикла, при каждом прохождении цикла, значение а становится на единицу меньше.

Так как значение а уменьшается, мы можем быть уверены, что цикл не зациклится и в какой-то момент выражение a > 0 станет ложью.

Следующий пример построен на основе примера про пароль из раздела о конструкции if. Вы наверняка обратили внимание, что нам приходилось заново запускать скрипт, если пароль не соответствовал требованиям.

Теперь попробуем с помощью цикла while исправить ситуацию. И сделать так, чтобы скрипт сам запрашивал пароль заново, если он не соответствует требованиям.

# -*- coding: utf-8 -*-

username = raw_input('Введите имя пользователя: ' )
password = raw_input('Введите пароль: ' )

pass_OK = False

while not pass_OK:
    if len(password) < 8:
        print 'Пароль слишком короткий\n'
        password = raw_input('Введите пароль еще раз: ' )
    elif username in password:
        print 'Пароль содержит имя пользователя\n'
        password = raw_input('Введите пароль еще раз: ' )
    else:
        print 'Пароль для пользователя %s установлен' % username
        pass_OK = True

В этом случае цикл while полезен, так как он возвращает наш скрипт снова в начало проверок, позволяет снова набрать пароль, но при этом не требует перезапуска самого скрипта.


Теперь скрипт отрабатывает так:

(test)nata@lab1:~$ python file9.py
Введите имя пользователя: nata
Введите пароль: nata
Пароль слишком короткий

Введите пароль еще раз: natanata
Пароль содержит имя пользователя

Введите пароль еще раз: 123345345345
Пароль для пользователя nata установлен

[править] break, continue, pass

В Python есть несколько операторов, которые позволяют менять поведение циклов по умолчанию.

[править] Оператор break

Оператор break позволяет досрочно прервать цикл:

  • break прерывает текущий цикл и продолжает выполнение следующих выражений
  • если используется несколько вложенных циклов, break прерывает внутренний цикл и продолжает выполнять выражения следующие за блоком
  • break может использоваться в циклах for и while

Note-icon.gif

Сильно увлекаться break не надо. Довольно часто достаточно просто немного подправить цикл и надобность в нем отпадет.

Но все же, в некоторых случаях, он действительно нужен и очень полезен.

Пример с циклом for:

In [1]: for num in range(10):
   ...:     if num < 7:
   ...:         print num
   ...:     else:
   ...:         break
   ...:     
0
1
2
3
4
5
6

Пример с циклом while:

In [2]: i = 0
In [3]: while i < 10:
   ...:     if i == 5:
   ...:         break
   ...:     else:
   ...:         print i
   ...:         i += 1
   ...:         
0
1
2
3
4

Ещё пример:

# -*- coding: utf-8 -*-

username = raw_input('Введите имя пользователя: ' )
password = raw_input('Введите пароль: ' )

while True:
    if len(password) < 8:
        print 'Пароль слишком короткий\n'
        password = raw_input('Введите пароль еще раз: ' )
    elif username in password:
        print 'Пароль содержит имя пользователя\n'
        password = raw_input('Введите пароль еще раз: ' )
    else:
        print 'Пароль для пользователя %s установлен' % username
        break

[править] Оператор continue

Оператор continue возвращает управление в начало цикла. То есть, continue позволяет "перепрыгнуть" оставшиеся выражения в цикле и вернуться в начало цикла.

Пример с циклом for:

In [4]: for num in range(5):
   ...:     if num == 3:
   ...:         continue
   ...:     else:
   ...:         print num
   ...:         
0
1
2
4

Пример с циклом while:

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

[править] Оператор pass

Оператор pass ничего не делает. Фактически это такая заглушка для объектов.

Например, Вы придумали как будет выглядеть скрипт, но, прежде чем писать его, Вы решили прописать структуру скрипта.

В таких случаях pass очень помогает. Его можно ставить в циклах, функциях, классах. И это не будет влиять на исполнение кода.

Пример использования pass:

In [6]: for num in range(5):
   ....:     if num < 3:
   ....:         pass
   ....:     else:
   ....:         print num
   ....:         
3
4

[править] for/else, while/else

В циклах for и while опционально может использоваться блок else.

[править] for/else

В цикле for:

  • блок else выполняется в том случае, если цикл завершил итерацию списка
    • но else не выполняется, если в цикле был выполнен break

Пример цикла for с else (блок else выполняется после завершения цикла for):

In [1]: for num in range(5):
   ....:     print num
   ....: else:
   ....:     print "Числа закончились"
   ....:     
0
1
2
3
4
Числа закончились

Пример цикла for с else и break в цикле (из-за break, блок else не выполняется):

In [2]: for num in range(5):
   ....:     if num == 3:
   ....:         break
   ....:     else:
   ....:         print num
   ....: else:
   ....:     print "Числа закончились"
   ....:     
0
1
2

Пример цикла for с else и continue в цикле (continue не влияет на блок else):

In [3]: for num in range(5):
   ....:     if num == 3:
   ....:         continue
   ....:     else:
   ....:         print num
   ....: else:
   ....:     print "Числа закончились"
   ....:     
0
1
2
4
Числа закончились

[править] 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 не выполняется):

In [6]: i = 0

In [7]: while i < 5:
   ....:     if i == 3:
   ....:         break
   ....:     else:
   ....:         print i
   ....:         i += 1
   ....: else:
   ....:     print "Конец"
   ....:     
0
1
2

[править] Работа с исключениями try/except/else/finally

[править] try/except

Если вы повторяли примеры, которые использовались ранее, то наверняка были ситуации, когда выскакивала ошибка. Скорее всего, эта была ошибка синтаксиса, когда не хватало, например, двоеточия.

Как правило, Python довольно понятно реагирует на подобные ошибки и их можно исправить.

Но, даже если код синтаксически написан правильно, все равно могут возникать ошибки. Это ошибки называются исключения (exceptions).

Примеры исключений:

In [1]: 2/0
---------------------------------------------------------------------------
ZeroDivisionError: integer division or modulo by zero

In [2]: 'test' + 2
---------------------------------------------------------------------------
TypeError: cannot concatenate 'str' and 'int' objects

В данном случае, мы увидели два исключения: ZeroDivisionError и TypeError.

Чаще всего, мы можем предсказать какого рода исключения возникнут во время исполнения программы. Например, если программа на вход ожидает два числа, а на выходе выдает их сумму, а пользователь ввел вместо одного числа, строку, то мы увидим ошибку TypeError, как в примере выше.

Python позволяет нам работать с исключениями. Мы можем их перехватывать и что-то выполнять, в случае, если возникло определенное исключение.

Для работы с исключениями используется конструкция try/except:

In [3]: try:
   ...:     2/0
   ...: except ZeroDivisionError:
   ...:     print "You can't divide by zero"
   ...:     
You can't divide by zero

Конструкция try работает таким образом:

  • сначала выполняются выражения, которые записаны в блоке try
  • если при выполнения блока try, не возникло никаких исключений, блок except пропускается. И выполняется дальнейший код
  • если во время выполнения блока try, в каком-то месте, возникло исключение, оставшаяся часть блока try пропускается
    • если в блоке except указано исключение, которые возникло, выполняется код в блоке except
    • если исключение, которое возникло, не указано в блоке except, выполнение программы прерывается и выдается ошибка

Обратите внимание, что строка 'Cool!' в блоке try не выводится:

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, если нужны разные действия, в зависимости от ошибки.

Например, у нас есть скрипт (divide.py), который делит два числа введенных пользователем:

# -*- coding: utf-8 -*-

try:
    a = raw_input("Введите первое число: ")
    b = raw_input("Введите второе число: ")
    print "Результат: ", int(a)/int(b)
except ValueError:
    print "Пожалуйста, вводите только числа"
except ZeroDivisionError:
    print "На ноль делить нельзя"

Примеры выполнения скрипта:

nata$ python divide.py
Введите первое число: 3
Введите второе число: 1
Результат:  3

nata$ python divide.py
Введите первое число: 5
Введите второе число: 0
Результат:  На ноль делить нельзя

nata$ python divide.py
Введите первое число: qewr
Введите второе число: 3
Результат:  Пожалуйста, вводите только числа

В данном случае, исключение ValueError возникает когда пользователь ввел строку, вместо числа. И во время перевода строки в число возникла ошибка.

Исключение ZeroDivisionError возникает в случае, если второе число было 0.

Если мы не ходим выводить различные сообщения на ошибки ValueError и ZeroDivisionError, можно сделать так:

# -*- coding: utf-8 -*-

try:
    a = raw_input("Введите первое число: ")
    b = raw_input("Введите второе число: ")
    print "Результат: ", int(a)/int(b)
except (ValueError, ZeroDivisionError):
    print "Что-то пошло не так..."

Проверяем:

nata$ python divide.py
Введите первое число: wer
Введите второе число: 4
Результат:  Что-то пошло не так...

nata$ python divide.py
Введите первое число: 5
Введите второе число: 0
Результат:  Что-то пошло не так...

Note-icon.gif

В блоке except можно не указывать конкретное исключение или исключения. В таком случае, будут перехватываться все исключения.

Это делать не рекомендуется!

[править] try/except/else

В конструкции try/except есть опциональный блок else. Он выполняется в том случае, если не было исключения.

Например, если нам надо выполнять в дальнейшем какие-то операции с данными, которые ввел пользователь, мы можем записать из в блоке else:

# -*- coding: utf-8 -*-

try:
    a = raw_input("Введите первое число: ")
    b = raw_input("Введите второе число: ")
    result = int(a)/int(b)
except (ValueError, ZeroDivisionError):
    print "Что-то пошло не так..."
else:
    print "Результат в квадрате: ", result**2

Пример выполнения:

nata$ python divide.py
Введите первое число: 10
Введите второе число: 2
Результат в квадрате:  25

nata$ python divide.py
Введите первое число: werq
Введите второе число: 3
Что-то пошло не так...

[править] try/except/finally

Блок finally это еще один опциональный блок в конструкции try. Он выполняется всегда, независимо от того, было ли исключение или нет.

Сюда ставятся действия, которые надо выполнить в любом случае. Например, это может быть закрытие файла.

Добавляем блок finally в наш скрипт:

# -*- coding: utf-8 -*-

try:
    a = raw_input("Введите первое число: ")
    b = raw_input("Введите второе число: ")
    result = int(a)/int(b)
except (ValueError, ZeroDivisionError):
    print "Что-то пошло не так..."
else:
    print "Результат в квадрате: ", result**2
finally:
    print "Вот и сказочке конец, а кто слушал - молодец."

Проверяем:

nata$ python divide.py
Введите первое число: 10
Введите второе число: 2
Результат в квадрате:  25
Вот и сказочке конец, а кто слушал - молодец.

nata$ python divide.py
Введите первое число: qwerewr
Введите второе число: 3
Что-то пошло не так...
Вот и сказочке конец, а кто слушал - молодец.

nata$ python divide.py
Введите первое число: 4
Введите второе число: 0
Что-то пошло не так...
Вот и сказочке конец, а кто слушал - молодец.

[править] Работа с файлами

В работе с файлами есть несколько аспектов:

  • открытие/закрытие
  • чтение
  • запись

[править] Открытие файлов

Прежде чем мы сможем работать с файлом, его надо открыть.

Для открытия файлов, чаще всего, используется функция open():

file = open('file_name.txt', 'r')

В функции open():

  • 'file_name.txt' - это имя файла
    • тут может указывать не только имя, но и путь (абсолютный или относительный)
  • 'r' - режим открытия файла

Функция open() создает объект file, к которому потом мы сможем применять различные методы, для работы с ним.

Примеры режимов открытия файлов:

  • r - открыть файл только для чтения (значение по умолчанию)
  • w - открыть файл для записи
    • если файл существует, то его содержимое удаляется
    • если файл не существует, то создается новый
  • a - открыть файл для дополнение записи. Данные добавляются в конец файла

[править] Чтение файлов

В Python есть несколько методов чтения файла:

  • read() - этот метод считывает содержимое файла в строку
  • readline() - считывает файл построчно
  • readlines() - этот метод считывает строки файла и создает список из строк

Пример использования метода read():

In [1]: f = open('test.txt')

In [2]: f.read()
Out[2]: 'This is line 1\nanother line\none more line\n'

In [3]: f.read()
Out[3]: ''

Комментарии к примеру:

  • метод read() считал весь файл в одну строку
  • в строке 3 метод read() вызван еще раз, но он уже возвращает пустую строку

Построчно файл можно считать с помощью метода readline():

In [4]: f = open('test.txt')

In [5]: f.readline()
Out[5]: 'This is line 1\n'

In [6]: f.readline()
Out[6]: 'another line\n'

Но, чаще всего, проще пройтись по объекту file в цикле:

In [7]: f = open('test.txt')

In [8]: for line in f:
   ....:     print line
   ....:     
This is line 1

another line

one more line

Еще один полезный метод - readlines(). Он считывает строки файла в список:

In [9]: f = open('test.txt')

In [10]: f.readlines()
Out[10]: ['This is line 1\n', 'another line\n', 'one more line\n']

Если нужно получить строки файла, но без перевода строки в конце (так как, в данном случае, split() разделяет на основании разделителя '\n', то без предварительного метода rstrip(), мы получим список, в конце которого будет пустая строка):

In [11]: f = open('test.txt')

In [12]: f.read().rsplit('\n')
Out[12]: ['This is line 1', 'another line', 'one more line', '']

In [13]: f = open('test.txt')

In [14]: f.read().rstrip().split('\n')
Out[14]: ['This is line 1', 'another line', 'one more line']

[править] Запись файлов

При записи очень важно определиться с режимом открытия файла (чтобы случайно его не удалить):

  • append - добавить строки в существующий файл
  • write - перезаписать файл
  • оба режима создают файл, если он не существует

Для записи в файл используются такие методы:

  • write() - записать в файл одну строку
  • writelines() - позволяет передавать в качестве аргумента список строк

Пример использования метода write():

In [1]: f = open('test2.txt', 'a')
In [2]: f.write('line1\n')
In [3]: f.write('line2\n')
In [4]: f.write('line3\n')
In [5]: f.close()

In [6]: cat test2.txt
line1
line2
line3

Пример использования метода write() (обратите внимание, что файл перезаписался):

In [8]: list1 = ['line4\n','line5\n','line6\n']

In [9]: f = open('test2.txt', 'w')
In [10]: f.writelines(list1)
In [11]: f.close()

In [12]: cat test2.txt
line4
line5
line6

Icon-caution.gif

Обратите внимание, что после выполнения операций с файлом, обязательно надо закрыть файл!

[править] Конструкция with

При работе с файлами, надо не забывать закрывать файл, после выполненых операций.

Однако, в Python существует более удобный способ работы с файлами, который гарантирует закрытие файла автоматически: конструкция with ... as:

In [13]: with open('test2.txt') as f:
   ....:     for line in f:
   ....:         print line
   ....:         
line4
line5
line6

In [14]: f.closed
Out[14]: True

Note-icon.gif

Эта конструкция может использоваться не только с файлами.

Подробнее об этом можно почитать по ссылке: http://stackoverflow.com/questions/3012488/what-is-the-python-with-statement-designed-for

Нечто похожее можно сделать с конструкцией try/finally (плюс обрабатывать исключения):

In [15]: try:
   ....:     f = open('test2.txt')
   ....: except IOError:
   ....:     print 'No such file'
   ....: finally:
   ....:     f.close()
   ....:     
In [16]: f.closed
Out[16]: True

Если такого файла нет:

In [16]: try:
   ....:     f = open('test4.txt')
   ....: except IOError:
   ....:     print 'No such file'
   ....: finally:
   ....:     f.close()
   ....:     
No such file

[править] Функции

Функция - это блок кода, выполняющий определенные действия:

  • у функции есть имя, с помощью которого можно запускать этот блок кода сколько угодно раз
    • запуск кода функции, называется вызовом функции
  • функциям можно передавать аргументы
    • соответственно, код функции будет выполняться с учетом указанных аргументов

[править] Создание функций

Создание функции:

  • функции создаются с помощью зарезервированного слова def
  • за def следуют имя функции и круглые скобки
  • внутри скобок могут указываться параметры, которые затем передаются функции
  • после круглых скобок идет двоеточие и с новой строки, с отступом, идет блок кода, который выполняет функция
  • первой строкой, опционально, может быть комментарий, так называемая docstring
  • в функциях может использоваться оператор return
    • он используется для прекращения работы функции и выхода из нее
    • чаще всего, оператор return возвращает какое-то значение

Пример функции:

In [1]: def printstr( s ):
   ...:     """Documentation string"""
   ...:     print s

Эта функция ожидает строку в качестве аргумента, и выводит ее:

In [2]: printstr('Test string')
Test string

In [3]: printstr('Test string2')
Test string2

Первая строка в определении функции - это docstring, строка документации. Это комментарий, который используется как описание функции. Его можно отобразить так:

In [4]: printstr.__doc__
Out[4]: 'Documentation string'

[править] Оператор return

Оператор return используется для прекращения работы функции, выхода из нее, и, как правило, возврата какого-то значения. Выражения, которые идут после return, не выполняются:

In [5]: def printstr( s ):
   ...:     """Documentation string"""
   ...:     print "String: ", s
   ...:     return s
   ...:     print "After return"
   ...: 

In [6]: printstr('Test string')
String:  Test string
Out[6]: 'Test string'

Обратите внимание, что строка 'After return' не выводится.

[править] Пространства имен. Области видимости

У переменных в Python есть область видимости. В зависимости от места в коде, где переменная была определена, определяется и область видимости, то есть, где переменная будет доступна.

Когда мы использовали имена переменных в программе, Python каждый раз, искал, создавал или изменял эти имена в соответствующем пространстве имен. Пространство имен, которое доступно нам в каждый момент, зависит от области в которой мы находимся.

У Python есть правило, которым он пользуется при поиске переменных. Оно называется LEGB.

Например, если внутри функции, мы обращаемся к имени переменной, Python ищет переменную в таком порядке по областям видимости (до первого совпадения):

  • L (local) - в локальной (внутри функции)
  • E (enclosing) - в локальной области объемлющих функций (это те функции, внутри которых находится наша функция)
  • G (global) - в глобальной (в скрипте)
  • B (built-in) - в встроенной (зарезервированные значения Python)

Соответственно есть локальные и глобальные переменные:

  • локальные переменные:
    • переменные, которые определены внутри функции
    • эти переменные становятся недоступными после выхода из функции
  • глобальные переменные
    • переменные, которые определены вне функции
    • эти переменные 'глобальны' только в пределах модуля
      • например, чтобы они были доступны в другом модуле, их надо импортировать

Пример локальной и глобальной переменной:

In [7]: s = 'string'

In [8]: def printstr( s ):
   ...:     s = s + ' test'
   ...:     return s
   ...: 

In [9]: printstr('function')
Out[9]: 'function test'

In [10]: s
Out[10]: 'string'

[править] Аргументы функций

Цель создания функции, как правило, заключается в том, чтобы вынести кусок кода, который выполняет определенную задачу, в отдельный объект. Это позволяет нам использовать этот кусок кода многократно, не создавая его заново в программе.

Как правило, функция должна выполнять какие-то действия с входящими значениями и на выходе выдавать результат.

Для того чтобы функция могла принимать входящие значения, мы должны ее создать с переменными:

In [11]: def sum_ab(a,b):
   ...:     return a + b
   ...: 

Тогда мы сможем при использовании функции, передавать ей аргументы:

In [12]: sum_ab(10,20)
Out[12]: 30

In [13]: sum_ab('test','string')
Out[13]: 'teststring'

В данном случае мы создали функцию sum_ab, которая ожидает на вход два аргумента a и b. И на выходе, возвращает сумму этих аргументов.

При таком определении функции, мы обязаны передать оба аргумента. Если мы передадим только один аргумент, возникнет ошибка (как и в случае, если мы передадим 3 и больше аргументов):

In [14]: sum_ab(50)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-cc30973e46d7> in <module>()
----> 1 sum_ab(50)

TypeError: sum_ab() takes exactly 2 arguments (1 given)

Попробуем разобраться как еще можно передавать аргументы и как сделать так, чтобы функция работала для произвольного числа аргументов.

Функции можно вызывать используя такие типы аргументов:

  • обязательные аргументы (позиционные)
  • ключевые аргументы
  • аргументы по умолчанию
  • аргументы переменной длинны

[править] Обязательные аргументы

Обязательные аргументы:

  • надо передать ровно сколько, сколько указано параметров функции (нельзя пропустить a или b, или указать больше)
  • надо передать в правильном порядке

Функция с обязательными аргументами:

In [15]: def divide(a,b):
   ...:     return a/b
   ...: 

In [16]: divide(10,2)
Out[16]: 5

[править] Ключевые аргументы

Ключевые аргументы:

  • передаются с указанием именем аргумента
  • засчет этого, они могут передаваться в любом порядке
In [17]: def divide(a,b):
   ....:     return a/b
   ....: 

In [18]: divide(b=10,a=100)
Out[18]: 10

[править] Аргументы со значением по умолчанию

При создании функции, можно указывать значение по умолчанию для аргумента:

In [19]: def divide(a,b=2):
   ....:     return a / b
   ....: 

В данном случае, у аргумента b есть значение по умолчанию. И, если вызывать функцию, не указывая значение для b, будет использоваться значение 2:

In [20]: divide(a=100,b=5)
Out[20]: 20

In [21]: divide(a=100)
Out[21]: 50

[править] Аргументы переменной длинны

Иногда заведомо неизвестно сколько аргументов надо передавать функции. Для таких ситуаций мы можем создавать функцию с параметрами переменной длинны.

Такой параметр может быть, как ключевым, так и позиционным.

[править] Позиционные аргументы переменной длинны

Начнем с позиционного:

In [22]: def sum_arg(a,*arg):
   ....:     print a, arg
   ....:     return a + sum(arg)
   ....: 

Функция sum_arg создана с двумя параметрами:

  • параметр a
    • если передается как позиционный аргумент, должен идти первым
    • если передается как ключевой аргумент, то порядок не важен
  • параметр *arg - ожидает аргументы переменной длины
    • сюда попадут все остальные аргументы в виде кортежа
    • эти аргументы могут отсутствовать

Попробуем вызвать функцию:

In [23]: sum_arg(1,10,20,30)
1 (10, 20, 30)
Out[23]: 61

In [24]: sum_arg(1,10)
1 (10,)
Out[24]: 11

In [25]: sum_arg(1)
1 ()
Out[25]: 1

А можно было создать такую функцию:

In [26]: def sum_arg(*arg):
   ....:     print arg
   ....:     return sum(arg)
   ....: 

In [27]: sum_arg(1,10,20,30)
(1, 10, 20, 30)
Out[27]: 61

In [28]: sum_arg()
()
Out[28]: 0
[править] Ключевые аргументы переменной длинны

Аналогичным образом можно создать параметр переменной длины для ключевых аргументов:

In [29]: def sum_arg(a,**karg):
   ....:     print a, karg
   ....:     return a + sum(karg.values())
   ....: 

Функция sum_arg создана с двумя параметрами:

  • параметр a
    • если передается как позиционный аргумент, должен идти первым
    • если передается как ключевой аргумент, то порядок не важен
  • параметр **karg - ожидает ключевые аргументы переменной длины
    • сюда попадут все остальные ключевые аргументы в виде словаря
    • эти аргументы могут отсутствовать

Попробуем вызвать функцию:

In [30]: sum_arg(a=10,b=10,c=20,d=30)
10 {'c': 20, 'b': 10, 'd': 30}
Out[30]: 70

In [31]: sum_arg(b=10,c=20,d=30,a=10)
10 {'c': 20, 'b': 10, 'd': 30}
Out[31]: 70

[править] Полезные встроенные функции и анонимная функция lambda

[править] Анонимная функция lambda

Конструкция lambda называется анонимной функцией, из-за того, что она создается без определения функции стандартным образом через def.

В конструкции lambda:

  • может содержаться только одно выражение
  • аргументов может передаваться сколько угодно

Пример анонимной функции:

In [32]: sum_arg = lambda a, b: a + b

In [33]: sum_arg(1,2)
Out[33]: 3

In [34]: sum_arg(10,22)
Out[34]: 32

[править] zip

Функция zip():

  • на вход функции передаются последовательности
  • zip() возвращает список кортежей, каждый из которых состоит из элементов
    • например, десятый кортеж будет содержать десятый элемент каждой из переданных последовательностей
  • если на вход были переданы последовательности разной длинны, то все они будут отрезаны по самой короткой последовательности
  • последовательность элементов соблюдается

Пример использования zip():

In [35]: a = [1,2,3,4,5]
In [36]: b = [10,20,30,40,50]

In [37]: zip(a,b)
Out[37]: [(1, 10), (2, 20), (3, 30), (4, 40), (5, 50)]

In [38]: zip(b,a)
Out[38]: [(10, 1), (20, 2), (30, 3), (40, 4), (50, 5)]

Использование zip() со списками разной длинны:

In [39]: c = [100,200,300]

In [40]: zip(a,b,c)
Out[40]: [(1, 10, 100), (2, 20, 200), (3, 30, 300)]

Функция zip() может быть полезна, например, когда из двух списков надо создать словарь (эта ситуация описывалась в разделе "Словарь из двух списков").

[править] map

Функция map() применяет указанную функцию к каждому элементу последовательности и возвращает список результатов.

Пример использования функции map():

In [41]: a = ['aaa','bbb','ccc']

In [42]: def to_upper(s):
   ....:     return s.upper()
   ....: 

In [43]: map(to_upper, a)
Out[43]: ['AAA', 'BBB', 'CCC']

Вместе с map() очень удобно использовать lambda:

In [44]: map(lambda s: s.upper(), a)
Out[44]: ['AAA', 'BBB', 'CCC']

Если функция, которую использует map(), ожидает два аргумента, то передаются два списка:

In [45]: a = ['a','b','c','d']

In [46]: b = [1,2,3,4]

In [47]: map(lambda x,y: x*y, a, b)
Out[47]: ['a', 'bb', 'ccc', 'dddd']

[править] filter

Функция filter() применяет функцию ко всем объектам списка, и возвращает те объекты, для которых функция вернула True.

Например, из списка чисел оставляем только нечетные:

In [48]: filter(lambda x: x%2,[10,111,102,213,314,515])
Out[48]: [111, 213, 515]

А теперь только четные:

In [49]: filter(lambda x: not x%2,[10,111,102,213,314,515])
Out[49]: [10, 102, 314]

[править] reduce

Функция reduce() применяет переданную функцию к первым двум элементам списка, затем применяет функцию к полученному результату и 3му элементу.

Например, если использовать функцию суммирования, то получится просто сумма всех элементов (что конечно можно сделать просто встроенной функцией sum()):

In [50]: a = [0,1,2,3,4,5,6]

In [51]: reduce(lambda x,y: x+y, a)
Out[51]: 21

In [52]: sum(a)
Out[52]: 21

Интереснее будет применить это с операцией умножения:

In [53]: a = [1,2,3,4,5,6]

In [54]: reduce(lambda x,y: x*y, a)
Out[54]: 720

[править] Модули

Модуль в Python это обычный текстовый файл с кодом Python и расширением .py.

Модули позволяют логически упорядочить и сгруппировать код.

Разделение на модули может быть, например, по такой логике:

  • разделение данных, форматирования и логики кода
  • группировка функций и других объектов по функционалу

Модули хороши тем, что позволяют повторно использовать уже написанный код и не копировать его (например, не копировать когда-то написанную функцию).

[править] Импорт модуля

Есть несколько способов импорта модуля:

  • import module
  • import module as
  • from module import object
  • from module import *

Попробуем в iPython проверить все варианты.

[править] 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, мы вызываем его так:

In [4]: os.getlogin()
Out[4]: 'natasha'

То есть, мы должны указать имя модуля, а затем через точку метод, функцию или другой объект.

Этот способ импорта хорош тем, что объекты модуля не попадают в именное пространство текущей программы. То есть, если мы создадим функцию с именем getlogin(), то мы не перебьем аналогичную функцию модуля os.

[править] import module as

Конструкция import module as позволяет импортировать модуль под другим именем (как правило, более коротким):

In [1]: import sys as s

In [2]: s.path
Out[2]: 
['',
 '/usr/local/bin',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/PyObjC',
 '/Library/Python/2.7/site-packages',
 '/Library/Python/2.7/site-packages/IPython/extensions',
 '/Users/natasha/.ipython']

[править] from module import object

Вариант from module import object удобно использовать, когда из всего модуля нужны только одна-две функции:

In [1]: from os import getlogin, getcwd

Теперь эти функции доступны в нашем именном пространстве:

In [2]: dir()
Out[2]: 
['In',
 'Out',
 ...
 'exit',
 'get_ipython',
 'getcwd',
 'getlogin',
 'quit']

Попробуем их использовать:

In [3]: getlogin()
Out[3]: 'natasha'

In [4]: getcwd()
Out[4]: '/Users/natasha/Desktop/Py_net_eng/code_test'

[править] from module import *

Вариант from module import * импортирует все имена модуля в текущее именное пространство:

In [1]: from os import *

In [2]: dir()
Out[2]: 
['EX_CANTCREAT',
 'EX_CONFIG',
 ...
 'wait',
 'wait3',
 'wait4',
 'waitpid',
 'walk',
 'write']

In [3]: len(dir())
Out[3]: 218

В модуле os очень много объектов, поэтому вывод сокращен. В конце указана длина списка имен текущего именного пространства.

Note-icon.gif

Такой вариант лучше не использовать.

Как минимум, его надо использовать с большой осторожностью.

[править] Создание своих модулей

Так как модуль это просто файл с расширение .py и кодом Python, мы можем легко создать несколько своих модулей.

Попробуем, для примера, использовать скрипт из раздела "Совмещение for и if". Разнесем шаблоны портов, данные и формирование команд в разные файлы.

Например, у нас есть файл sw_int_templates.py:

access_template = ['switchport mode access', 'switchport access vlan', 'spanning-tree portfast', 'spanning-tree bpduguard enable']

trunk_template = ['switchport trunk encapsulation dot1q', 'switchport mode trunk', 'switchport trunk allowed vlan']

l2int_template = ['no switchport', 'ip address']

И файл sw1.py:

sw1_fast_int = {'access':{'0/12':'10','0/14':'11','0/16':'17','0/17':'150'}}

Теперь попробуем совместить все вместе в файле generate_sw_conf.py:

import sw_int_templates
from sw1 import sw1_fast_int


for int in sw1_fast_int['access']:
    print 'interface FastEthernet' + int
    for command in sw_int_templates.access_template:
        if command.endswith('access vlan'):
            print ' %s %s' % (command, sw1_fast_int['access'][int])
        else:
            print ' %s' % command

Вывод мы получаем такой же:

nata$ python generate_sw_conf.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

В этом примере мы увидели:

  • как импортировать модули
  • что модули - это обычные файлы с кодом Python
  • что мы можем импортировать все объекты модуля (import module)
  • или только некоторые (from module import object)

[править] Пути поиска модулей

При импорте модуля, Python ищет модуль в таком порядке:

  • в текущем каталоге
  • если модуль не найден, Python ищет модуль в каталогах, которые указаны в переменной PYTHONPATH
  • если модуль не найден, Python проверяет путь по умолчанию. В Unix это, как правило, /usr/local/lib/python/

Пути поиска модулей хранятся в переменной sys.path:

In [1]: import sys

In [2]: sys.path
Out[2]: 
['',
 '/usr/local/bin',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload',
 '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/PyObjC',
 '/Library/Python/2.7/site-packages',
 '/Library/Python/2.7/site-packages/IPython/extensions',
 '/Users/natasha/.ipython']

[править] Регулярные выражения

Регулярное выражение это специальная последовательность из обычных и специальных символов. Эта последовательность задает шаблон, который позже используется для обработки подстрок.

Подробнее о регулярных выражениях можно почитать тут: Регулярное выражение

В Python для работы с регулярными выражениями используется модуль re. Соответственно, для начала работы с регулярными выражениями в Python, надо импортировать модуль re.

Начнем с простого примера. Попробуем найти подстроку, которая соответствует шаблону, в указанной строке. Пока что, мы не используем специальные символы и в роли шаблона у нас простая строка 'dhcp':

In [1]: import re

In [2]: line = '00:09:BB:3D:D6:58   10.1.10.2    86250   dhcp-snooping   10    FastEthernet0/1'

In [3]: print re.search('dhcp', line)
<_sre.SRE_Match object at 0x10edeb9f0>

In [4]: print re.search('dhcpd', line)
None

Функция search():

  • используется для поиска подстроки, которая соответствует шаблону
  • возвращает объект Match, если подстрока найдена
  • возвращает 'None', если подстрока не найдена

Из объекта Match можно получить несколько вариантов полезной информации.

Например, с помощью метода span(), можно получить числа, указывающие начало и конец подстроки:

In [5]: match = re.search('dhcp', line)

In [6]: match.span()
Out[6]: (49, 53)

In [7]: line[49:53]
Out[7]: 'dhcp'

Метод group() позволяет получить подстроку, которая соответствует шаблону:

In [15]: match.group()
Out[15]: 'dhcp'

Важный момент в использовании функции search(), то что она ищет только первое совпадение в строке, которое соответствует шаблону:

In [16]: line2 = 'test dhcp, test2 dhcp2'

In [17]: match = re.search('dhcp', line2)

In [18]: match.group()
Out[18]: 'dhcp'

In [19]: match.span()
Out[19]: (5, 9)

Для того чтобы найти все совпадения, можно использовать функцию findall():

In [20]: line2 = 'test dhcp, test2 dhcp2'

In [21]: match = re.findall('dhcp', line2)

In [22]: print match
['dhcp', 'dhcp']

Особенность функции findall() в том, что она возвращает список подстрок, которые соответствуют шаблону, а не объект Match. Поэтому нельзя вызвать методы, которые мы использовали выше в функции search().

Для того чтобы получить все совпадения, но при этом, получить совпадения в виде объекта Match(), можно использовать функцию finditer():

In [23]: line2 = 'test dhcp, test2 dhcp2'

In [24]: match = re.finditer('dhcp', line2)

In [25]: print match
<callable-iterator object at 0x10efd2cd0>

In [26]: for i in match:
   ....:     print i.span()
   ....:     
(5, 9)
(17, 21)

In [27]: line2[5:9]
Out[27]: 'dhcp'

In [28]: line2[17:21]
Out[28]: 'dhcp'

Можно воспользоваться и методами start(), end() (так удобнее получить позиции подстрок):

In [29]: line2 = 'test dhcp, test2 dhcp2'

In [30]: match = re.finditer('dhcp', line2)

In [31]: for i in match:
   ....:     b = i.start()
   ....:     e = i.end()
   ....:     print line2[b:e]
   ....:     
dhcp
dhcp

Кроме вышеперечисленных функций, в Python есть возможность заранее скомпилировать регулярное выражение, а затем использовать его. Это особенно полезно в тех случаях, когда регулярное выражение много используется в скрипте.

Пример компиляции регулярного выражения и его использования:

In [32]: line2 = 'test dhcp, test2 dhcp2'

In [33]: regex = re.compile('dhcp')

In [34]: match = regex.finditer(line2)

In [35]: for i in match:
   ....:     b = i.start()
   ....:     e = i.end()
   ....:     print line2[b:e]
   ....:     
dhcp
dhcp

Итак, основные функции для работы с регулярными выражениями:

  • match() - эту функцию мы не рассматривали. Ее особенность в том, что последовательность ищется в начале строки
  • search() - ищет первое совпадение с шаблоном
  • findall() - ищет все совпадения с шаблоном. Выдает результирующие строки в виде списка
  • finditer() - ищет все совпадения с шаблоном. Выдает итератор
  • compile() - компилирует регулярное выражение. К этому объекту затем можно применять все перечисленные функции

[править] Специальные символы

Пока что мы рассматривали самый простой вариант шаблона - строку.

Полностью возможности регулярных выражений проявляются при использовании специальных символов.


[править] Жадность регулярных выражений

По умолчанию, регулярные выражение, жадные (greedy). Это значит, что результирующая подстрока, которая соответствует шаблону, будет наиболее длинной.

Пример жадного поведения:

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 [5]: line = '<text line> some text>'

In [6]: match = re.search('<.*?>', line)

In [7]: match.group()
Out[7]: '<text line>'

Note-icon.gif

Обратите внимание, что жадность касается именно символов повторения

[править] Группировка выражений

[править] Нумерованные группы

С помощью определения групп элементов в шаблоне, можно изолировать части текста, которые соответствуют шаблону.

Группа определяется помещением выражения в круглые скобки (). Внутри выражения, группы нумеруются слева направо, начиная с 1. Затем к группам можно обращаться к ним по номерам и получать текст, которые соответствует выражению в группе.

Пример использования групп:

In [8]: line = '<text line> some text <12343>'
In [9]: match = re.search('(<.*?>).* (<\d+>)', line)

В данном примере у нас указаны две группы:

  • первая группа - это любые символы заключенные в <>. Эта группа не жадная
  • вторая группа - числа, заключенные в <>

Теперь можно обращаться к группам по номеру. Группа 0 это строка, которая соответствует всему шаблону:

In [10]: match.group(0)
Out[10]: '<text line> some text <12343>'

In [11]: match.group(1)
Out[11]: '<text line>'

In [12]: match.group(2)
Out[12]: '<12343>'

Для вывода всех подстрок, которые соответствуют указанным группам, используется метод groups:

In [13]: match.groups()
Out[13]: ('<text line>', '<12343>')

[править] Именованные группы

Когда выражение сложное, становится сложнее определять номер группы, плюс, при дополнении выражения, может получиться так, что порядок групп изменился. И прийдется изменить и код, который ссылается на группы.

Синтаксис именованной группы (?P<name>regex):

In [14]: line = '<text line> some text <12343>'
In [14]: match = re.search('(?P<text><.*?>).* (?P<number><\d+>)', line)

Теперь к этим группа можно обращаться по имени:

In [15]: match.group('text')
Out[15]: '<text line>'

In [16]: match.group('number')
Out[16]: '<12343>'

Также очень полезно то, что с помощью метода groupdict(), можно получить словарь, где ключи - имена групп, а значения - подстроки, которые им соответствуют:

In [17]: match.groupdict()
Out[17]: {'number': '<12343>', 'text': '<text line>'}

[править] Разбор вывода команды show ip dhcp snooping с помощью именованных групп

Рассмотрим еще один пример использования именованных групп.

У нас есть файл dhcp_snooping.txt с выводом команды show ip dhcp snooping binding:

MacAddress          IpAddress        Lease(sec)  Type           VLAN  Interface
------------------  ---------------  ----------  -------------  ----  --------------------
00:09:BB:3D:D6:58   10.1.10.2        86250       dhcp-snooping   10    FastEthernet0/1
00:04:A3:3E:5B:69   10.1.5.2         63951       dhcp-snooping   5     FastEthernet0/10
00:05:B3:7E:9B:60   10.1.5.4         63253       dhcp-snooping   5     FastEthernet0/9
00:09:BC:3F:A6:50   10.1.10.6        76260       dhcp-snooping   10    FastEthernet0/3
Total number of bindings: 4

С помощью скрипта parse_dhcp_snooping.py мы разберем вывод команды.

Но, для начала, попробуем разобрать одну строку:

In [18]: line = '00:09:BB:3D:D6:58   10.1.10.2        86250       dhcp-snooping   10    FastEthernet0/1'

В регулярном выражении, именованные группы используются для тех частей вывода, которые мы хотим позже использовать:

In [22]: match = re.search(r'(?P<mac>.+?) +(?P<ip>.*?) +(\d+) +([\w-]+) +(?P<vlan>\d+) +(?P<int>.*$)', line)

Комментарии к регулярному выражению:

  • (?P<mac>.+?) + - в группу с именем 'mac' попадают любые символы, но, так как выражение не жадное, а следом указано, что должны идти пробелы (один или более), то на выходе мы получаем последовательность символов, до пробела
  • (?P<ip>.*?) + - тут аналогично, последовательность любых символов до пробела. Имя группы 'ip'
  • (\d+) + - числовая последовательность (1 или более цифр), а затем один или более пробелов
    • сюда попадет значение Lease
  • ([\w-]+) + - буквы или '-', в количестве 1 или более
    • сюда попадает тип соответствия (в данном случае, все они dhcp-snooping)
  • (?P<vlan>\d+) + - именованная группа 'vlan'. Сюда попадают только числовые последовательности, с одним или более символами
  • (?P<int>.*$)' - именованная группа 'int'. Сюда попадают любые символы, которые находятся в конце строки (в предыдущем выражении эта группа ограничена пробелом)

Обратите внимание, что для первых двух групп элементов отключена жадность. Для остальных жадность можно не отключать, так как в них более четко указаны какие именно символы должны быть.

В результате мы получим такой словарь:

In [23]: match.groupdict()
Out[23]: 
{'int': 'FastEthernet0/1',
 'ip': '10.1.10.2',
 'mac': '00:09:BB:3D:D6:58',
 'vlan': '10'}

Теперь переберем аналогично все строки файла dhcp_snooping.txt в скрипте parse_dhcp_snooping.py и выведем в нормальном виде информацию об устройствах.

Файл parse_dhcp_snooping.py:

# -*- coding: utf-8 -*-
import re

regex = re.compile('(?P<mac>.+?) +(?P<ip>.*?) +(\d+) +([\w-]+) +(?P<vlan>\d+) +(?P<int>.*$)')
result = []

with open('dhcp_snooping.txt') as data:
    for line in data:
        if line[0].isdigit():
            result.append(regex.search(line).groupdict())

print "К коммутатору подключено %d устройства" % len(result)

for num, comp in enumerate(result, 1):
    print "Параметры устройства %s:" % num
    for key in comp:
        print "\t%s:\t%s" % (key,comp[key])

Вывод результата:

natasha$ 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

[править] Замена подстроки с помощью re

С помощью регулярных выражений можно выполнять замену в строках.

Простой пример замены:

In [24]: line = "cat's are great, cat's cat's cat's"

In [25]: regex = re.compile('(cat)')

In [26]: print regex.sub('dog', line)
dog's are great, dog's dog's dog's

[править] Split строк с помощью re

В ситуациях, когда разделитель, используя который, надо разбить строки на части, сложный и не получается разбить строку стандартными методами, можно использовать функцию split():

In [27]: line = 'Text\n in one line.\n\nSecond line\n\n\nAnother line\n\n\nThe End'

In [28]: re.split(r'\n{2,}', line)
Out[28]: ['Text\n in one line.', 'Second line', 'Another line', 'The End']

В данном случае, разделитель - это два пробела или больше.

[править] Сериализация данных

[править] CSV

[править] JSON

[править] YAML

[править] Jinja2

Jinja2 это язык шаблонов, который используется в Python. Jinja2 используется для генерации документов на основе одного или нескольких шаблонов.

Примеры использования:

  • шаблоны для генерации HTML-страниц
  • шаблоны для генерации конфигурационных файлов в Unix/Linux
  • шаблоны для генерации конфигурационных файлов сетевых устройств

Jinja это не единственный язык шаблонов (шаблонизатор) для Python и, уж тем более, не единственный язык шаблонов в целом. Однако, он довольно широко используется и, что главное, прост в использовании.

Установить Jinja2 можно с помощью pip:

pip install jinja2

Идея Jinja очень проста: мы разделяем данные и шаблон. Это позволяет использовать один и тот же шаблон, но подставлять в него разные данные.

В самом простом случае, шаблон это просто текстовый файл, в котором указаны места подстановки значений, с помощью переменных 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

Комментарии к шаблону:

  • В Jinja переменные записываются в двойных фигурных скобках.
  • При выполнении скрипта, эти переменные будут заменены нужными значениями.
  • Главное преимущество тут в том, что это шаблон может использоваться для генерации конфигурации разных устройств, с помощью подстановки других наборов переменных.

Рассмотрим простейший пример скрипта с генерацией файла на основе шаблона Jinja (файл generator.py):

from jinja2 import Template

template = Template(u"""
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
""")

liverpool = {'id':'11', 'name':'Liverpool', 'int':'Gi1/0/17', 'ip':'10.1.1.10'}

print template.render( liverpool )

Комментарии к файлу generator.py:

  • В первой строке мы импортируем из Jinja2 класс Template.
  • Затем, создаем объект template и уже в нем прописываем наш шаблон, вставляя переменные в синтаксисе Jinja.
  • После шаблона мы создаем словарь liverpool, в котором ключи должны быть такими же, как имена переменных в шаблоне.
    • А значения, которые соответствуют ключам, это те данные, которые мы хотим подставить на место переменных.
  • Последняя строка рендерит шаблон используя словарь liverpool, то есть, подставляет значения в переменные.

Если запустить скрипт generator.py, то вывод будет таким:

natasha$ python 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

[править] Пример использования Jinja2

Аналогичным образом можно использовать Jinja для генерации нескольких конфигурационных файлов.

В этом примере логика разнесена в 3 разных файла:

  • router_template.py - шаблон
  • routers_info.py - в этом файле, в виде списка словарей, находится информация о маршрутизаторах, для которых нам нужно сгенерировать конфигурационный файл
  • router_config_generator.py - в этом скрипте импортируются два других файла и генерируются конфигурационные файлы маршрутизаторов


Файл router_template.py


Файл routers_info.py

routers = [
{
    '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

# -*- coding: utf-8 -*-
from jinja2 import Template
from router_template import template_r1
from routers_info import routers

for router in routers:
    r1_conf = router['name']+'_r1'
    with open(r1_conf,'w') as f:
        f.write(template_r1.render( router ))

Файл router_config_generator.py импортирует предыдущие файлы:

  • из файла router_template импортируем шаблон template_r1
  • из файла routers_info импортируем список routers

Затем в цикле перебираем объекты (словари) в списке routers:

  • название файла будет состоять из поля name в словаре и строки _r1
    • например, Liverpool_r1
  • затем мы открываем файл с таким именем, в режиме для записи
  • пишем в файл результат рендеринга шаблона с использованием текущего словаря
  • конструкция with сама закрывает файл
  • возвращаемся в начало цикла (пока не переберем все словари)

Запускаем файл router_config_generator.py:

natasha$ python router_config_generator.py

В результате получатся три конфигурационных файла:




[править] Пример использования Jinja с корректным использованием программного интерфейса

Note-icon.gif

Для того чтобы разобраться с Jinja2, лучше использовать предыдущие примеры.

Этот вариант показан для того, чтобы показать, в каком виде предыдущий пример, скорее всего, встретится в реальной жизни. И как, в таком случае, обрабатывать вводные данные и шаблон.

Термин "программный интерфейс" относится к способу работы Jinja с вводными данными и шаблоном, для генерации итоговых файлов.

Выше для загрузки шаблона, мы использовали класс Template и, уже в самом файле, указывали, что это шаблон. А затем его импортировали.

Такой вариант проще всего понять, однако, в реальной жизни, лучше использовать другой способ:

  • будет лучше, если шаблон будет простым текстовым файлом
  • для того чтобы загрузить шаблон, используем:
    • Loader (загрузчик):
      • позволяет указать путь (или пути) к шаблонам
    • Environment (окружение) позволяет:
      • указывать где искать шаблоны, с помощью загрузчика
      • указывать дополнительные параметры обработки шаблона
      • использовать методы Environment, для того чтобы указать как именно обрабатывать шаблон

Note-icon.gif

Подробнее о программном интерфейсе Jinja2 можно почитать на странице Jinja2.

Кроме изменения шаблона и собственно скрипта, переделаем также формат вводных данных:

  • данные записаны в файле просто в виде строк, через запятую
    • для того чтобы не надо было описывать их в синтаксисе объектов Python и не надо было писать кавычки и так далее
  • при обработке данных из файла, мы превратим их в списки, а затем обработаем списки
  • для этого воспользуемся примером генерации словарей из списков, который рассматривался в разделе "Словари"
  • обратите внимание, что тут используется несколько приемов, которые мы изучали ранее и, чтобы понять скрипт, может потребоваться пересмотреть предыдущие темы

Переделанный пример предыдущего скрипта, шаблона и файла с данными:




Файл router_config_generator.py импортирует из модуля jinja2:

  • FileSystemLoader - загрузчик, который позволяет работать с файловой системой
    • тут указывается путь к каталогу, где находятся шаблоны
    • в данном случае, шаблон находится в каталоге templates
  • Environment - класс для описания параметров окружения:
    • в данном случае, указан только загрузчик

Note-icon.gif

Обратите внимание, что шаблон теперь находится в каталоге templates.

Для того чтобы указать текущий каталог в загрузчике, надо добавить пару строк и изменить значение в загручике:

import os

curr_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader = FileSystemLoader(curr_dir))

Метод get_template() используется для того, чтобы получить шаблон. В скобках указывается имя файла.

Затем мы открываем файл с данными (routers_info.txt):

  • используем генератор списков для того чтобы создать список списков (где каждый вложенный список это список слов в исходной строке):
    • генератор списков - перебираем строки файла
    • strip() - удаляем символ \n в конце строки
    • split(',') - разделяем строку в список элементов, взяв как разделитель запятую
  • далее снова используем list comprehension для того чтобы создать список словарей
    • словари создаются из комбинации двух списков:
      • первый список в списке data - это ключи, которые используются в словарях
      • остальные списки это наборы данных
    • как использовать dict и zip, можно посмотреть в подразделе "Словарь из двух списков (advanced)"

Последняя часть осталась неизменной.

В дальнейшем, в практических примерах, мы будем использовать такой вариант.

[править] Работа с базами данных

Использование баз данных - это еще один способ хранения информации.

База данных (БД) - это данные, которые хранятся в соответствии с определенной схемой. В этой схеме каким-то образом описаны соотношения между данными.

Язык БД (лингвистические средства) - используется для описания структуры БД, управления данными (добавление, изменение, удаление, получение), управления правами доступа к БД и ее объектам, управления транзакциями.

Система управления базами данных (СУБД) - это программные средства, которые дают возможность управлять БД. СУБД должны поддерживать соответствующий язык (языки) для управления БД.

Note-icon.gif

В разговорной речи (но и в литературе также) часто можно встретить использование термина БД, вместо термина СУБД.

Это не совсем корректно, но, тем не менее, встречается довольно часто.

Мы не будем углубляться в разновидности современных СУБД и их классификацию. Остановимся на немного упрощенной классификации по модели СУБД.

Можно выделить такие три типа СУБД по модели данных (перечислены не все типы):

  • Реляционные
    • SQLite, PostgreSQL, MySQL
  • Документо-ориентированные
    • MongoDB, CouchDB
  • Ключ-значение
    • Redis, Berkeley DB

В дальнейшем рассматривается СУБД SQLite, представитель реляционной модели и, соответственно, далее мы говорим о реляционных СУБД.

[править] SQL

SQL (structured query language) - используется для описания структуры БД, управления данными (добавление, изменение, удаление, получение), управления правами доступа к БД и ее объектам, управления транзакциями.

Язык SQL подразделяется на такие 4 категории:

  • DDL (Data Definition Language) - язык описания данных
  • DML (Data Manipulation Language) - язык манипулирования данными
  • DCL (Data Control Language) - язык определения доступа к данным
  • TCL (Transaction Control Language) - язык управления транзакциями

В каждой категории есть свои операторы (перечислены не все операторы):

  • DDL
    • CREATE - создание новой таблицы, СУБД, схемы
    • ALTER - изменение существующей таблицы, колонки
    • DROP - удаление существующих объектов из СУБД
  • DML
    • SELECT - выбор данных
    • INSERT - добавление новых данных
    • UPDATE - обновление существующих данных
    • DELETE - удаление данных
  • DCL
    • GRANT - предоставление пользователям разрешения на чтение/запись определенных объектов в СУБД
    • REVOKE - отзывает ранее предоставленые разрешения
  • TCL
    • COMMIT Transaction - применение транзакции
    • ROLLBACK Transaction - откат всех изменений сделанных в текущей транзакции

[править] SQL и Python

Для работы с реляционной СУБД в Python можно использовать два подхода:

  • работать с библиотекой, которая соответствует конкретной СУБД и использовать для работы с БД язык SQL
    • Например, для работы с SQLite мы будем использовать модуль sqlite3
  • работать с ORM, которая использует объектно-ориентированный подход для работы с БД
    • Например, SQLAlchemy

[править] SQLite

SQLite — встраиваемая в процесс реализация SQL-машины. Слово SQL-сервер здесь не используем, потому что как таковой сервер там не нужен — весь функционал, который встраивается в SQL-сервер, реализован внутри библиотеки (и, соответственно, внутри программы, которая её использует).

На практике, SQLite часто используется как встроенная СУБД в приложениях.

Note-icon.gif

Если, в дальнейшем, по какой-то причине потребуется использовать другую СУБД, например, PostgreSQL, данные можно перенести в нее из SQlite, без проблем.

[править] SQLite CLI

В комплекте поставки SQLite идёт также утилита для работы с SQLite в командной строке. Утилита представлена в виде исполняемого файла sqlite3 (sqlite3.exe для Windows) и с ее помощью можно вручную выполнять команды SQL.

В помощью этой утилиты очень удобно проверять правильность команд SQL, а также в целом знакомится с языком SQL.

Попробуем с помощью этой утилиты разобраться с базовыми командами SQL, которые нам понадобятся для работы с БД.

Для начала разберемся как создавать БД.

Для того чтобы создать БД (или открыть уже созданную) надо просто вызвать sqlite3 таким образом:

nata:$ sqlite3 testDB.db
SQLite version 3.7.13 2012-06-11 02:05:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> 

Внутри sqlite3 можно выполнять команды SQL или, так называемые, метакоманды (или dot-команды).

[править] Метакоманды

К метакомандам относятся несколько специальных команд, для работы с SQLite. Они относятся только к утилите sqlite3, а не к SQL языку. В конце этих команд ';' ставить не нужно.

Рассмотрим несколько метакоманд:

  • .help - команда, которая выводит подсказку со списком всех метакоманд
  • .exit или .quit - выход из сессии sqlite3
  • .databases - показывает присоединенные БД
  • .tables - показывает доступные таблицы

Примеры выполнения:

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              

[править] Основы SQL (в sqlite3 CLI)

Note-icon.gif

Если вы знакомы с базовым синтаксисом SQL, этот раздел можно пропустить и сразу перейти к разделу "Модуль sqlite3".

[править] CREATE

Для начала, создадим таблицу switch, в которой будет хранится информация о коммутаторах:

sqlite> CREATE table switch (
   ...>     mac          text primary key,
   ...>     hostname     text,
   ...>     model        text,
   ...>     location     text
   ...> );

Аналогично можно было создать таблицу и таким образом:

sqlite> create table switch (mac text primary key, hostname text, model text, location text);

В данном примере мы описали таблицу switch, используя язык DDL. Мы определили какие поля будут в таблице и значения какого типа будут в них находиться.

Кроме того, поле mac является первичным ключом. Это автоматически значит, что:

  • поле должно быть уникальным
  • в нем не может находиться значение NULL

В нашем примере это вполне логично, так как MAC-адрес у коммутаторов должен быть уникальным.

На данный момент записей в таблице нет, есть только ее определение. Просмотреть определение можно такой командой:

sqlite> .schema switch
CREATE TABLE switch (
mac          text primary key,
hostname     text,
model        text,
location     text
);

[править] DROP

Удалить таблицу можно так:

sqlite> DROP table switch

[править] INSERT

Добавим записи в таблицу. Есть несколько вариантов добавления записей, в зависимости от того, все ли поля будут заполнены и будут ли они идти по порядку определения полей или нет.

Если мы будем указывать значения для всех полей, то добавить запись можно таким образом (порядок полей должен соблюдаться):

sqlite> INSERT into switch values ('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str');

Если нужно указать не все поля, или указать их в произвольном порядке, используется такая запись:

sqlite> INSERT into switch (mac, model, location, hostname)
   ...> values ('0000.BBBB.CCCC', 'Cisco 3850', 'London, Green Str', 'sw5');

[править] SELECT

Теперь в нашей таблице две записи. Просмотрим их:

sqlite> SELECT * from switch;
0000.AAAA.CCCC|sw1|Cisco 3750|London, Green Str
0000.BBBB.CCCC|sw5|Cisco 3850|London, Green Str

В данном случае мы отображаем все записи в таблице switch.

В отображении таблицы не хватает названия полей. Включить их отображение можно с помощью команды .headers ON.

sqlite> .headers ON
sqlite> SELECT * from switch;
mac|hostname|model|location
0000.AAAA.CCCC|sw1|Cisco 3750|London, Green Str
0000.BBBB.CCCC|sw5|Cisco 3850|London, Green Str

Заголовки отобразились, но в целом отображение не очень приятное. Хотелось бы, чтобы все выводилось в виде колонок. За форматирование вывода отвечает команда .mode.

Нам нужен режим .mode column:

sqlite> .mode column
sqlite> SELECT * from switch;
mac             hostname    model       location         
--------------  ----------  ----------  -----------------
0000.AAAA.CCCC  sw1         Cisco 3750  London, Green Str
0000.BBBB.CCCC  sw5         Cisco 3850  London, Green Str

При желании, можно выставить и ширину колонок. Для этого используется команда .width. Например, попробуйте выставить .width 20.

[править] ALTER

Теперь попробуем изменить таблицу. Добавим новые поля в таблицу switch.

Добавим в определение таблицы такие новые поля:

  • mngmt_ip - IP-адрес коммутатора в менеджмент VLAN
  • mngmt_vid - VLAN ID (номер VLAN) для менеджмент VLAN
  • mngmt_vname - Имя VLAN, который используется для менеджмента

Добавление записей выполняется с помощью DDL, используя команду ALTER:

sqlite> ALTER table switch ADD COLUMN mngmt_ip text;
sqlite> ALTER table switch ADD COLUMN mngmt_vid varchar(10);
sqlite> ALTER table switch ADD COLUMN mngmt_vname  text;

Теперь таблица выглядит так (новые поля установлены в значение NULL):

sqlite> SELECT * from switch;
mac             hostname    model       location           mngmt_ip    mngmt_vid   mngmt_vname
--------------  ----------  ----------  -----------------  ----------  ----------  -----------
0000.AAAA.CCCC  sw1         Cisco 3750  London, Green Str                                     
0000.BBBB.CCCC  sw5         Cisco 3850  London, Green Str                                    

Note-icon.gif

Задание:

Добавить такие значения в новые поля:

  • sw1:
    • mngmt_ip 10.255.0.1
    • mngmt_vid 255
    • mngmt_vname MNGMT
  • sw5:
    • mngmt_ip 10.255.0.5
    • mngmt_vid 255
    • mngmt_vname MNGMT

Если в процессе выполнения задания надо будет удалить неправильную запись, можно использовать команду (изменив по аналогии):

  • delete from switch where mngmt_vid = 255;

В итоге таблица должна выглядеть так:

sqlite> SELECT * from switch;
mac             hostname    model       location           mngmt_ip    mngmt_vid   mngmt_vname
--------------  ----------  ----------  -----------------  ----------  ----------  -----------
0000.AAAA.CCCC  sw1         Cisco 3750  London, Green Str  10.255.0.1  255         MNGMT      
0000.BBBB.CCCC  sw5         Cisco 3850  London, Green Str  10.255.0.5  255         MNGMT      

[править] UPDATE

Если информация о каком-то из коммутаторов изменилась, и необходимо изменить одно из полей, используется команда UPDATE.

Например, предположим, что sw1 был заменен с модели 3750 на модель 3850. Соответственно, изменилось не только поле модель, но и поле MAC-адрес.

Внесем изменения и проверим результат:

sqlite> UPDATE switch set model = 'Cisco 3850' where hostname = 'sw1';
sqlite> UPDATE switch set mac = '0000.DDDD.DDDD' where hostname = 'sw1';

sqlite> SELECT * from switch;
mac             hostname    model       location           mngmt_ip    mngmt_vid   mngmt_vname
--------------  ----------  ----------  -----------------  ----------  ----------  -----------
0000.DDDD.DDDD  sw1         Cisco 3850  London, Green Str  10.255.0.1  255         MNGMT      
0000.BBBB.CCCC  sw5         Cisco 3850  London, Green Str  10.255.0.5  255         MNGMT      

[править] WHERE

Оператор WHERE используется для уточнения запроса. С помощью этого оператора мы можем указывать определенные условия, по которым отбираются данные. Если условие выполнено, возвращается соответствующее значение из таблицы, если нет, не возвращается.

Например, наша таблица с коммутаторами немного разрослась:

sqlite> SELECT * from switch;
mac             hostname    model       location           mngmt_ip    mngmt_vid   mngmt_vname
--------------  ----------  ----------  -----------------  ----------  ----------  -----------
0000.DDDD.DDDD  sw1         Cisco 3850  London, Green Str  10.255.0.1  255         MNGMT      
0000.BBBB.CCCC  sw5         Cisco 3850  London, Green Str  10.255.0.5  255         MNGMT      
0000.2222.CCCC  sw2         Cisco 3750  London, Green Str  10.255.0.2  255         MNGMT      
0000.3333.CCCC  sw3         Cisco 3750  London, Green Str  10.255.0.3  255         MNGMT      
0000.4444.CCCC  sw4         Cisco 3650  London, Green Str  10.255.0.4  255         MNGMT      

Воспользуется оператором WHERE и отфильтруем вывод. Отобразим информацию только о тех коммутаторах, модель которых 3750:

sqlite> SELECT * from switch WHERE model = 'Cisco 3750';
mac             hostname    model       location           mngmt_ip    mngmt_vid   mngmt_vname
--------------  ----------  ----------  -----------------  ----------  ----------  -----------
0000.2222.CCCC  sw2         Cisco 3750  London, Green Str  10.255.0.2  255         MNGMT      
0000.3333.CCCC  sw3         Cisco 3750  London, Green Str  10.255.0.3  255         MNGMT      

В данном случае нам пришлось писать полностью все строку, которая соответствет полю model. Если бы поле было записано в разном формате, то мы бы уже не смогли таким образом вывести нужные коммутаторы.

Например, если в таблице поле model записано в разном формате:

sqlite> SELECT * from switch;
mac             hostname    model       location           mngmt_ip    mngmt_vid   mngmt_vname
--------------  ----------  ----------  -----------------  ----------  ----------  -----------
0000.DDDD.DDDD  sw1         Cisco 3850  London, Green Str  10.255.0.1  255         MNGMT      
0000.BBBB.CCCC  sw5         Cisco 3850  London, Green Str  10.255.0.5  255         MNGMT      
0000.2222.CCCC  sw2         C3750       London, Green Str  10.255.0.2  255         MNGMT      
0000.3333.CCCC  sw3         Cisco 3750  London, Green Str  10.255.0.3  255         MNGMT      
0000.4444.CCCC  sw4         Cisco 3650  London, Green Str  10.255.0.4  255         MNGMT      

В таком варианте предыдущий запрос с оператором WHERE нам не поможет:

sqlite> SELECT * from switch WHERE model = 'Cisco 3750';
mac             hostname    model       location           mngmt_ip    mngmt_vid   mngmt_vname
--------------  ----------  ----------  -----------------  ----------  ----------  -----------
0000.3333.CCCC  sw3         Cisco 3750  London, Green Str  10.255.0.3  255         MNGMT      

Но вместе с оператором WHERE мы можем использовать оператор LIKE:

sqlite> SELECT * from switch WHERE model LIKE '%3750';
mac             hostname    model       location           mngmt_ip    mngmt_vid   mngmt_vname
--------------  ----------  ----------  -----------------  ----------  ----------  -----------
0000.2222.CCCC  sw2         C3750       London, Green Str  10.255.0.2  255         MNGMT      
0000.3333.CCCC  sw3         Cisco 3750  London, Green Str  10.255.0.3  255         MNGMT      

LIKE с помощью символов '_' и '%' указывает на что должно быть похоже значение:

  • '_' - обозначает один символ или число
  • '%' - обозначает ноль, один или много символов

[править] DELETE

DELETE используется для удаления записей. Например:

sqlite> DELETE from switch where hostname = 'sw4';

[править] ORDER BY

И еще один полезный оператор ORDER BY. Он используется для сортировки вывода по определенному полю, по возрастанию или убыванию.

Например, выведем все записи в таблице switch и отсортируем их по имени коммутаторов (по умолчанию выполняется сортировка по умолчанию, поэтому параметр ASC можно не указывать):

sqlite> SELECT * from switch ORDER BY hostname ASC;
mac             hostname    model       location           mngmt_ip    mngmt_vid   mngmt_vname
--------------  ----------  ----------  -----------------  ----------  ----------  -----------
0000.DDDD.DDDD  sw1         Cisco 3850  London, Green Str  10.255.0.1  255         MNGMT      
0000.2222.CCCC  sw2         C3750       London, Green Str  10.255.0.2  255         MNGMT      
0000.3333.CCCC  sw3         Cisco 3750  London, Green Str  10.255.0.3  255         MNGMT      
0000.BBBB.CCCC  sw5         Cisco 3850  London, Green Str  10.255.0.5  255         MNGMT      

[править] Модуль sqlite3

Для работы с SQLite в Python используется модуль sqlite3.

Модуль sqlite3 предоставляет совместимый с DB-API 2.0 интерфейс для работы с SQLite.

Рассмотрим основные понятия, которые использует модуль для работы с SQLite.

[править] Connection

Объект Connection - это подключение к конкретной БД, можно сказать, что этот объект представляет БД. Например, мы можем его создать так:

  • conn = sqlite3.connect('dhcp_snooping.db')

У объекта Connection есть несколько методов, с помощью которых, мы можем выполнять команды SQL:

  • execute() - метод для выполнения одного выражения SQL
  • executemany() - этот метод позволяет выполнить выражение SQL для последовательности параметров (или для итератора)
  • executescript() - этот позволяет выполнить несколько выражений SQL за один раз

[править] Cursor

Для чтения данных из БД, используется объект Cursor - это основной способ работы с БД. Создается курсор из соединения с БД:

  • cursor = sqlite3.connect('dhcp_snooping.db').cursor()

Note-icon.gif

На самом деле, при использовании методов:

  • Connection.execute()
  • Connection.executemany()
  • Connection.executescript()

также создается курсор, но неявно, и фактически эти методы применяются к объекту Cursor.

При чтении данных, мы должны сначала выполнить выражение SQL, используя один из методов (описаны выше):

  • execute()
  • executemany()
  • executescript()

А затем, с помощью метода fetch...(), мы обрабатываем полученные данные, и, в зависимости от метода, возвращаем их:

  • fetchone() - возвращает одну строку данных
  • fetchmany() - возвращает список строк данных. С помощью параметра size, можно указывать какое количество строк возвращается
  • fetchall() - возвращает все строки в результате запроса, в виде списка

[править] Connection как менеджер контекста

После выполнения всех необходимых операций, изменения должны быть сохранены (надо выполнить commit()), а затем можно закрыть курсор, если он больше не нужен.

При работе с SQLite, мы можем использовать объект Connection, как менеджер контекста и, в таком случае, нам не надо явно делать commit и закрывать соединение:

  • при возникновении исключения, транзакция автоматически откатывается
  • если исключения не было, автоматически выполняется commit

Пример использования соединения с базой, как менеджера контекстов:

  • with sqlite3.connect('test.db') as conn

[править] Пример использования SQLite

В разделе "Регулярые выражения" был пример разбора вывода команды show ip dhcp snooping binding. На выходе, мы получили информацию о параметрах подключенных устройств (interface, IP, MAC, VLAN).

Результат хороший, но в таком варианте мы можем посмотреть только все подключенные устройства к коммутатору. Если же нам нужно узнать на основании одного из параметров, другие, то в таком виде это не очень удобно.

Например, если нам нужно по IP-адресу получить информацию о том, к какому интерфейсу подключен компьютер, какой у него MAC-адрес и в каком он VLAN, то по выводу скрипта это сделать не очень просто и, главное, не очень удобно.

Попробуем записать в SQLite информацию полученную из вывода sh ip dhcp snooping binding.

Это позволит нам легко делать запрос по любому параметру и получать недостающие.

Для этого примера нам достаточно одной таблицы, где будет храниться информация.

Определение таблицы будет прописано в отдельном файле dhcp_snooping_schema.sql и выглядит так:

create table dhcp (
    mac          text primary key,
    ip           text,
    vlan         text,
    interface    text
);

Для всех полей определен тип данных "текст". И MAC-адрес является первичным ключом нашей таблицы. Что вполне логично, так как, MAC-адрес должен быть уникальным.

Теперь попробуем создать файл БД, подключиться к базе данных и создать таблицу.

Файл create_sqlite3_ver1.py:

import sqlite3

with sqlite3.connect('dhcp_snooping.db') as conn:
    print 'Creating schema...'
    with open('dhcp_snooping_schema.sql', 'rt') as f:
        schema = f.read()
        conn.executescript(schema)
    print "Done"

Комментарии к файлу:

  • используем менеджер контекста with ... as
  • при выполнении строки with sqlite3.connect('dhcp_snooping.db') as conn:
    • создается файл dhcp_snooping.db, если его нет
    • создается объект Connection
  • в БД создается таблица, на основании команд, которые указаны в файле dhcp_snooping_schema.sql:
    • открываем файл 'dhcp_snooping_schema.sql'
    • schema = f.read() - считываем весь файл как одну строку
    • conn.executescript(schema) - метод executescript позволяет выполнять несколько команд SQL, которые прописаны в файле (в данном случае команда одна)

В результате должен быть создан файл СУБД и таблица dhcp.

Проверить, что таблица создалась, мы можем с помощью утилиты sqlite3, которая позволяет выполнять запросы прямо в командной строке.

Выведем список созданных таблиц:

nata:$ sqlite3 dhcp_snooping.db "SELECT name FROM sqlite_master WHERE type='table'"
dhcp

Пока что в нашей таблице ничего нет, поэтому, остановимся на этом.

Теперь нам нужно записать информацию, которую мы получили из вывода 'sh ip dhcp snooping binding' в таблицу.

Дополняем наш скрипт create_sqlite3_ver1.py таким образом, чтобы он и парсил команду (переносим сюда регулярные выражения) и добавлял записи из файла dhcp_snooping.txt в БД:

import sqlite3
import re

regex = re.compile('(.+?) +(.*?) +\d+ +[\w-]+ +(\d+) +(.*$)')
result = []

with open('dhcp_snooping.txt') as data:
    for line in data:
        if line[0].isdigit():
            result.append(regex.search(line).groups())

with sqlite3.connect('dhcp_snooping.db') as conn:
    print 'Creating schema...'
    with open('dhcp_snooping_schema.sql', 'rt') as f:
        schema = f.read()
        conn.executescript(schema)
    print "Done"

    print 'Inserting DHCP Snooping data'

    for val in result:
        query = """insert into dhcp (mac, ip, vlan, interface)
        values (?, ?, ?, ?)""" 
        conn.execute(query, val)

Note-icon.gif

Обратите внимание, что пока что, нам надо удалять файл БД каждый раз, так как наш скрипт его каждый раз пытается создать.

Комментарии к скрипту:

  • в регулярном выражении, которое проходится по выводу команды sh ip dhcp snooping binding, используются не именованные группы, как в примере раздела "Регулярные выражения"
    • группы созданы только для тех элементов, которые нас интересуют
  • result - это список, в котором хранится результат обработки команды
    • но теперь тут не словари, а кортежи с результатами
    • это нужно для того, чтобы их можно было сразу передавать на запись в БД
  • Перебираем в полученном списке кортежей, элементы
  • В этом скрипте мы используем еще один вариант записи в БД
    • строка query описывает запрос. Но, вместо значений мы указываем знаки вопроса
    • затем методу execute, мы передаем строку запроса и кортеж val, где находятся значения

Note-icon.gif

Эту часть в скрипте

    for val in result:
        query = """insert into dhcp (mac, ip, vlan, interface)
        values (?, ?, ?, ?)""" 
        conn.execute(query, val)

можно было бы заменить таким образом:

    query = """insert into dhcp (mac, ip, vlan, interface) values (?, ?, ?, ?)"""
    conn.executemany(query, result)

Метод executemany сам загружает данные, перебирая их внутри самой библиотеки sqlite3.

Такой вариант не всегда хорош, так как, в случае возникновении проблемы, не запишутся все записи.

Проверим, что данные записались:

nata:~$ sqlite3 dhcp_snooping.db "select * from dhcp"
00:09:BB:3D:D6:58|10.1.10.2|10|FastEthernet0/1
00:04:A3:3E:5B:69|10.1.5.2|5|FastEthernet0/10
00:05:B3:7E:9B:60|10.1.5.4|5|FastEthernet0/9
00:07:BC:3F:A6:50|10.1.10.6|10|FastEthernet0/3
00:09:BC:3F:A6:50|192.168.100.100|1|FastEthernet0/7

Теперь попробуем запросить по определенному параметру:

nata:~$ sqlite3 dhcp_snooping.db "select * from dhcp where ip = '10.1.5.2'"
00:04:A3:3E:5B:69|10.1.5.2|5|FastEthernet0/10

То есть, теперь на основании одного параметра, мы можем получать остальные.

Переделаем наш скрипт таким образом, чтобы была проверка файла на существование. Если файл БД есть, то не надо создавать таблицу, считаем, что она уже создана.

Файл create_sqlite_db_ver2.py:

import os
import sqlite3
import re

data_filename = 'dhcp_snooping.txt'
db_filename = 'dhcp_snooping.db'
schema_filename = 'dhcp_snooping_schema.sql'

regex = re.compile('(.+?) +(.*?) +\d+ +[\w-]+ +(\d+) +(.*$)')

with open(data_filename) as data:
    result = [regex.search(line).groups() for line in data if line[0].isdigit()]

db_exists = os.path.exists(db_filename)

with sqlite3.connect(db_filename) as conn:
    if not db_exists:
        print 'Creating schema...'
        with open(schema_filename, 'rt') as f:
            schema = f.read()
        conn.executescript(schema)
        print 'Done'

        print 'Inserting DHCP Snooping data'
        for val in result:
            query = """insert into dhcp (mac, ip, vlan, interface)
            values (?, ?, ?, ?)"""
            conn.execute(query, val)
    else:
        print 'Database exists, assume dhcp table does, too.'

Теперь у нас есть проверка наличия файла БД, и файл БД будет создаваться только, если его нет.

Проверим. В случае если файл уже есть:

nata:$ python create_sqlite_db_ver2.py 
Database exists, assume dhcp table does, too.

Если файла нет (предварительно его удаляем):

nata:$ rm dhcp_snooping.db
nata:$ python create_sqlite_db_ver2.py 
Creating schema...
Done
Inserting DHCP Snooping data

Теперь делаем отдельный скрипт, который занимается отправкой запросов в БД и выводом результатов. Он должен:

  • ожидать от пользователя ввода параметров:
    • имя параметра
    • значение параметра
  • делать нормальный вывод данных по запросу

get_data_ver1.py

# -*- coding: utf-8 -*-
import sqlite3
import sys

db_filename = 'dhcp_snooping.db'

if len(sys.argv) == 1:
    print "\nВ таблице dhcp такие записи:"
    print '-' * 70
    with sqlite3.connect(db_filename) as conn:
        cursor = conn.cursor()
        cursor.execute('select * from dhcp')

        for row in cursor.fetchall():
            print '%-18s %-17s %-5s %-20s' % row

elif len(sys.argv) == 3:
    key, value = sys.argv[1:]
    keys = ['mac', 'ip', 'vlan', 'interface']
	#Проверка указанного ключа (параметра)
    if key in keys:
        keys.remove(key)
        with sqlite3.connect(db_filename) as conn:
            #Позволяет далее обращаться к данным в колонках, по имени колонки
            conn.row_factory = sqlite3.Row

            cursor = conn.cursor()

            cursor.execute("select * from dhcp where %s = ?" % key, (value,))

            print "\nDetailed information for host(s) with", key, value
            print '-' * 40
            for row in cursor.fetchmany(10):
                for k in keys:
                    print "%-12s: %s" % (k,row[k])
                print '-' * 40
    else:
        print "Данный параметр не поддерживается."
        print "Допустимые значения параметров: mac, ip, vlan, interface"
else:
    print "Введите, пожалуйста, два параметра"

Комментарии к скрипту:

  • сначала проверяем количество параметров, которые ввел пользователь
    • если параметры не переданы, то отображаем все содержимое БД
      • в проверке длинна sys.argv равно 1, из-за того, что имя скрипта тоже находится в этом списке
      • затем обрабатываем все результаты с помощью метода fetchall() и выводим их таблицей
    • если было передано 2 параметра (3 вместе с именем скрипта):
      • проверяем правильное ли было введено имя ключа (параметра)
        • если правильно, то подключаемся к БД:
          • conn.row_factory = sqlite3.Row - позволяет далее обращаться к данным в колонках, по имени колонки
          • выбираем из БД те строки, в которых ключ равен указанному значению и выводим их
        • если имя параметра было указано неправильно, выводим сообщение об ошибке
    • если был передан только один параметр, выводим сообщение об ошибке

Проверим работу скрипта.

Сначала вызовем скрипт без параметров (должно быть показано содержание БД):

nata:$ python get_data_ver1.py 

В таблице dhcp такие записи:
----------------------------------------------------------------------
00:09:BB:3D:D6:58  10.1.10.2         10    FastEthernet0/1     
00:04:A3:3E:5B:69  10.1.5.2          5     FastEthernet0/10    
00:05:B3:7E:9B:60  10.1.5.4          5     FastEthernet0/9     
00:07:BC:3F:A6:50  10.1.10.6         10    FastEthernet0/3     
00:09:BC:3F:A6:50  192.168.100.100   1     FastEthernet0/7     

Показать параметры хоста с IP 10.1.10.2:

nata:$ python get_data_ver1.py ip 10.1.10.2

Detailed information for host(s) with ip 10.1.10.2
----------------------------------------
mac         : 00:09:BB:3D:D6:58
vlan        : 10
interface   : FastEthernet0/1
----------------------------------------

Показать хосты в VLAN 10:

nata:$ python get_data_ver1.py vlan 10

Detailed information for host(s) with vlan 10
----------------------------------------
mac         : 00:09:BB:3D:D6:58
ip          : 10.1.10.2
interface   : FastEthernet0/1
----------------------------------------
mac         : 00:07:BC:3F:A6:50
ip          : 10.1.10.6
interface   : FastEthernet0/3
----------------------------------------

Проверим скрипт на ошибки.

Сначала зададим неправильно название параметра:

nata:$ python get_data_ver1.py vln 10
Данный параметр не поддерживается.
Допустимые значения параметров: mac, ip, vlan, interface

Указываем имя параметра без значения параметра:

nata:$ python get_data_ver1.py vlan
Введите, пожалуйста, два параметра

На этом мы завершаем работу с этим примером в данном разделе. Но ниже, в разделе с практическими примерами, мы будем дальше развивать этот скрипт.

[править] Scapy

[править] Ansible

На данный момент существует несколько систем управления конфигурациями.

Однако, для работы с сетевым оборудованием, больше всего подходит Ansible. Связано это с тем, что Ansible не требует установки агента на управляемые хосты.

[править] Основы Ansible

Ansible:

  • Работает без установки агента на управляемые хосты
  • Использует SSH для подключения к управляемым хостам
  • Выполняет изменения, с помощью модулей Python, которые выполняются на управляемых хостах
  • Может выполнять действия локально, на управляющем хосте
  • Использует YAML для описания конфигурационных файлов
  • Содержит множество модулей
  • Легко писать свои модули

[править] Терминология

  • Control machine — управляющий хост. Сервер Ansible, с которого происходит управление другими хостами
  • Manage node — управляемые хосты
  • Inventory — инвентарный файл. В этом файле описываются хосты, группы хостов. А также могут быть созданы переменные
  • Playbook — файл сценариев
  • Play — сценарий (набор действий). Связывает несколько действий с хостами, для которых эти действия надо выполнить
  • Task — действие. Вызывает модуль с указанными параметрами и переменными
  • Module — модуль Ansible. Реализует определенные функции

[править] Варианты подключения к управляемым хостам

Ansible поддерживает такие типы подключений:

  • paramiko - Python SSH модуль
  • SSH - OpenSSH
  • local - действия выполняются локально, на управляющем хосте

По умолчанию используется OpenSSH. Но, можно указать, что нужно использовать paramiko.

При подключении по SSH, по умолчанию предполагается использование SSH ключей. Но, при желании, можно переключиться на использование паролей.


Для выполнения действий на управляемых хостах, Ansible загружает на хост модуль Python и выполняет его.

Однако, это не единственный вариант работы с управляемыми хостами. Ansible может выполнять модули Python и локально, на управляющем хосте.

[править] Практические примеры использования Python

[править] Подключение к сетевому оборудованию (Telnet/SSH)

[править] Использование Jinja2 для создания шаблонов конфигурации