摘要
首先说明,以下几类读者请自行对号入座:
- 对CMDB很了解但对于Python还没有上手的读者,强烈建议阅读前面几篇;
- 对Python了解较少只能写出简单脚本的读者,强烈建议阅读此篇;
- 已经可以熟练写出Python脚本,但对CMDB不是很了解的读者,建议阅读此篇;
- 即了解Python,又了解CMDB的读者,可以出门左转,看下一篇。
上一节可能对刚开始编程的读者朋友们有一点挑战,其中涉及到通过循环来对复杂数据结构的修改,但只要大家认真理解了上一节的内容,这一节的内容就会感觉简单很多,这一节我们接着阅读剩余的对CMDB进行删改查的代码部分。
【一】代码优化
在讲解删改查之前,我们需要再次回顾一下前两节的代码,看看有没有什么可以优化的地方。
import json
def init(region):
with open("data.json", "r+") as f:
data = json.load(f)
if region in data:
print("region %s already exists" % region)
return
data[region] = {"idc": region, "switch": {}, "router": {}}
with open("data.json", "w+") as f:
json.dump(data, f, indent=2)
print(json.dumps(data, indent=2))
def add(path, attrs=None):
if attrs is None:
print("add operation must have attrs arg")
return
try:
attrs = json.loads(attrs)
except Exception:
print("input attribute is an invalid json string")
return
with open("data.json", "r+") as f:
data = json.load(f)
path_seg = path.split("/")[1:]
target_path = data
for idx, seg in enumerate(path_seg):
if idx == len(path_seg)-1:
if seg in target_path:
print("%s already exists in %s, please use update operation" %
(seg, path))
return
target_path[seg] = attrs
break
target_path = target_path[seg]
with open("data.json", "w+") as f:
data = json.dump(data, f, indent=2)
print(json.dumps(data, indent=2))
仔细的读者可能之前就已经发现,我们的初始化地域功能和新增资产信息功能都有对数据源的存取操作,而且看起来好像代码完全一样,那么在代码的重构原则中有涉及到,如果一段代码在两处及以上地方重复编写,那么就需要将其重构为单独的方法。这个的意思就是说我们对数据源的存取已经有两处都使用了,而且可预见的是在之后的删改查中也会使用,那么我们就需要将其单独抽象为一个函数,这样就可以被不同的地方重复调用,重构后代码如下:
import json
def read_file():
with open("data.json", "r+") as f:
data = json.load(f)
return data
def write_file(data):
with open("data.json", "w+") as f:
json.dump(data, f, indent=2)
def init(region):
data = read_file()
if region in data:
print("region %s already exists" % region)
return
data[region] = {"idc": region, "switch": {}, "router": {}}
write_file(data)
print(json.dumps(data, indent=2))
def add(path, attrs=None):
if attrs is None:
print("add operation must have attrs arg")
return
try:
attrs = json.loads(attrs)
except Exception:
print("input attribute is an invalid json string")
return
data = read_file()
path_seg = path.split("/")[1:]
target_path = data
for idx, seg in enumerate(path_seg):
if idx == len(path_seg)-1:
if seg in target_path:
print("%s already exists in %s, please use update operation" %
(seg, path))
return
target_path[seg] = attrs
break
target_path = target_path[seg]
write_file(data)
print(json.dumps(data, indent=2))
大家可以看到,已经完成的代码会随着合理的重构优化而减少,因为我们会避免掉冗余的代码块,所以编程绝不是看谁写的行数多谁就会更牛X。
【二】更新资产信息
同样的更新资产信息的功能我们也需要五步法进行思考和实践
- 先要思考一下实现这个功能有哪些地方需要注意:
- 这里我先依次给大家解答一下:
- 如何定位到要更新的路径这里我们在上一节介绍过,还不太理解的读者可以看上一节内容
- 我们要更新的信息的类型是一个需要注意的地方,因为我们的信息可以是字典格式,也可以是字符串或数组
- 现在要做的就是将思路更进一步细化到可实现的伪代码:
-
我们需要定义一个update() 函数来实现这个功能,并且这个函数需要接收两个参数,分别是要更新的信息和信息要更新到的指定路径,那么我们的函数签名应该是update(path, attrs) -
我们传入的attrs 必须是一个json格式的字符串,传入的path 必须是一个通过/ 分隔的字符串 -
通过path 去按层级定位数据源中的指定位置,通过字典的赋值将attrs 更新到数据源指定位置上 -
通过json.load 和json.dump 做数据持久化
- 接下来就是需要写出一份能实现上述功能的伪代码,如下:
def update(path, attrs):
if attrs is valid
attrs = parse_attrs()
data = read_file()
seg = path.split()
target_path = position_data()
data[target_path] = attrs
write_file(data)
print(data)
大家可以发现更新和添加的逻辑十分相似,事实上在实际的其他增删改查场景中,更新和添加也都是如此。
- 最终更新资产信息的代码如下:
def update(path, attrs=None):
if attrs is None:
print("add operation must have attrs arg")
return
try:
attr_json = json.loads(attrs)
except Exception:
print("attributes is not valid json string")
return
data = read_file()
target_path = data
path_seg = path.split("/")
for idx, seg in enumerate(path_seg[1:]):
if idx == len(path_seg)-2:
if seg not in target_path:
print("update path is not exists in data, please use add function")
return
if type(attrs) != type(target_path[seg]):
print("update attributes and target_path attributes are different type.")
return
if isinstance(attr_json, dict):
target_path[seg].update(attr_json)
elif isinstance(attr_json, list):
target_path[seg].extend(attr_json)
else:
target_path[seg] = attr_json
else:
target_path = target_path[seg]
write_file(data)
print(json.dumps(data, indent=2))
更新有两个关键点需要大家注意一:
5.1 在更新操作时,我们是对数据源中已存在的路径进行更新,这时候就涉及到数据的安全性,如果attrs 为None 可能会造成将原有信息清除。
判断attrs 的合法性
if attrs is None:
print("add operation must have attrs arg")
return
我们首先要保证的就是attrs 这个参数不能为None ,None 是Python中的一个表示空的变量类型,所以如果我们没有从命令行获取到attrs 时,那么我们的程序应该给出提示,这里我是打印了一行提醒,要求添加资产的操作必须有attrs 参数,然后直接return 退出函数
5.2 第二点就是对于更新信息的类型,在添加功能中由于是在原先不存在的路径上新增信息,所以我们无需考虑attrs 的类型,直接利用字典的特性进行赋值即可;但更新时,由于路径上已经存在数据,所以我们就需要对其类型做较为详细的判断。
更新属性
if seg not in target_path:
print("update path is not exists in data, please use add function")
return
if type(attrs) != type(target_path[seg]):
print("update attributes and target_path attributes are different type.")
return
if isinstance(attr_json, dict):
target_path[seg].update(attr_json)
elif isinstance(attr_json, list):
target_path[seg].extend(attr_json)
else:
target_path[seg] = attr_json
- 需要判断要更新的路径是否在数据源中存在,如果不存在的话就需要使用添加的功能进行添加
- 需要对数据源中指定路径的类型和
attrs 的类型进行比较,如果类型不同也不可以进行更新 - 数据源中指定路径的类型是字典的话不可以直接赋值,这样会将原先的属性信息抹除,这里需要用到字典的一个特性
dict.update() ,这个功能接收一个参数,可以将两个字典合并,并且用参数字典中的信息更新原始字典中的信息。 - 如果源路径上的信息类型是数组,那么我们就需要将要更新的
attrs 添加到原来的信息上,这里用到了数组的一个特性list.extend() ,这个功能接收一个参数,可以将参数数组合并到原始数组后面。
**Tips: extend 和 append **
关于数组的这两个方法是平时经常使用到的,通过例子大家就可以很好的理解用法:
> my_list = [1, 2, 3]
> new_list = [4, 5]
> my_list.append(new_list)
> my_list
> my_list.extend(new_list)
> my_list
可以发现,append 是将某个元素整体添加到了原始数组的末尾,而extend 是将新的数组整合到原始数组末尾,并且通过查看这两个方法参数也可以看出区别
def append(self, __object: _T) -> None: ...
def extend(self, __iterable: Iterable[_T]) -> None: ...
- 如果源路径上的信息类型不是字典也不是数组就可以直接赋值
到目前位置更新属性的功能也已经讲解完了,更新和添加大体上的逻辑类型,但更新中用到了大量了逻辑判断,关于判断语句还有一个需要和读者们讲解的地方,比如以上面更新方法中的多个逻辑判断为例,很多刚接触编程的读者可能会这样写:
if seg in target_path:
if type(attrs) == type(target_path[seg]):
if isinstance(attr_json, dict):
target_path[seg].update(attr_json)
elif isinstance(attr_json, list):
target_path[seg].extend(attr_json)
else:
target_path[seg] = attr_json
else:
print("update attributes and target_path attributes are different type.")
else:
print("update path is not exists in data, please use add function")
通过这样多层次的if...else... 嵌套,虽然也可以实现相同的功能,但对于代码的可读性上会是很大的灾难,并且多层嵌套对于后期逻辑的修改也是十分困难的,所以我们在编程的同时一定要尽力避免这种多层的嵌套,一般常用的标准是对于循环语句和判断语句不要存在三层及以上的嵌套。那么当我们遇到上面的这种情况时,我们可以参照我给出的代码示例,先去判断非法逻辑,如果命中非法逻辑则直接抛出异常或者退出函数,这样一个简单的改动对代码的可读性和可维护性都会大大提高。
这一节我们又着重复习了一次五步法,并且对于更新功能的逻辑做了详细的解读,本来想把删除和查询也一起在这一节讲解,但又担心知识点太多,大家一时不太容易接受,之后我就不会再带着大家去一步一步的练习五步法,但这是一个熟能生巧的过程,希望读者朋友们能在自己实践的过程中潜移默化的使用它,我们下一节见。
篇后语
不知道大家在这几篇的学习中有没有发现,不管是在编程还是阅读源码前的逻辑梳理都十分的重要,而且代码中的很多部分都是对一些边界case的处理,所以对于伪代码的抽象也可以帮助我们更好的去理解复杂的业务逻辑。但这些边界case对于代码的健壮性又起到了关键作用,所以读者朋友们在编程的同时,也应该去培养对于边界case的敏感度,从不同维度去预判代码或者业务逻辑可能出现的逻辑,并提前规避它。
除此之外,虽然很多读者朋友是刚接触编程,但我们仍然从编程思维的养成和源码的阅读上,向大家普及一些更深入的东西,比如重构的原则,和多层嵌套的优雅处理等,所以我的本意是能够将这些知识在刚开始学习的时候就耳濡目染的让大家去了解,而不是说新手就应该死记硬背一些基础的方法和规范,这对于学习来说反而会适得其反,所以希望大家能在阅读文章的同时仔细去感受体会。
欢迎大家添加我的个人公众号【Python玩转自动化运维】加入读者交流群,获取更多干货内容
|