前言
《从零开始NetDevOps》是本人8年多的NetDevOps实战总结的一本书(且称之为书,通过公众号连载的方式,集结成册,希望有天能以实体书的方式和大家相见)。
NetDevOps是指以网络工程师为主体,针对网络运维场景进行自动化开发的工作思路与模式,是2014年左右从国外刮起来的一股“网工学Python"的风潮,最近几年在国内逐渐兴起,最近两年在国内也有少量的几本书,从不同角度来阐述了NetDevOps和它的技能体系,但和本人对于NetDevOps的理解还是有所不同,因为国内的网络环境相对比较复杂,而一些知识又和国外的平台绑定比较深,导致NetDevOps实践中有很多难题困扰着大家,笔者也在各种平台和场合解答过很多问题。此次希望能通过自己的知识分享,给大家呈现出一个不同于其他人的实战为指导、普适性强、善于抠细节、知其然知其所以然风格、深入浅出的NetDevOps知识体系,给大家一个不同的视角,一个来自于实战中的视角。
本人在国内某大型金融机构的数据中心从事网络自动化开发8年之久,从最早的脚本开发、一个简单的web自动化工具,到目前迭代出了一个由众多微服务组成的网络自动化运维系平台,覆盖了国内外众多主流厂商的设备,日常运维各种“奇葩”需求,和众多网工交流过NetDevOps底层技术,和很多网络团队管理者聊过网络自动化的前景与NetDevOps思想,甚至有幸和一些总监、创始人们聊过网络自动化运维平台的建设。
思想的不断碰撞、知识体系的不断迭代,让我有很多想说又无法名状的想法与情愫,希望在这本书中,与读者朋友们娓娓道来。
正文
2.4 基础数据类型
每个编程语言都有自己独特的基础数据类型,我们耳熟能详的比如整数、字符串、数组等等,其他所有的复杂对象都是从这些基础的数据结构衍生出来的。。
Python的基础数据类型包含以下几种:数字、字符串、列表、字典、元组、集合、布尔。
另外还有一个特殊值,空值None,类似于其他语言中的NULL,虽然它不是基础数据类型,但它很“基础”。它代表是一个空对象(它不是基础数据类型,但是是一个非常简单且需要掌握的数据类型),不指向任何实际的数据。
下面我们将展开讲解各个数据类型。
2.4.1 数字
Python中的数字分为整数(int)、浮点数(float)、复数(complex),复数在日常中基本不会涉及,我们跳过。
整数
Python中的整数与我们的书写习惯一致。正整数、负整数、零均按我们的日常使用习惯使用即可。
整数的上下限在64位系统中可以认为无上限。而且在日常网络运维也不会出现一个内存无法保存的整数。我们无需关注这些极端情况。
a = 10
b = 0
c = -20
浮点数
浮点数即我们日常所说的小数,浮点数在Python中与我们的书写也一致。
x = 51.2
y = -11.2
同时它还支持科学计数法,大家了解即可,日常开发中很少使用。
a = 1.23e-18 # 等同于1.23*10^-18
数字的计算
python的数值支持加(+),减(-),乘(*),除(/),整除(//),取余(%),代码如下:
a = 6
b = 8
print(a + b) # 输出结果14
print(a - b) # 输出结果-2
print(a * b) # 输出结果48
print(a / b) # 输出结果0.75
print(a // b) # 输出结果0
print(a % b) # 输出结果6
2.4.2 字符串
字符串是非常重要的一个数据类型,它是以引号括起来的一段文本。引号可以是单引号、双引号、三引号(三个单引号或者三个双引号),但是一定要成对出现,引号中间的文本内容是字符串承载的数据。字符串是严格区分大小写的。只写一对引号,内容为空,我们称之为空字符串。在Python中没有char(单个字符)这种数据类型,字符串中的字符可以是任意个,包括零个、一个或者多个。
示例:
a = 'NetDevOps'
b = "NetDevOps"
c = '''this is a book about NetDevOps
这是一本关于NetDevOps的书
'''
示例中我们用单引号,双引号和三引号分别创建了三个字符串。假如我们的字符串中含有引号,这个时候该如何处理呢?
方法1:定义字符串的引号和字符串文本中的引号使用不同的引号。对于新手建议使用这种。
d = "It's a book about NetDevOps."
e = 'It is a book about "NetDevOps".'
f = '''It's a book abount "NetDevOps".''' # 文本中既有单引号又有双引号,我们可以考虑用三引号。
方法2:使用转义符号反斜杠——\
,转移符号后接我们要使用的引号。
d = 'It\'s a book about NetDevOps.'
e = "It is a book about \"NetDevOps\"."
关于转义
我们想用字符串表示一个回车怎么处理呢?
Python的做法是使用\n
代表回车,其中\
就是转义符号,它后面接字母n
代表回车换行,字母n
的表达意义发生了转换,这就是转义。反斜杠被称为转义符号,\n
被称为转义字符。
下表是一些常用的转义字符(笔者根据日常运维所需进行了取舍,针对初学者列举出了使用频率比较高的)
转义字符 | 说明 |
\n | 换行,将光标位置移到下一行开头。 |
\r | 将光标位置移到本行开头 |
\t | 横向制表符 |
| 单引号 |
| 双引号 |
| 斜杠符号, |
在NetDevOps中, 我们写代码,我们定义一个设备名称的变量,就可以赋值成字符串类型的,设备的制造商可以是字符串,设备的所在房间可以是字符串,设备端口的配置可以字符串。待执行的一条命令也可以是字符串。
dev_name = 'as01'
dev_manufacture = 'HUAWEI'
dev_room = '0401'
# 我们可以适当对单词进行缩写,比如用intf代表interface。
# 但是尽量不要用int,int是一个用于将对象转换成整数的函数。
intf_config = '''interface Vlan20
ip address 192.168.137.201 255.255.255.0
'''
cmd = "show version"
字符串的常用方法
Python的字符串提供了很多便利的方法(可以简单等同于函数,初学者不必纠结名称),可以方便我们处理字符串,比如进行查找、切割、转大小写等,根据笔者的使用经验和大家简单介绍一下常用的方法。
注:函数是一个单独定义的代码块,方法是对象中的一个执行特定功能的代码块。对于初学者可以简单把函数与方法划等号,也可以简单认为方法是在对象中的函数的称谓,无需过多纠结这两个名词。本书编写会尽量严谨,但是读者在阅读过程中可以把方法和函数划等号。
以下方法直接在我们的字符串变量后写点和方法的名字即可,方法中要传入对应的参数,形式如下。
字符串变量.方法名(<参数>)
format
format方法是一个字符串格式化的方法,字符串的格式化是指,按照一定模板,向模板内传值,生成一个符合模板格式要求的字符串。Python的字符串格式化方法非常多,此处我们重点介绍format方法。
先编写一个字符串的模板,对于其中希望填充值的地方用花括号{}
括起来,然后对模板字符串调用format方法,依次传入要填充的字符串,数目一定要与花括号的数目对应。
server = 'host01'
ip_addr = '192.168.1.100'
intf_desc_tpl = 'connect to {}, ip:{}'
intf_desc = intf_desc_tpl.format(server,ip_addr)
print(intf_desc) # 结果是"connect to host01, ip:192.168.1.100"
这种方法还有另外一个书写方式,在定义字符串模板的时候,希望填充的值用花括号括起来,同时花括号内填这个填充值的参数名称,然后对模板字符串调用format方法,为花括号内的参数进行赋值。此方法可读性非常好,但是写起来会比上面的方法多写一些内容,初学者可以在看到函数部分后再回来看看此部分示例。
server = 'host01'
ip_addr = '192.168.1.100'
intf_desc_tpl = 'connect to {SERVER}, ip:{SERVER_IP}'
intf_desc = intf_desc_tpl.format(SERVER=server, SERVER_IP=ip_addr)
print(intf_desc) # 结果是"connect to host01, ip:192.168.1.100"
find
find方法用户发现字符串中是否包含子串,如果发现子串返回子串首字母出现的位置索引值(Python中的索引从零开始),如果未发现返回-1
。
我们可以通过比较find的结果与0比较,小于零代表未发现,大于等于零代表发现了子串。
intf_show = 'Eth1/1 is up'
up_index = intf_show.find('up')
print(up_index)
最终输出结果是10。
如果我们改为find('down')
则输出结果是-1。
我们在NetDevOps开发中可以用于判断回显是否包含关键字。
startswth
startswith方法用于判断是否以给定的字符串开始的,返回是真(True)或假(False)。
intf_show = 'Ethernet1/1 is up'
is_interface_line = intf_show.startswith('Ethernet')
print(is_interface_line) # 输出结果是Ture
endswith
endswith方法用于判断是否以给定的字符串结束的,返回是真(True)或假(False)。
intf_show = 'Ethernet1/1 is up'
interface_up = intf_show.endswith('up')
print(interface_up)
find 、startswith、endswith主要用于在文本中发现是否有关键字,通过关键字我们可以判断一些状态,或者确定此行中是否有我们要提取的信息等等。
split
split方法用于切割字符串,返回的结果是列表(list,后续会展开讲)。
默认是用空白符来进行切割,空白符泛指没有显示却又占位置的符号,比如空格、制表符、换行。
intf_show = 'Ethernet1/1 is up'
result = intf_show.split()
print(result) # 结果是['Ethernet1/1', 'is', 'up']
通过这种方式,我们就可以获取包含一些字段信息的列表,再通过列表的访问机制就可以提取出端口名称和端口状态了,这个我们在列表中讲解使用方法。
我们也可以用指定的字符去切割,比如我们使用is
去切割。
intf_show = 'Ethernet1/1 is up'
result = intf_show.split('is')
print(result) # 结果是['Ethernet1/1 ', ' up']
这样直接获取了端口和状态。
strip
strip方法用去去除字符串左右的指定字符串,不修改原来的字符串(因为字符串是不可修改的类型),返回一个新的字符串。
默认是去除左右的所有空白符。
我们也可以在方法内直接传入要去除的字符串,同时strip还有两个变种方法lstrip和rstrip,可以只去除左侧或者右侧的指定字符串,但这些NetDevOps脚本编写很少涉及,故不演示。
intf_show = ' Ethernet1/1 is up '
result = intf_show.strip()
print(result) # 结果是"Ethernet1/1 is up"
splitlines
splitlines方法用于将一大段文本按行切割,行的结束符号Python会帮我们自动判断,最终返回一个字符串的列表。。
intf_config = '''interface Vlan20
ip address 192.168.137.201 255.255.255.0
'''
configs = intf_config.splitlines()
# 结果是['interface Vlan20', ' ip address 192.168.137.201 255.255.255.0']
print(configs)
replace
replace方法用于将某字符串替换为我们指定的字符串。它有两个参数,第一个想要替换的字符串,第二个是要去替换之前那个字符串的字符串。第一个是old,原来字符串中拟替换的字符,一个是new,拟将之前提及的字符串替换的内容。
intf_name = 'Eth1/1'
full_intf_name = intf_name.replace('Eth', 'Ethernet')
print(full_intf_name) # 结果是"Ethernet1/1"
由于字符串是不可变的数据类型,所以原有的变量intf_name指向的字符串不会被修改,我们需要将函数返回的值赋值给一个变量,可以是新定义一个变量,也可以用原有的变量。
intf_name = 'Eth1/1'
intf_name = intf_name.replace('Eth', 'Ethernet')
print(intf_name) # 结果是"Ethernet1/1"
字符串的拼接
字符串通过加号可以实现字符串的拼接,生成一个新的字符串。
server = 'host01'
ip_addr = '192.168.1.100'
intf_desc = 'connect to ' + server + ', ip:' + ip_addr
print(intf_desc) # 结果是"connect to host01, ip:192.168.1.100"
对于初学者而言,多用于按照一定格式拼接多个字符串。如果是这种情况,更推荐字符串格式化这种方式。
以上几个字符串的相关使用,结合判断、循环,我们就可以写出一些复杂的逻辑,对网络配置进行解析,提取出我们想要的信息。这个思路,我们在掌握了判断循环后再去编写代码实现。
2.4.3 列表
列表(list)有点类似于其他编程语言中的数组,它是一组有序的数据,每个成员都有一个索引值,索引值从零开始依次递增。
这组有序的数据类型可以是Python的基础数据类型,也可以是复杂的对象。Python的列表最大的不同在于成员的数据类型可以不一。
它的创建方式比比较简单,用中括号(方括号)创建,列表中的成员用逗号隔开。
intfs = ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
dev_info = ['192.168.1.1', 'as01', 'huawei', 'ce6800', 48, ['beijing', 'dc01']]
如上,第一个我们用于定义一组端口,都是字符串的成员。这是在日常NetDevOps开发中比较常见的一种形式,成员都是相同的类型,代表一类事物。
第二个列表我们稍微改了一下,成员既有字符串,又有数字,还有列表。这种是典型用多个维度的成员组成的列表,用于描述一个事物。dev_info这个变量中我们通过成员描述了它的IP地址、设备名称、厂商、系列、端口数以及所属数据中心。
这是笔者总结的两种组织列表的场景:同一维度的成员描述一系列事物,不同维度的成员描述一个事物。
访问成员
列表是有序的,通过它对应的排序(从零开始),称之为索引更为准确,通过方括号,我们可以访问到这个成员。
intfs = ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
intf = intfs[0] # 此处千万不要用int去命名端口变量,会与int函数冲突
print(intf) # 此处输出'Eth1/1'
intf = intfs[2]
print(intf) # 此处输出'Eth1/3'
Python的列表访问成员还有一个非常有意思的,异于其他语言的特性,负索引。
我们可以输入负数,代表倒数第N个成员。负索引最后一个成员开始排序,最后一个的索引是-1。参考下图:
intfs = ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
intf = intfs[-1] # 此处千万不要用int去命名端口变量,会与int函数冲突
print(intf) # 此处输出'Eth1/4'
无论是正索引还是负索引,我们访问都不能越界,如上图,我们不能访问索引为4或者-5的成员,因为它不存在,Python都会报错。
计算列表长度
我们想获取列表长度的时候可以直接调用一个Python的内置函数len,然后传入列表。
intfs = ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
intf_sum = len(intfs)
print(intf_sum) # 此处输出4
print(intfs[intf_sum - 1]) # 此处输出'Eth1/4'
我们访问列表的最后一个成员的时候,可以使用长度减一的索引来访问,但是不建议,Python的风格(我们称之为pythonic),一般使用负索引。
追加成员
列表是一个可变的数据类型,在创建之后,我们还可以继续在列表内追加成员,使用列表的append方法即可,一次只能追加一个成员,可以一直追加。
intfs = ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
intfs.append('Eth1/4')
# 结果是['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4', 'Eth1/4']
print(intfs)
合并列表
两个列表合并有两种方式:
- 使用加法,两个列表合并成一个新的列表,原有的两个列表没有任何变化
intfs_part1 = ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
intfs_part2 = ['Eth1/5', 'Eth1/6', 'Eth1/7', 'Eth1/8']
intfs = intfs_part1 + intfs_part2
print(intfs_part1) # 结果是['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
print(intfs_part2) # 结果是['Eth1/5', 'Eth1/6', 'Eth1/7', 'Eth1/8']
print(intfs) # 结果是['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4', 'Eth1/5', 'Eth1/6', 'Eth1/7', 'Eth1/8']
- 使用extend方法,将另外一个列表B批量追加到调用方法的列表A之后,只有调用方法的列表A发生变化,有了新的成员。
intfs_part1 = ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
intfs_part2 = ['Eth1/5', 'Eth1/6', 'Eth1/7', 'Eth1/8']
intfs_part1.extend(intfs_part2)
print(intfs_part1) # 结果是['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4', 'Eth1/5', 'Eth1/6', 'Eth1/7', 'Eth1/8']
print(intfs_part2) # 结果是['Eth1/5', 'Eth1/6', 'Eth1/7', 'Eth1/8']
切片
切片如同这个词的字面意思,是指从一个已有的列表中切取一“片”,这一“片”也就是一个子列表,切片的方式很灵活,它的规则如下。
[start_index:stop_index:step]
start_index 是指起始索引值,可以不写,默认是列表头。
stop_index 是指结束索引值,可以不写,默认取到列表尾。
step 是指步长,是取成员的间隔。(可以为负数,达到反向颠倒切片效果,了解即可)
通过以上的方式,则会返回一个新的列表。
切片过程中如果指定了stop_index,则只能取到stop_index 前一个符合步长的索引值。类似于数学中的开区间,无法取值到stop_index。
intfs = ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
sub_intfs = intfs[:3]
# 只能取值到3前面的索引也就是到索引2,结果是['Eth1/1', 'Eth1/2', 'Eth1/3']
print(sub_intfs)
sub_intfs = intfs[1:3]
# start_index是可以取到的,但是stop_index是取不到的,结果是['Eth1/2', 'Eth1/3']
print(sub_intfs)
sub_intfs = intfs[::2]
# 旗帜索引可以不填写,默认从头取到尾,间隔为2,结果是['Eth1/1', 'Eth1/3']
print(sub_intfs)
2.4.4 字典
字典(dcit)是一组通过关键字进行索引的、有序的、不重复的、可变的数据集合,可以简单理解为一组键值对且键不可重复(key是关键字,value是对应的值,二者构成了一个成员)。
注:自Python3.6开始,字典变为了有序的数据集合。
字典的创建方式很简单,通过花括号{}
来创建一个字典,成员(键值对)间用逗号隔开,键与值之间用冒号隔开。形如:
key必须是可以哈希的对象,对于初学者而言,认为只有字符串和数字可以作为key,实际在使用中,我们用字符串做key的更常见。
value可以是任何python的数据类型,包含了基础的数据类型,也包含复杂的数据对象。
dev_info = {'ip': '192.168.1.1', 'name': 'as01', 'manufacture': 'huawei', 'series': 'ce6800', 'ports_sum': 48}
为了提高可读性冒号的前后各一个空格,逗号后面一个空格,适当留白。当字典的成员(键值对)比较多的时候,我们可以在某成员后适当换行,对齐字段,对可读性有很大帮助。
dev_info = {'ip': '192.168.1.1',
'name': 'as01',
'manufacture': 'huawei',
'series': 'ce6800',
'ports_sum': 48}
字典的访问
我们可以用方括号[]
,指定key访问到对应的value。
dev_ip = dev_info['ip']
print(dev_ip) # 输出结果是'192.168.1.1'
如果字典中无此key,则程序会报错,另有一种安全的访问方法,是字典的get方法,它的逻辑是:
- 如果有对应的键值对,返回其值,
- 如果无则返回一个默认值,如果我们没指定默认值,则返回特殊的空值
None
。
# 取值存在
dev_ip = dev_info.get('ip')
print(dev_ip) # 输出结果是'192.168.1.1'
# 取值不存在,返回指定默认值
ssh_port = dev_info.get('ssh_port', 22)
print(ssh_port) # 输出结果是我们给定的默认值22
# 取值不存在,未指定默认值,返回None
username = dev_info.get('username')
print(ssh_port) # 输出结果是默认值,Python中的特殊对象None
字典的成员修改与添加
字典的键值对可以修改也可以追加,直接通过指定key进行赋值:
- key value对存在,再次赋值会更新原有的值
- key value对不存在,会创建新的k,v对
dev_info['name'] = 'as02'
dev_info['ssh_port'] = 22
# 输出结果是{'ip': '192.168.1.1', 'name': 'as02',
# 'manufacture': 'huawei', 'series': 'ce6800',
# 'ports_sum': 48, 'ssh_port': 22}
print(dev_info)
在这个过程中我们也发现,新添加的成员位于字典的最后。
2.4.5 布尔
布尔(bool)只有真或假两个值,对应True和False,在进行判断的时候非常有用。
数字、字符串、列表、字典等有一些运算是可以得到一个布尔值的。比如比较数字的大小返回的就是布尔值,某成员是否在列表内,某key是否在字典里出现等等。
逻辑计算:且、或、非
布尔值可以进行逻辑运算,也称布尔运算。
包含三种运算:
- 且 ,and连接左右布尔值,左右布尔值均为真,结果才为真,否则为假。
- 或 ,or连接左右布尔值,左右布尔值有一个为真,结果为真,否则为假。
- 非,not后接布尔值,取反操作,如果布尔值为真则结果为假,如果布尔值为假,则结果为真。
flag1 = True
flag2 = False
flag = flag1 and flag2
print(flag) # 结果为False
flag = flag1 or flag2
print(flag) # 结果为True
flag = not flag1
print(flag) # 结果为False
数字比较获得布尔值
数字类的比较运算如下:
# 数字类比较有大于> ,小于< ,等于== ,大于等于>= ,小于等于<= ,不等于!=
a = 10
b = 12
print(a > b) # 输出结果是False
print(a < b) # 输出结果是True
print(a == b) # 输出结果是False
print(a >= b) # 输出结果是False
print(a <= b) # 输出结果是True
print(a != b) # 输出结果是True
对于两个数字是否相等用的是==
,因为一个等于号=
代表的是赋值,不等于用的符号是!=
。
in 与 not in获得布尔值
字符串、列表、字典可以通过in运算符进行成员的一个包含判断。字符串查找子串,列表查找成员、字典查找key。
intf_show = 'Eth1/1 is up'
up = 'up' in intf_show
print(up) # 因为字符串中出现过'up',故结果是True
intfs = ['Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4']
print('Eth1/7' in intfs) # 由于端口中无Eth1/7,故返回False
dev_info = {'ip': '192.168.1.1',
'name': 'as01',
'manufacture': 'huawei',
'series': 'ce6800',
'ports_sum': 48}
print('ssh_port' in dev_info) # 由于此字典中无ssh_port这个key,所以返回False
not in 进行一个不包含的计算。
intf_show = 'Eth1/1 is up'
down = 'down' not in intf_show
print(down) # 因为字符串'down'不在intf_show中,故结果是True
2.4.5 元组
元组(tuple)是有序的、不可变的一组数据,与列表极其相似,除了一点,元组成员不可变(不可增加、删除、修改)。
可以通过索引访问,可以切片。
其创建方式为用圆括号()
括起成员,逗号间隔,成员可以为任意数据类型。
intfs = ('Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4')
intfs = ('Eth1/1',) # 只有一个成员的元组,也一定要注意在成员后面加一个逗号
dev_info = ('192.168.1.1', 'as01', 'huawei', 'ce6800', 48, ['beijing', 'dc01'])
其访问和切片方式与列表完全相同。
它有哪些使用场景呢?(针对初学者)
- 为了对数据进行保护,防止后续编程误修改数据,可以将其定义为元组,
- 方便一次性赋值给变量
特性1不必多说,我们通过一段代码看看2特性:
dev_info = ('192.168.1.1', 'as01')
dev_ip, dev_name = dev_info
print(dev_ip, dev_name) # 输出结果192.168.1.1 as01
有些函数或者方法会返回多个数据,这个时候会自动封装成元组。通过上述方法可以非常方便的进行赋值(变量和元组成员必须数量一致),而不需要索引访问。
2.4.6 集合
集合(set)是无序的、不可重复的一组数据,与列表类似,但是它的特点是成员不重复,我们在初始化的时候传入多个值相等的成员时,集合会自动去重保留一个。
其创建方法是使用花括号{}
创建,成员中间用逗号隔开。对于初学者而言,成员的数据类型建议初学阶段锁定在数字和字符串。不建议使用其他数据类型,比如集合的成员不能是字典,会报TypeError: unhashable type: 'dict'
的错误,集合是通过哈希了成员之后,根据哈希值去重,字典无法哈希,进而报错,大家了解即可。
intfs = {'Eth1/1', 'Eth1/2', 'Eth1/3', 'Eth1/4','Eth1/3', 'Eth1/4'}
print(intfs) # 结果是{'Eth1/2', 'Eth1/4', 'Eth1/1', 'Eth1/3'},顺序每次都可能变化,因为集合是无序的一组数据。
allow_vlan = {200,200,201,203,204}
print(allow_vlan) # 结果是{200, 201, 203, 204}
# 会报TypeError: unhashable type: 'dict'的错误
# set是哈希了成员之后,根据哈希值去重,字典无法哈希,进而报错,大家了解即可
err_set = {{'intf_name':'Eth1/1','desc':'test'}}
对于初学者,可以使用集合进行去重。
2.4.6 类型转换
Python对基础数据提供了类型转换,比如用int函数将数据转为整数,float将对象转为浮点数,str将对象转为字符串,list将对象转为列表,tuple将对象转为元组,set将对象转为集合。其中列表、元组、集合可以通过对应函数相互转换,但是可能会丢失部分信息,比如排序,以及重复成员只会保留一个。
以上均不改变原来的数据的值,而是新返回一个数据,大家按需将返回值赋值给一个新的变量或者是赋值给原有的变量。
# type函数可输出变量的类型
a = '1'
a = int(a)
print(a, type(a)) # 输出1 <class 'int'>
a = '1'
a = float(a)
print(a, type(a)) # 输出1.0 <class 'float'>
a = 100
a = str(a)
print(a, type(a)) # 输出100 <class 'str'>
a = (1, 2, 3)
a = list(a)
print(a, type(a)) # 输出 [1, 2, 3] <class 'list'>
a = [1, 2, 3]
a = tuple(a)
print(a, type(a)) # 输出 (1, 2, 3) <class 'tuple'>
a = [1, 2, 3, 3, 3]
a = set(a)
print(a, type(a)) # 输出{1, 2, 3} <class 'set'>,丢失了成员,顺序也无法保证。