Python递归中 return 代码陷阱
最近写接口测试程序中,需要查询多层嵌套字典中某个Key的值,查到则返回,查不到返回None,查询的Key可以是任意层次的Key,如 Value本身也是多层字典,或者Key已经是叶子结点。
思想:利用Python的递归思想,逐层深入遍历,最后返回结果值
最终的成品代码参考了一下博客内容:
1 #获取字典中的objkey对应的值,适用于字典嵌套 2 #targetDict:要查找的字典;serchKey:要查找的目标key 3 #ret:递归过程中,向外部(上层)传送 return值。被查找目标在第几层,则返回几次ret 4 #default:查不到符合的serchKey,就返回默认值None 5 def dict_getValue(targetDict,serchKey,default=None): 6 for k,v in targetDict.items(): 7 if k == serchKey: 8 return v 9 elif isinstance(v,dict): 10 ret = dict_getValue(v,serchKey,default) 11 if ret is not default: #ret与default=None不等,表示找到serchKey,则ret会作为返回值向上层返回。 12 return ret 13 return default
测试数据,拼接在上面的代码里即可
1 if __name__ == '__main__': 2 targetDict ={"H": {"Ver": ["aaaa","bbbb"],"ACID": {'kkk':"aaaaa"},"CInf": [100,2,1000]}, 3 "B": { "Login": {"Type": "LP","Name": "41SS676","Pwd": {'aaa':"123456"},"ForToken": 1}}} 4 print (recursionSearch(targetDict,'Name'))
在成品之前,尝试过几种写法,都无法达到最终要求,进行了一些分析,现记录下来:
1、查找的Key只能是叶子结点,非叶子结点的无法实现查找,代码如下:
1 def recursionSearch(targetDict,serchKey): #递归查找 2 for k,v in targetDict.items(): 3 if isinstance(v,dict) : #值是字典元素,则递归处理 4 recursionSearch(v,serchKey) 5 elif k == serchKey: 6 pp=targetDict[k] 7 print (pp) 8 return pp
结果:
print (recursionSearch(targetDict,'kkk')) 看到打印出来的叶子结点的值正是我想要查找的Key='kkk'的值‘aaaaaa’,
print (recursionSearch(targetDict,'Name')) 换第二个分支里面的叶子结点,也能看到函数内打印结果41SS676是我想要的,应该是对的吧?
Python输出:
aaaaaa
None <---函数输出结果
41SS676
None <---函数输出结果
[Finished in 0.2s]
但为毛函数结果是None呢???
分析:
(1).代码确实能查找叶子结点,但。。。函数返回还是有问题。
(2).这代码遇到值为字典型的就会继续深入,如果目标Key的值恰好是字典数据,程序只会继续深入而不会就此停止。
2、更换if条件,不会直接到叶子结点级别才开始查找
1 def recursionSearch(targetDict,serchKey): #递归查找 2 for k,v in targetDict.items(): 3 if k == serchKey: 4 return v 5 elif isinstance(v,dict) : #值是字典元素,则递归处理 6 recursionSearch(v,serchKey)
分析:
(2).这个程序最后一行只进行了递归调用,但是没有返回递归的值,导致一旦出现递归,则必然返回断档,结果必然是None。无return的函数返回值就是None,Python规定。
参考《Python学习手册第4版》531页 “没有renturn语句的函数”
3、那就把递归调用的返回值也return一下
1 def recursionSearch(targetDict,serchKey): #递归查找 2 for k,v in targetDict.items(): 3 if k == serchKey: 4 return v 5 elif isinstance(v,dict) : #值是字典元素,则递归处理 6 ret = recursionSearch(v,serchKey) 7 return ret
结果:这种代码只能按照第一个元素这条线深入递归下去,无论最终找到或者找不到目标值,都会结束递归。
这种没脑子的增加return直接导致的是:
(1).查找的Key在第一层第一个键值对的值中,且递归调用时,Key也在目标字典的第一个位置,能够返回正确值;
如:Key='H',Key=‘ACID’,Key=‘kkk’都能返回正确值,如果Key=‘B’,Key=‘CInf’只会返回None
(2).换句话说:for循环里只会使用第一对(k,v)
分析:
(1).如果在代码最后加一个else:return None呢?事实证明这样仍然会中断for循环,没有任何改进的作用。
(2).必须增加一个处理方法,让程序能够在for循环中循环下去,不能只局限在第一对(k,v)中。
主要就是用莫条件限制return ret是否执行,如果此return不执行,则for能继续循环下去
如果ret是None就继续循环,如果ret不是None就证明找到目标,应该return ret,精简之后语句:if ret is not None: return ret
到此就算结束了,已经全部修改完成,虽然和参考文章上的代码有些在default的区别,但功能已经完善了。经过如下测试:
正向测试:
print (recursionSearch(targetDict,'H')) #{'Ver': ['aaaa', 'bbbb'], 'ACID': {'kkk': 'aaaaa'}, 'CInf': [100, 2, 1000]}
print (recursionSearch(targetDict,'ACID')) #{'kkk': 'aaaaa'}
print (recursionSearch(targetDict,'kkk‘’)) #aaaaa
print (recursionSearch(targetDict,'Ver')) #['aaaa', 'bbbb']
print (recursionSearch(targetDict,'B')) #{'Login': {'Type': 'LP', 'Name': '41SS676', 'Pwd': {'aaa': '123456'}, 'ForToken': 1}}
print (recursionSearch(targetDict,'Login')) #{'Type': 'LP', 'Name': '41SS676', 'Pwd': {'aaa': '123456'}, 'ForToken': 1}
print (recursionSearch(targetDict,'Pwd')) #{'aaa': '123456'}
print (recursionSearch(targetDict,'aaa')) #123456
以上均能返回正确目标键的值 --测试通过
逆向测试:
print (recursionSearch(targetDict,'aaaaaa')) #None没有键属性是‘aaaaaa’的,只有一个键的值是‘aaaaaa’,测试函数是否是按键名查找
print (recursionSearch(targetDict,'B111')) #None
以上均能争取返回None --测试通过
至此,从最初级错误程序,一步一步走到正确程序。
我的同事和我讨论了一天,最终弄清楚了正确程序的原理,也一步一步分析清楚错误程序错在哪里,应该如何改进。