1. 程式人生 > >python3:遞迴函式中使用global nonlocal

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]