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
View Code

测试数据,拼接在上面的代码里即可

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递归调用的结果

 

结果:这种代码只能按照第一个元素这条线深入递归下去,无论最终找到或者找不到目标值,都会结束递归。

这种没脑子的增加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   --测试通过

 

至此,从最初级错误程序,一步一步走到正确程序。

我的同事和我讨论了一天,最终弄清楚了正确程序的原理,也一步一步分析清楚错误程序错在哪里,应该如何改进。

posted @ 2017-09-20 20:13  kuzaman  阅读(6304)  评论(2编辑  收藏  举报