摘要
首先说明,以下几类读者请自行对号入座:
- 对CMDB很了解但对于Python还没有上手的读者,强烈建议阅读此篇;
- 了解过Python基本的数据结构,但又没有经常在实践中运用的读者,建议阅读此篇;
- 已经可以熟练写出Python脚本,但对CMDB不是很了解的读者,建议阅读此篇;
- 即了解Python,又了解CMDB的读者,可以出门左转,看下一篇。
上一节我们通过对自动化运维的基石–CMDBv1.0的演示,为大家讲了Python的基本数据类型和相关的操作,那么这一节我们就深入cmdb-v1.0.py的源码,并了解一下Python的语句,函数以及面向对象相关的知识。
一说到阅读源码很多读者就要慌了,觉得Python都没入门就阅读源码了?首先Python的一大好处就是,代码的逻辑像阅读英文一样简洁,并且我们的cmdb-v1.0.py的源码只有一百一十行左右,就实现了对资产数据增删改查的基本功能,话不多说,马上开始:
Python脚本的启动
root> # python ./cmdb-v1.0.py [额外参数...]
在命令行中直接通过python
加文件名就可以执行该脚本,那么当执行该脚本时,脚本内部做了什么操作呢
if __name__ == "__main__":
operations = ["get", "update", "delete"]
args = sys.argv
if len(args) < 3:
print("please input operation and args")
else:
if args[1] == "init":
init(args[2])
elif args[1] == "add":
add(*args[2:])
elif args[1] == "get":
get(args[2])
elif args[1] == "update":
update(args[2], args[3])
elif args[1] == "delete":
delete(*args[2:])
else:
print("operation must be one of get,update,delete")
上述代码就是我们整个脚本的启动入口,大家最先看到的就是一行判断语句,那我们就先从判断语句开始讲起
条件判断
不管哪种编程语言,条件判断都是其最基本的逻辑,是让一行一行的代码能够被编排起来的最基本手段,条件判断可以实现在不同的情况下执行不同的代码块,如图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjWVURka-1638719888894)(1EADCCAAD9D44819B96E1B150746F3A2)]
下面为Python判断语句的伪代码形式,当判断条件为真时执行语句1,为假时执行语句2,执行语句可以为多行,通过缩进来控制
if 判断条件:
执行语句1
else:
执行语句2
我们的源码中的第一行 if __name__ == "__main__"
,这就是一个字符串判断的语句,__name__
是一个Python的内置变量,它表示当前被执行脚本的名称,所以此处判断语句的含义为是否当前被执行脚本的名称等于"__main__"
,这里有两个地方需要大家注意一下:
- 当使用
python 文件名.py
的方式执行脚本时,该脚本的__name__
值即为__main__
- 条件判断语句中
等于
通过==
来表示,而非=
在掌握了判断语句的原理后,再加上我们上节内容所讲,我们就可以理解源码中启动入口的基本逻辑
if __name__ == "__main__": # 当前被执行脚本的名称是否等于"__main__",如果等于执行以下语句
args = sys.argv # 获取命令行输入的参数,此处sys.argv为python的内置方法
if len(args) < 3: # 如果参数数量小于3个,则执行以下语句
print("please input operation and args") # 打印提示内容
else: # 如果参数数量不小于3个,则执行以下语句
if args[1] == "init": # 是否参数的第二个元素等于"init"(数组下标从0开始)
init(args[2]) # 如果等于"init"则执行该函数
elif args[1] == "add": # 如果不等于"init",则判断是否等于"add"
add(*args[2:]) # 如果等于"add"则执行该函数
elif args[1] == "get": # 如果也不等于"add",则判断是否等于"get"
get(args[2]) # 如果等于"get"则执行该函数
elif args[1] == "update": # 如果也不等于"get",则判断是否等于"update"
update(args[2], args[3]) # 如果等于"update"则执行该函数
elif args[1] == "delete": # 如果也不等于"update",则判断是否等于"delete"
delete(*args[2:]) # 如果等于"delete"则执行该函数
else: # 如果都不等于则执行以下打印语句,输出提示
print("operation must be one of get,update,delete")
大家可以发现只通过上述的条件判断语句就可以根据我们执行脚本时的命令行参数,去分别执行不同的增删改查的逻辑,读者可能对这里的*arg
有一些疑问,我们会在番外篇中提到。
循环语句
目前我们已经掌握了让脚本启动,并且根据不同的条件判断去依次执行语句的能力,不过这时程序还只是在顺序执行,如果我们想查询多个资产信息,那么就必须多次去执行查询的语句,这时候就需要使用循环语句,循环语句可以让我们执行某一个代码块多次,如图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gjFoJyka-1638719888897)(48477676D7B44D50ACCADE9820AA485E)]
Python中的循环语句的伪代码形式如下所示
for 判断条件: # 只要判断条件为真就会一直执行语句1
执行语句1
由于循环语句相对比较好理解,我们就先简单介绍一下,后面的源码中遇到时,再深入讲解一些细节
函数
通过上面的学习我们已经能够比较好的编排自己的代码去顺序执行或者循环执行,但对于一些可以重复使用的语句,我们可以把其组织起来,将它们定义为一个函数,这样我们后续就可以直接去使用这个函数,而不必每次都编写大量相同的语句。
Python中的函数伪代码形式如下所示
def 函数名称(参数...):
代码块
比如我们源码中定义的查询资产信息的函数的伪代码如下
def get(path): # 函数名称为 get, 接受一个参数 path
打开资产信息的文件
根据参数path去查询资产信息中对应的信息
打印相关信息
当我们定义好这样一个函数之后,我们后续就可以十分方便的去调用它,调用的方法就是
info = get("/beijing/switch/10.0.0.1")
其实函数的本意就是我们将一些可被复用的代码进行提取,将其中可变的变量作为参数传入,而将其相同的逻辑保留,这样我们每次只需要传入不同的参数就可以执行该逻辑,不用在需要使用该逻辑的地方再次编写冗余的代码
比如在脚本的启动入口地方,如下
def init(path):
...
def get(path):
...
def delete(path):
...
if args[1] == "init":
init(args[2])
elif args[1] == "get":
get(args[2])
else:
print("operation must be one of get,update,delete")
我们只需要在执行脚本时,通过判断命令行的指令,就可以去执行不同的函数,十分方便,但这里有两个地方需要大家注意一下
- 定义的函数只是语句的抽象逻辑,如果不调用它,那么它就永远不会执行,比如如果我们定义了一个删除的函数,但始终都没有任何地方去调用它,那么它就永远不会被执行
print()
其实也是一个函数,只不过它是Python的内置函数,它的功能相当于接收一个字符串,并将其输出到屏幕上,所以我们其实在尝试编写第一个python程序print("hello world")
时,就已经无形中使用到了函数
Tips
大家可以设想一下,如果每次我们想将内容输出到屏幕,都需要自己去编写print的内部逻辑细节,那简直就是一场灾难;所以在什么时候将某个代码块抽象为函数,将多少逻辑的代码块抽象为一个函数,这其实是编程的艺术,取决于每个人对于实际场景的把握。但也有一些变成规范可依。
我们通常只将一个功能抽象为一个函数,也就是说每个函数只实现一个单一的功能。
面向对象
很多对于编程稍微有了解的读者都知道,面向对象是很多编程语言都有的一个特性,所谓面向对象其实是一种编程的思路,与之不同的思路还有面向过程;
比如同样要实现相同的功能,可以使用不同的思路,思路没有孰优孰劣之分,只要在当前场景适用即可
虽然Python实现一些简单的功能,只需要面向过程即可,比如将目标场景,拆分为不同的步骤,将每个步骤定义为函数,然后通过编排函数去实现最终的目标,但Python本身从设计之处就是一门面向对象的语言,并且Python中一切皆对象。
那么对象究竟是什么:世界上的任何事物都可以把它看成一个对象,其具有自己的属性和行为,不同的对象之间通过方法来交互。
比如Python中的某个字符串,它就是一个对象,它具有自己属性和方法,如下
root> # a = "string"
root> # dir(a)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit',
'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
我们定义了一个字符串变量a,通过dir
方法来查看其所具有的所有属性和方法。
面向对象只是解决问题的思路,我们并不是将问题拆解为不同的步骤,而是将问题分解为不同的对象,在我们的CMDBv1.0中,我们要解决的问题就是实现资产信息的增删改查,那么我们分析该问题发现需要两个对象来解决:
- 资产对象
- 属性:可以执行的操作,当前的版本,创建时间,上次修改的时间等
- 方法:增、删、改、查
- 信息存储对象
- 属性:存储的位置,数据的大小
- 方法:存,取
所以经过我们面向对象的拆解,最终将我们的问题从面向过程,即根据命令行参数的传入去执行不同的函数,而修改为了我们与资产对象进行交互,而资产对象与存储对象进行交互。
知识总结
-
开始初步阅读CMDBv1.0版本的源码
-
讲解了Python基本的条件语句和循环语句
-
讲解了Python的函数,以及面向对象的分析
CMDB系列第二节我们就暂且讲到这里,对于判断语句和循环语句还有很多细节没有涉及到,但我们已经掌握了其基本的原理,并且我们了解了面向对象的思路。
后面的章节我们会继续阅读CMDBv1.0的源码,了解更为细节的内容,并且用面向对象的思路将CMDBv1.0改造为CMDBv1.5,敬请期待。
篇后语
很多读者在阅读的过程中可能发现,我们很多的知识都是浅尝辄止,看似都是一些皮毛,并没有什么真材实料,包括阅读源码也是,只看了个大概;其实不然,当我们新上手一门新的技术时,我们并不能揪住一个知识点不放,比如字符串是一个对象,通过
dir
可以发现它有二十多个属性和二十多中方法,但我们难道要在一开始就都掌握并把他们背会吗?答案当然是否定的,这些细节我们初期都不需要去深究,我们的场景是构建一个简易版CMDB,那么我们只需要一步一步将这过程中阻碍我们前进的知识掌握即可,具体的细节可以在后续的深入过程中去慢慢了解。这也是为什么很多学生不愿意听老师讲课的原因,因为他并不知道我这节课学的知识点有什么用,只是机械的接受老师的灌输,所以好的学习方法一定是自顶向下的,希望读者朋友们能体会到其真正的内涵。
欢迎大家添加我的个人公众号【Python玩转自动化运维】加入读者交流群,获取更多干货内容