python3:递归函数中使用global nonlocal
首先是一个最简单的计算阶乘的递归函数:
def recuision(n):
if(n == 1):
return 1
else:
return n * recuision(n-1)
print(recuision(4))
运行结果:
但现在有可能,你需要对每次调用递归时的n,进行一些记录:
value = 0
li = []
def recuision(n):
global value,li
#记录数的总和
value += n
#列表记录有哪些数
li.append(n)
#或者改成这句li = [n],记录最后一次递归过程的数
if(n == 1):
return 1
else:
return n * recuision(n-1)
print(recuision(4))
print(value,li)
运行结果:
比如这里的value:用来存储每次调用时的n的总和
li:列表用来存储每次调用时的n
这是设计递归程序,常用的一些手法,就是递归的过程中,对在外部定义的一些变量进行改变,用来记录某次特定的递归调用的状态。所以value和li就是我所说的外部变量。但这里必须有global这行代码,如果没有,将会有如下报错信息(因为如果没有使用global,且在函数内部对与外部变量同名的本地变量value执行+=,所以程序以为value是本地变量,但这个本地变量value还没有被赋值过,所以会报错):
当地变量value还么有定义就开始引用。
运行结果为
运行结果为
因为在函数进行赋值语句了,程序就认为a为本地变量,但这句话又在print的后面,所以报错。
对于此错误UnboundLocalError的进一步理解:
不使用global,且当变量为基本类型时:
value = 0
li = []
def recuision(n):
print('value=',value)
if(n == 1):
return 1
else:
return n * recuision(n-1)
print(recuision(4))
print(value,li)
运行结果:
当对外部的基本类型变量,只是引用,而没有改变其值时,不会报错。
不使用global,且当变量为非基本类型时:
value = 0
li = [1,2,3]
def recuision(n):
print('li=',li)
if(n == 1):
return 1
else:
return n * recuision(n-1)
print(recuision(4))
print(value,li)
运行结果:
当对外部的非基本类型变量,只是引用,而没有改变其值时,不会报错。
不使用global,且当变量为非基本类型时,且改变其引用:
value = 0
li = [4,5,6,7]
def recuision(n):
li = [1]
print('li=',li)
if(n == 1):
return 1
else:
return n * recuision(n-1)
print(recuision(4))
print(value,li)
运行结果:
外部的非基本类型变量。其实,这里在递归中的li和外部的li,已经不是同一个变量了,在递归调用时,为递归函数本地创建了一个li变量。
不使用global,且当变量为非基本类型时(list),且改变其引用的引用(这里是list[index]):
value = 0
li = [4,5,6,7]
def recuision(n):
li[n-1] = [n]
print('li=',li)
if(n == 1):
return 1
else:
return n * recuision(n-1)
print(recuision(4))
print(value,li)
运行结果:
外部的非基本类型变量。可以发现,这里改变的是递归函数外部的li变量,而且这里并没有创建出一个本地的li变量。
导入写好的递归模块:
递归模块(假设此模块名为test1):
value = 0
li = []
def recuision(n):
global value,li
value += n
li.append(n)
if(n == 1):
return 1
else:
return n * recuision(n-1)
主模块:
import test1
print(test1.value)
print(test1.li)
print(test1.recuision(4))
print(test1.value)
print(test1.li)
运行结果:。一般写好的递归模块,就可以这么使用。
在递归函数中使用nonlocal:
在以上的递归函数,外部变量都是global的,要想改变其引用,必须使用global。但现在如果有一个需求,需要把这些递归函数的外部变量不放在global域中(其实放在global域就够用了,我就是想研究一下==),然后能够,让给函数传一个参数,返回递归运行后那些外部变量,这时就得用到nonlocal了。这样函数看起来就很好看,不需要在外部设置那么多变量了。
当变量是基本类型时,使用这种外部变量(不是函数里传参数,而是不传这个参数,直接使用),分为两种情况:
1.如果只是使用其引用,那么能够得到其正确的值
2.如果是对其进行赋值行为,那么此时程序认为你是创建了一个与外部变量同名的内部变量,所以你实际是在操作一个内部变量,而这个内部变量如果引用前没有赋初值,程序就会报错
当变量是非基本类型(即可变对象)时,使用这种外部变量(不是函数里传参数,而是不传这个参数,直接使用),就只有一种情况:
1.你操作的一直是这个可变对象
nonlocal的基本用法:
def outer():
num = 10
def inner():
nonlocal num # nonlocal关键字声明
num = 100
print(num)
inner()
print(num)
outer()
运行结果:。两个num都是outer函数域里面的num,是同一个变量。
def outer():
num = 10
def inner():
num = 100
print(num)
inner()
print(num)
outer()
运行结果:。两个num不是同一个,outer有一个num变量,在inner的外部。inner也有个本地的num变量。
如何在递归函数中使用nonlocal:
def te(count):
value = 0
flag = [False]*count
result_flag = [False]*count
def recursion(i):
nonlocal value,flag,result_flag
value +=1
flag[i] = True
print(flag)
if(flag == [True,True,False,False,False]):
result_flag = flag
for j in range(i+1,count):
recursion(j)
flag[i] = False
for i in range(count):
recursion(i)
return (value,result_flag)
print(te(5))
运行结果:。可以看出来,递归一共调用31次。
其实也就是。比如C51,就是,从5个里面选一个出来的情况,只有5种:1,2,3,4,5。
其递归过程形如(没有画完,反正就是这个意思啦):
此递归程序,不需要设置递归的终点,因为二叉树的分支越来越少,到最后就没有分支了,递归也就到终点了。把上图的12345当成Ture,False的状态就行,有这个数,就代表这个数字上的位置为True,否则为False。
因为value,flag,result_flag,都是相对于递归函数的外部变量,但是这三个变量又都在te函数里,且刚好在recursion函数的外部,所以这里需要使用nonlocal。
以上这种递归程序的写法:在函数中定义真正的递归函数,然后在该函数中调用该递归函数。就可以达到,不需要定义全局变量,而且只给函数传一个参数,的效果。
引用变量赋值错误:
观察运行结果,返回的元祖,其中的result_flag应该为的,但是并没有,result_flag的每个元素都是False。其实,这里是引用型变量赋值造成的:
就好比flag是一条狗绳,牵在第一条狗身上,result_flag是另一条狗绳,牵在第二条狗身上。考虑flag的变化,虽然此变量在递归过程中一直在变化,但是递归结束后,flag必定都是False,因为在递归中,开始时有,结束时有,改变状态后,还会恢复状态。
执行了result_flag = flag,那么就把result_flag这条狗绳,也牵在了第一条狗身上,那么第二条狗没人管了,也就跑了(被垃圾回收了),但实际上,程序想要的效果是,同时存在两条狗,且返回第二条狗,第一条狗只是作为一个变量在递归中使用。
所以需要进行如下修改:
if(flag == [True,True,False,False,False]):
for m in range(len(flag)):
result_flag[m] = flag[m]
运行结果:
此时,result_flag和flag,这两个引用实际指向的是,两个不同的变量。也就是,同时存在了两条狗。
或者重新构造一个新的list:
if(flag == [True,True,False,False,False]):
result_flag = [True,True,False,False,False]