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]