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]

 

 

 

posted @ 2018-06-04 13:48  allMayMight  阅读(116)  评论(0编辑  收藏  举报