Python 第三方库详解

亦余心之所善兮,虽九死其犹未悔。这篇文章主要讲述Python 第三方库详解相关的知识,希望能为你提供帮助。
JSONJSON是一种轻量级的数据交换格式。虽然 JSON 基于 javascript开发,但它是一种“语言无关”的文本格式,并且采用 C 语言家族,比如 C、C++、C#、java、python 和 Perl 等语言的用法习惯,成为一门理想的数据交换语言.
JSON 基础知识JSON 的数据结构具有易读的特点,它由键值对(Collection)和对象(Object)组成,在结构上非常类似 Python 的字典(Dictionary),如下是一个典型的 JSON 数据格式。


"intf":"Gigabitethernet0/0",
"status":"up"

与 Python 的字典一样,JSON 的键值对也由冒号分开,冒号左边的"intf"和"status"即键值对的键(Name),冒号右边的"Gigabitethernet0/0"和"up"即键值对的值(Value),每组键值对之间都用逗号隔开。
JSON 与字典不一样的地方如下。
(1)JSON 里键的数据类型必须为字符串,而在字典里字符串、常数、浮点数或者元组等都能作为键的数据类型。
(2)JSON 里键的字符串内容必须使用双引号""括起来,不像字典里既可以用单引号,又可以用双引号来表示字符串。
JSON 里键值对的值又分为两种形式,一种形式是简单的值,包括字符串、整数等,比如上面的 "Gigabitethernet0/0"和 "up"就 是 一 种 简 单 的 值 。 另 一 种 形 式 被 称 为 对 象(Object),对象内容由大括号表示,对象中的键值对之间用逗号分开,它们是无序的/
举例如下。
"Vendor":"Cisco", "Model":"2960"

当有多组对象存在时,我们将其称为 JSON 阵列(JSON Array),阵列以中括号 [] 表示,阵列中的元素(即各个对象)是有序的(可以把它理解为列表),举例如下。

"devices":[
"Vendor":"Cisco", "Model":"2960",
"Vendor":"Cisco", "Model":"3560",
"Vendor":"Cisco", "Model":"4500"
]

JSON 在 Python 中的使用Python 中已经内置了 JSON 模块,需要使用时只需 import json 即可,如下所示。

JSON 模块主要有两种函数:json.dumps()和 json.loads()。前者是 JSON 的编码器(Encoder),用来将 Python 中的对象转换成 JSON 格式的字符串,如下所示。

由此可以看到,我们用 json.dumps()将 Python 中 3 种类型的对象:字符串(parry)、字典("c": 0, "b": 0, "a": 0)、列表(json.dumps([1,2,3]))转换成了 JSON 格式的字符串,并用 type()函数进行了验证。
而 json.loads()的用法则是将 JSON 格式的字符串转换成 Python 的对象,如下所示。

我们将两个 JSON 格式的字符串:[1,2,3]和 "vendor":"Cisco", "model":"2960",用json.loads()转换成了它们各自对应的 Python 对象:列表[1,2,3]和字典"vendor":"Cisco", "model":"2960"。需要注意的是,在创建对应 Python 字典类型的 JSON 字符串时,如果对键使用了单引号,则 Python 会返回“SyntaxError: invalid sytanx”,提示语法错误,如下所示。

原因就是:JSON 里键的字符串内容必须使用双引号""括起来,不像字典里既可以用单引号,又可以用双引号来表示字符串,这点请务必注意。
正则表达式的痛点我们知道,在 Python 中可以使用正则表达式来对字符串格式的文本内容做解析(Parse),从而匹配到我们感兴趣的文本内容,但是这么做有一定限制,比如我们需要用正则表达式从下面一台 2960 交换机 show ip int brief 命令的回显内容中找出哪些物理端口是 Up 的。

如果用常规思维的正则表达式 GigabitEhternet\\d\\/\\d 来匹配,则会将除       VLAN1 外的所有物理端口都匹配上,显然这种做法是错误的,因为当前只有 GigabitEthernet0/0 这一个物理端口的状态是 Up 的,如下图所示。

读者朋友也许此时想到了另一种方法,那就是在交换机的 show ip int brief 后面加上| i up 提前做好过滤,再用正则表达式 GigabitEhternet\\d\\/\\d 来配,比如下图这样。

【Python 第三方库详解】这种方法确实可以匹配出我们想要的结果,但是如果这时把要求变一变:找出当前交换机下所有 Up 的端口(注意,现在不再只是找出所有 Up 的物理端口,虚拟端口 Vlan1也要算进去),并且同时给出它们的端口号及 IP 地址。
这个要求意味着不仅要用正则表达式匹配到上面文本里的 GigabitEthernet0/0、Vlan1、unassigned、192.168.2.11 四项内容,还要保证正则表达式没有匹配到其他诸如 YES、unset、NVRAM、up 等文本内容,怎么样?这个难度是不是立即上升了几个等级?
为了解决类似这样的正则表达式的痛点,我们必须搬出“救兵”TextFSM 了。
TextFSM 和 ntc-templatesTextFSM 最早是由 Google 开发的一个开源 Python 模块,它能使用自定义的变量和规则设计出一个模板(Template),然后用该模板来处理文本内容,将这些无规律的文本内容按照自己打造的模板来将它们整合成想要的有序的数据格式。
举个例子,在前面的例子中,如果有办法把 show ip int brief 的回显内容转换成 JSON,将它们以 JSON 阵列的数据格式列出来,是不是会很方便配合 for 循环来匹配出我们想要的东西?比如看到下面这样的 JSON 阵列后,读者朋友们是不是想到点什么呢?
[

"intf": "GigabitEthernet0/0",
"ipaddr": "unassigned",
"status": "up",
"proto": "up"
,

"intf": "GigabitEthernet0/1",
"ipaddr": "unassigned",
"status": "down",
"proto": "down"
,

"intf": "GigabitEthernet0/2",
"ipaddr": "unassigned",
"status": "down",
"proto": "down"
,

"intf": "GigabitEthernet0/3",
"ipaddr": "unassigned",
"status": "down",
"proto": "down"
,
]

可以看到,JSON 阵列格式在 Python 中实际上是一个数据类型为列表的对象(以 [开头,以]结尾),既然是列表,那我们就能很方便地使用 for 语句遍历列表中的每个元素(这里所有的元素均为字典),并配合 if 语句,将端口状态为“up”(“status”键对应的值)的端口号(“intf”键对应的值)和 IP 地址(“ipaddr”键对应的值)一一打印出来即可
下面介绍如何在 Python 中使用 TextFSM 创建我们需要的模板。
TextFSM 的安装作为 Python 的第三方模块,TextFSM 有两种安装方法。
第一种方法是使用 pip 安装,如下所示。

pip 安装完毕后进入 Python 并 import textfsm,如果 Python 没有报错,则说明安装成功,如下所示。

第二种方法是使用 git clone 命令从 GitHub 下载 textfsm 的源码,如果你的 CentOS 8主机没有安装 Git,系统会提醒你一并安装,具体如下所示。
Git clone https://github.com/google/textfsm.git


源码下载后,会看到当前目录下多出一个 textfsm 文件夹。进入该文件夹后,会看到如下所示的 setup.py 文件。

然后输入下面命令执行该 py 文件进行安装,如下所示。
python3.8 setup.py install


TextFSM 模板的创建和应用TextFSM 的语法本身并不难,但是使用者必须熟练掌握正则表达式。下面我们以思科Nexus 7000 交换机上 show vlan 命令的回显内容为例,如下所示,来看如何用 TextFSM 创建模板,以及如何使用模板将该 show vlan 命令的回显内容整理成我们想要的数据格式。

我们创建一个 TextFSM 模板,模板内容如下。
Value VLAN_ID (\\d+)
Value NAME (\\w+)
Value STATUS (\\w+)

Start
^$VLAN_ID\\s+$NAME\\s+$STATUS\\s+ -> Record

(1)在 TextFSM 中,我们使用 Value 语句来定义变量,这里定义了 3 个变量,分别为VLAN_ID、NAME 和 STATUS。
Value VLAN_ID (\\d+)
Value NAME (\\w+)
Value STATUS (\\w+)

(2)每个变量后面都有它自己对应的正则表达式模式(Pattern),这些模式写在括号()中。
比如变量 VLAN_ID 顾名思义是要去匹配 VLAN 的 Id 号的,所以它后面的正则表达式模式写为(\\d+)。\\d 这个特殊序列用来匹配数字,后面的+用来做贪婪匹配。
变量 NAME 是用来匹配 VLAN 的名称的,因为这里 VLAN 的名称掺杂了字母和数字,比如 VLAN0002,所以它的正则表达式模式写为(\\w+)。\\w 这个特殊序列用来匹配字母或数字,配合后面的+用来做贪婪匹配。
变量 STATUS(\\w+)用来匹配VLAN 状态,VLAN 状态会有 active 和 inactive 之分。
(3)在定义好变量后,我们使用 Start 语句来定义匹配规则,匹配规则由正则表达式的模式及变量名组成。
Start
^$VLAN_ID\\s+$NAME\\s+$STATUS\\s+ -> Record

(4)Start 语句后面必须以正则表达式^开头。^是正则表达式中的一种特殊字符,用于匹配输入字符串的开始位置,注意紧随其后的$不是正则表达式里的$,它的作用并不是用来匹配输入字符串的结尾位置,而是用来调用我们之前设置好的 VLAN_ID 并匹配该变 量。注意,在 TextFSM 中调用变量时可以用大括号,写成$VLAN_ID,也可以不用,写成$VLAN_ID,但是 TextFSM 官方推荐使用大括号。VLAN_ID 对应的正则表达式恰巧是\\d+,这样就匹配到了 1、2、3、4、5、6、7、8 这些 VLAN_ID,而后面的\\s+$则表示匹配 1、2、3、4、5、6、7、8 后面的空白字符(\\s 这个特殊序列用来匹配空白字符)。
^$VLAN_ID\\s+

(5)同理,我们调用变量 NAME,它对应的正则表达式模式为\\w+,该特殊序列用来匹配“show vlan”命令回显内容中的 default、VLAN0002、VLAN0003、…、VLAN0008等内容,而后面的\\s+则用来匹配之后所有的空白字符。
$NAME\\s+

(6)变量STATUS也一样,它对应的\\w+用来匹配 active 和 inactive 这两种 VLAN 状态(例子中给出的 show vlan 的回显内容中没有 inactive,但是不影响理解)以及后面的空白字符。
最后用-> Record 来结束 TextFSM 的匹配规则。
$STATUS\\s+ -> Record

在了解了 TextFSM 的语法基础后,接下来看怎么在 Python 中使用 TextFSM。首先将上面的 TextFSM 模板文件以文件名 show_vlan.template 保存,如下所示。

然后在相同的文件夹下创建一个名叫 textfsm_demo.py 的 Python 脚本。

将下面的代码写入该脚本。

代码分段讲解如下。
(1)首先我们用 from textfsm import TextFSM 引入 TextFSM 模块的 TextFSM 函数,该
函数为 TextFSM 模块下最核心的类。
from textfsm import TextFSM

(2)将 show vlan 的回显内容以三引号字符串的形式赋值给变量 output。
output =
N7K# show vlan
VLAN Name Status Ports
----------------------------------------------------------------------------
1 default active Eth1/1, Eth1/2, Eth1/3
Eth1/5, Eth1/6, Eth1/7
2 VLAN0002 active Po100, Eth1/49, Eth1/50
3 VLAN0003 active Po100, Eth1/49, Eth1/50
4 VLAN0004 active Po100, Eth1/49, Eth1/50
5 VLAN0005 active Po100, Eth1/49, Eth1/50
6 VLAN0006 active Po100, Eth1/49, Eth1/50
7 VLAN0007 active Po100, Eth1/49, Eth1/50
8 VLAN0008 active Po100, Eth1/49, Eth1/50

(3)打开之前创建好的模板文件 show_vlan.template, 调用 TextFSM()函数将它赋值给变量 template,最后调用 template 下的 ParseText()函数对文本内容做解析,ParseText()函数中的参数 output 即 show vlan 命令的回显内容,最后用 print()函数将被模板解析后的回显内容打印出来,看看是什么样的内容。
f = open(show_vlan.template)
template = TextFSM(f)
print (template.ParseText(output))

一切就绪后,执行脚本看效果,如下所示。

由此可以看到,之前无序的纯字符串文本内容在被我们创建的 TextFSM 模板解析后,已经被有序的嵌套列表替代,方便我们配合 for 循环做很多事情。
ntc-templates用 TextFSM 制作的模板很好用,但是缺点也很明显:每个 TextFSM 模板都只能对应一条 show 或者 display 命令的回显文本内容,而目前每家知名厂商的网络设备都有上百种show 或 display 命令,并且每家厂商的回显内容和格式都完全不同,有些厂商还有多种不同的操作系统,比如思科就有 ios、IOS-XE、IOS-XR、NX-OS、ASA、 WLC 等多种 OS版本,这些版本又有各自特有的 show 命令,难道我们必须自己动手造轮子,每种操作系统的每条命令都要靠自己手动写一个对应的模板吗?不用担心,已经有前人帮我们造好了轮子,这就是 ntc-templates。
我们可以通过 Netmiko 模块来调用 TextFSM 和 ntc-tempalte。接下来就以
实验的形式演示如何使用 ntc-template。
(1)首先确认主机的 Python 里安装了 Netmiko 模块,如下所示。

(2)在根目录下创建一个名为 ntc-template 的文件夹,如下所示。

(3)移动到该文件夹下,如下所示。

(4)然后用下面的 git clone 命令下载 ntc-template.git 文件,如下所示。
git clone https://github.com/networktocode/ntc-templates.git


(5)安装完成后,可以用 ls 命令看到在/ntc-template 下面多出了另一个 ntc-templates文件夹,再依次输入命令 cd ntc-templates/templates/和 ls,就能看到全部的 ntc-templates模板集了,如下所示。

(6)因为在 Netmiko 里是用变量 NET_TEXTFSM 来调用 ntc-templates 模板集的,所以还要用 export 来设置相应的环境变量,将 ntc-templates 模板集的完整路径/ntc-template/ntc-templates/templates 赋值给变量 NET_TEXTFSM,然后用 echo 命令检验,如下所示。
export NET_TEXTFSM=/ntc-template/ntc-templates/templates
echo $NET_TEXTFSM


注:export 命令只在当前生效,下次 CentOS 重启后用 export 命令设置的环境变量就会失效,可以使用 cd~命令回到当前用户的家目录,然后用 vi 编辑家目录下的.bashrc 文件,如下所示。

将 export NET_TEXTFSM=/ntc-template/ntc-templates/templates 写在      .bashrc 的最下面并保存,这样下次 CentOS 重启后环境变量设置仍然有效,如下所示。

(7)一切准备就绪后,创建一个 Python 3 的脚本,脚本内容如下。
from Netmiko import ConnectHandler
import json

SW1 =
device_type: cisco_ios,
ip: 192.168.2.11,
username: python,
password: 123,


connect = ConnectHandler(**SW1)
print ("Sucessfully connected to " + SW1[ip])
interfaces = connect.send_command(show ip int brief, use_textfsm=True)
print (json.dumps(interfaces,

    推荐阅读