中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python中作用域的深入講解

發布時間:2020-09-21 11:02:17 來源:腳本之家 閱讀:117 作者:駿馬金龍 欄目:開發技術

前言

作用域是指變量的生效范圍,例如本地變量、全局變量描述的就是不同的生效范圍。

python的變量作用域的規則非常簡單,可以說是所有語言中最直觀、最容易理解的作用域。

在開始介紹作用域之前,先拋一個問題:

x=1
def f():
 x=3
 g()
 print("f:",x) # 3

def g():
 print("g:",x) # 1

f()
print("main:",x) # 1

上面的代碼將輸出3、1、1。解釋參見再述作用域規則。另外,個人建議,本文最后一小節內容盡量理解透徹。

python作用域規則簡介

它有4個層次的作用域范圍:內部嵌套函數、包含內部嵌套函數的函數自身、全局作用域、內置作用域。上面4個作用域的范圍排序是按照從內到外,從小到大排序的。

Python中作用域的深入講解

其中:

  • 內置作用域是預先定義好的,在__builtins__模塊中。這些名稱主要是一些關鍵字,例如open、range、quit等
  • 全局作用域是文件級別的,或者說是模塊級別的,每個py文件中處于頂層的變量都是全局作用域范圍內的變量
  • 本地作用域是函數內部屬于本函數的作用范圍,因為函數可以嵌套函數,嵌套的內層函數有自身的內層范圍
  • 嵌套函數的本地作用域是屬于內層函數的范圍,不屬于外層

所以對于下面這段python代碼來說,如果它處于a.py文件中,且沒有嵌套在其它函數內:

X=1
def out1(i):
 X=2
 Y='a'
 print(X)
 print(i)
 def in1(n):
 print(n)
 print(X,Y)
 in1(3)
out1(2)

那么:

處于全局作用域范圍的變量有:X、out1

處于out1本地作用域范圍的變量有:i、X、Y、in1

處于嵌套在函數out1內部的函數in1的本地作用域范圍的變量有:n

注意上面的函數名out1和in1也是一種變量。

如下圖所示:

Python中作用域的深入講解

搜索規則

當在某個范圍引用某個變量的時候,將從它所在的層次開始搜索變量是否存在,不存在則向外層繼續搜索。搜索到了,則立即停止。

例如函數ab()中嵌套了一個函數cd(),cd()中有一個語句print(x),它將首先檢查cd()函數的本地作用域內是否有x,如果沒有則繼續檢查外部函數ab()的本地作用域范圍內是否有x,如果沒有則再次向外搜索全局范圍內的變量x,如果還是沒有,則繼續搜索內置作用域,像"x"這種變量名,在內置作用域范圍內是不存在的,所以最終沒有搜索到,報錯。如果一開始在cd()中就已經找到了變量x,就不會再搜索ab()范圍以及更外層的范圍。

所以,內層范圍可以引用外層范圍的變量,外層范圍不包括內層范圍的變量。

內置作用域

內置作用域主要是一些內置的函數名、內置的異常等關鍵字。例如open,range,quit等。

兩種方式可以搜索內置作用域:一是直接導入builtins模塊,二是讓python自動搜索。導入builtins模塊會讓內置作用域內的變量直接置于當前文件的全局范圍,自動搜索內置作用域則是最后的階段進行搜索。

一般來說無需手動導入builtins模塊,不過可以看看這個模塊中包含了哪些內置變量。

>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', ...............
'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

變量掩蓋和修改規則

如果在函數內部引用了一個和全局變量同名的變量,且不是重新定義、重新賦值(其實python中沒有變量聲明的概念,只有賦值的概念),那么函數內部引用的是全局變量。

例如,下面的函數g()中,print函數中的變量x并未在g()中單獨定義或賦值,所以這個x引用的是全局變量x,它將輸出值3。

x=3
def g():
 print(x) # 引用全局變量x

如果函數內部重新賦值了一個和全局變量名稱相同的變量,則這個變量是本地變量,它會掩蓋全局變量。注意是掩蓋而非覆蓋,掩蓋的意思是出了函數的范圍(函數退出),全局變量就會恢復。或者換句話說,在函數內部看到的是本地變量x=2,在函數外部看到的是全局變量x=3

例如:下面的g()中重新聲明了x,這個x稱為g()的本地變量,全局變量x=3暫時被掩蓋(當然,這是對該函數來說的掩蓋)。

x=3
def g():
 x=2 # 定義并賦值本地變量x
 print(x) # 引用本地變量x

python是一種解釋性語言,讀一行解釋一行,讀了下一行就忘記前一行(詳細見下文)。所以在使用變量之前必須先進行變量的定義(聲明)。

例如下面是錯誤的:

def g():
 print(x)
 x=3
g()

錯誤信息:

UnboundLocalError: local variable 'x' referenced
before assignment

這個很好理解,但是下面和同名的全局變量混合的時候,就不那么容易理解了:

x=1
def g():
 print(x)
 x=2
g()

這里也會報錯,而不是輸出x=1或2。

這里需要解釋一下,雖說python是逐行解釋的。但每個函數屬于一個區塊,這個區塊范圍是一次性解釋的,并不會讀一行忘記一行,而是一直讀,讀完整個區塊再解釋。所以,讀完整個g()區塊后,首先就記住了重新定義了本地變量x=2,于是g()中所有使用變量x的時候,都是本地變量x,所以print(x)中的x也是本地變量,但這違反了使用變量前先賦值的規則,所以也會報錯。

因此,在函數內修改和全局變量同名的變量前,必須先修改,再使用該變量。所以,上面的代碼中,x=2必須放在print的前面:

x=1
def g():
 x=2
 print(x)
g()

所以,對于函數來說,也必須先定義函數,再調用函數。下面是錯誤的:

g()
def g():
 x=2
 print(x)

報錯信息:

NameError: name 'g' is not defined

但是下面的代碼中,f()中先調用了g(),然后才定義g(),為什么能執行呢:

x=1
def f():
 x=3
 g()
 print("f:",x) # 3

def g():
 print("g:",x) # 1

f()
print("main:",x) # 1

實際上并非是先調用了g(),python解釋到def f()區塊的時候,只是聲明這一個函數,并非調用這個函數,真正調用f()的時候是在def g()區塊的后面,所以實際上是先聲明完f()和g()之后,再調用f()和g()的。

但如果把f()放在def f()def g()的中間,將會報錯,因為調用f()函數的時候,def g()還沒解釋到,也就是說g()還沒有聲明。

x=1
def f():
 x=3
 g()
 print("f:",x)

f() # 報錯

def g():
 print("g:",x) 

更容易犯錯的一種情況是邊賦值,邊使用。下面的代碼是錯誤的:

x=3

def f1():
 x += 3
 print(x)
f1()

因為x += 3也是賦值操作,函數內部只要是賦值操作就表示聲明為本地變量。它首先計算x=x+3右邊的x+3,然后將結果賦值給左邊的變量x,但計算x+3的時候變量x并未定義,所以它是錯誤的。錯誤信息:

UnboundLocalError: local variable 'x' referenced before assignment

關于全局變量

關于python中的全局變量:

  • 每個py文件(模塊)都有一個自己的全局范圍
  • 文件內部頂層的,不在def區塊內部的變量,都是全局變量
  • def內部聲明(賦值)的變量默認是本地變量,要想讓其變成全局變量,需要使用global關鍵字聲明
  • def內部如果沒有聲明(賦值)某變量,則引用的這個變量是全局變量

默認情況下,下面f()中的x變量是全局變量:

x=2
def f():
 print(x)
f() # 輸出2

默認情況下,下面f()中的x變量是本地變量:

x=2
def f():
 x=3
 print(x)
f() # 輸出3
print(x) # 輸出2

global關鍵字

如果想要在def的內部修改全局變量,就需要使用global關鍵字聲明變量:

x=2
def f():
 global x
 x=3
 print(x)

f() # 輸出3
print(x) # 輸出3

global可以聲明一個或多個變量為全局變量,多個變量使用逗號隔開,也可以聲明事先不存在的變量為全局變量:

x=2
def f():
 global x,y
 x,y = 3,4
 print(x,y)
f()
print(x,y)

不能global中直接賦值。所以下面的是錯的:

global x=2

注意,global不是聲明變量,在變量賦值之前,變量是一定不存在的,就算是被global修飾了也一樣不存在,所以下面的代碼是錯的。實際上,global有點類似于聲明變量的名稱空間,而非變量。

x=2
def f():
 global x,y
 print(y)

報錯信息:

NameError: name 'y' is not defined

必須在print(y)之前(不能是之后)加上y的賦值語句,才表示它的存在。

x=2
def f():
 global x,y
 y=3
 print(y)

global修飾的變量必須在它的賦值之前,所以下面的是錯的,因為y=2首先將它聲明為本地變量了。

def f():
 y=2
 global y

全局變量的不安全性

考慮下面這個問題:

x=2
def f():
 global x
 x=3

def g():
 global x
 x=4

f()或g()
print(x)

這時,函數f()和g()的調用順序決定了print輸出的全局變量x的值。因為全局變量是共享的,如果多線程同時執行這段代碼,就不知道是誰先誰后修改,導致print(x)的值隨機性。這是多線程不安全特性。因此,如果允許,應盡量不要將函數內部的變量修飾為全局變量。

訪問其它模塊中的全局變量

python中一個文件一個模塊,在模塊1中可以導入模塊2中的屬性(例如全局變量)。

例如,b.py文件中:

x=3

a.py文件中:

import b
print(b.x)
b.x=4

上面的a.py中導入了b.py模塊,通過b.x可以訪問、修改這個來自于b.py中的全局變量x。

這是極不安全的,因為誰也不知道是否有其他的模塊也在修改b.x

所以,沒有人會去直接修改其它模塊的屬性,如果要修改,基本上都會通過類似面向對象中的setter函數進行修改。只需在b.py中定義一個函數,以后在其它文件中使用這個函數修改即可。

b.py文件中:

x=3
def setx(n)
 global x
 x=n

a.py文件中:

import b
b.setx(54) # 將b.x變量設置為54

其它訪問全局變量的方法

上面通過import導入模塊文件,就可以獲取這個模塊中屬性的訪問權。實際上,也可以在當前模塊文件中使用import mod_name導入當前模塊,其中mod_name為當前文件名,這樣就可以在函數內部直接訪問全局變量,而無需使用global關鍵字。

除了import mod_name可以導入當前模塊,使用sys模塊的modules()函數也可以導入:sys.modules['mod_name']

例如,在b.py文件中:

x=3

def f():
 global x
 x += 2

def f1():
 x=4 # 本地變量

def f2():
 x=4 # 本地變量
 import b
 b.x += 2 # 全局變量

def f3():
 x=4 # 本地變量
 import sys
 glob = sys.modules['b']
 glob.x += 2 # 全局變量

def test():
 print("aaa",x) # 輸出3
 f();f1();f2();f3()
 print("bbb",x) # 輸出9

在a.py文件中:

import b
b.test()

nonlocal關鍵字

當函數進行嵌套的時候,內層函數的作用域是最內層的,它的外層是外層函數的作用域。內層函數和外層函數的關系類似于本地作用域與全局作用域的關系:

(1).內層函數中賦值的變量是屬于內層、不屬于外層的本地變量

(2).內層函數中使用的未在當前內層函數中賦值的變量是屬于外層、全局的變量

例如,下面的嵌套代碼中,f2()中print(x,y)的x是屬于外層函數f1()的本地變量,而y則是屬于內層函數自身的本地變量,外層函數f1()無法訪問屬于內層函數的y。

x=3

def f1():
 x=4
 def f2():
 y=5
 print(x,y)
 f2()
f1()

nonlocal語句可以修飾內層函數中的變量使其成為它上一層函數的變量。它的用法和global基本相同,修飾多個變量的時候,需要逗號隔開。但和global有一點不同,global修飾的變量可能事先并未存在于全局作用域內,但nonlocal修飾的變量必須已經存在于上層或上上層(或更多層)函數,不能只存在于全局(見下面示例)。

例如下面的代碼片段中嵌套了2次,其中f3()中的x使用nonlocal修飾,使得這個x變成它上一層作用域f2()中的x變量。

x=3

def f1():
 x=4 # f1的本地變量
 def f2():
 x=5 # f2的本地變量
 def f3():
 nonlocal x # f2的本地變量
 print("f3:",x) # 輸出5
 x=6
 f3()
 print("f2:",x) # 被修改,輸出6
 f2()
f1()

上面的代碼將輸出:

f3: 5
f2: 6

如果將上面的f2()中的x=5刪除,會如何?

x=3

def f1():
 x=4
 def f2():
 def f3():
 nonlocal x # f1()的本地
 print("f3:",x) # 輸出4
 x=6 # 修改f1()的本地
 f3()
 print("f2:",x) # 輸出6
 f2()
 print("f1:",x) # 輸出6
f1()

注意,上面f3()中的nonlocal將x修飾為f1()的本地變量,因為f3()的上一層f2()中沒有變量x,所以f2()繼承了f1()的變量x,使得f3()修改上一層f2()中的變量,等價于修改f1()中的變量x。

但如果把f1()中的x=4也刪除,那么將報錯,因為nonlocal無法將變量修飾為全局范圍。

所以,nonlocal默認將內層函數中的變量修飾為上一層函數的作用域范圍,如果上一層函數中不存在該變量,則修飾為上上層、上上上層直到頂層函數,但不能修飾為全局作用域范圍。

同樣的,只要在內層函數中賦值,就表示聲明這個變量的作用域為內層函數作用域范圍。所以,下面的代碼是錯誤的:

x=3
def f1():
 x=4
 def f2():
 print(x)
 x=3
 f2()
f1()

下面的代碼也是錯的:

x=3
def f1():
 x=4
 def f2():
 x += 3
 print(x)
 f2()
f1()

錯誤信息:

UnboundLocalError: local variable 'x' referenced before assignment

至于原因,前文已經解釋的很清楚。

訪問外層函數變量的其它方法

在以前的版本中,還沒有nonlocal關鍵字,這時如果要保存外層函數的變量,就需要使用函數參數默認值的方式定義內層函數。

x=3
def f1():
 x=4
 def f2(x=x):
 x += 3
 print("f2:",x)
 x=5
 f2()
 print("f1:",x)
f1()

輸出:

f2: 7
f1: 5

上面的f2(x=x)中,等號右邊的x來自于f1()中x=4,然后將其賦值給f2()的本地作用域變量x。注意,python的作用域是詞法作用域,函數區塊的定義位置決定了它看到的變量。所以,盡管調用f2()之前再次對x進行了賦值,f2()函數調用時,f2(x=x)等號右邊的x早已經賦值給左邊的本地變量x了。它們的關系如下圖所示:

Python中作用域的深入講解

避免函數嵌套

一般來說,函數嵌套都只用于閉包(工廠函數),而且是結合匿名函數(lambda)實現的閉包。其它時候,函數嵌套一般都可以改寫為非嵌套模式。

例如,下面的嵌套函數:

def f1():
 x=3
 def f2():
 nonlocal x
 print(x)
 f2()
f1()

可以改寫為:

def f1():
 x=3
 f2(x)

def f2(x):
 print(x)

f1()

循環內部的函數

當函數位于循環結構中,且這個函數引用了循環控制變量,那么結果可能會出乎意料。

本來以匿名函數(lambda)來解釋更清晰,但因為尚未介紹匿名函數,所以這里采用命名函數為例。

下面的代碼中,將5個函數作為列表的元素保存到列表list1中。

def f1():
 list1 = []
 for i in range(5):
 def n(x):
 return i+x
 list1.append(n)
 return list1

mylist = f1()
for i in mylist: print(i)
print(mylist[0](2))
print(mylist[2](2))

結果:

<function f1.<locals>.n at 0x02F93660>
<function f1.<locals>.n at 0x02F934B0>
<function f1.<locals>.n at 0x02F936A8>
<function f1.<locals>.n at 0x02F93738>
<function f1.<locals>.n at 0x02F93780>
6
6

從結果中可以看到mylist[0](2)mylist[2](2)的執行結果是一樣的,不僅如此,mylist[N](2)的結果也全都一樣。換句話說,保存到列表中的各個函數n()中所引用的循環控制變量"i"并沒有因為循環的迭代而改變,而且列表中所有函數保存的i的值都是循環的最后一個元素i=4

(注:對于此現象,各語言基本都是如此,本節稍作解釋,真正的本質原因在本文的最后一節做了額外的補充解釋代碼塊細述)。

先看下面的例子:

def f1():
 for i in range(5):
 def n():
 print(i)
 return n

f1()()

結果輸出4。可見,print(i)的值并沒有隨循環的迭代過程而改變。

究其原因,是因為def n()只是函數的聲明,它不會去查找i的值是多少,所以不會將i的值替換到函數n()的i變量,而是直接保存變量i的地址,當循環結束時,i指向最后一個元素i=4的地址。

當開始調用n()的時候,即f1()(),才會真正開始查找i的值,這時候i指向的正是i=4。

這就像下面的代碼一樣,在還沒有開始調用f()的時候,f()內部的x一直都只是指向它所看見的變量x,而這個x是全局作用域范圍。當真正開始調用f()的時候,才會去定位x的指向。

x=3
def f():
 print(x)

回到上面循環中的嵌套函數,如果要保證循環的迭代能作用到其內部的函數中,可以采用默認參數值的方式進行賦值:

def f1():
 list1 = []
 for i in range(5):
 def n(x,i=i):
 return i+x
 list1.append(n)
 return list1

上面def n(x,i=i)中的i=i是設置默認參數值,等號右邊的i是函數聲明時就查找并替換完成的,所以每次循環迭代過程中,等號右邊的i都不同,等號左邊的參數i的默認值就不同。

再述作用域規則

python的作用域是詞法作用域,這意味著函數的定義位置決定了它所看見的變量。除了詞法作用域,還有動態作用域,動態作用域意味著函數的調用位置決定了它所看見的變量。關于詞法、動態作用域,本文不多做解釋,想要了解的話,可以參考一文搞懂:詞法作用域、動態作用域、回調函數、閉包

下面是本文開頭的問題:

x=1
def f():
 x=3
 g()
 print("f:",x) # 3

def g():
 print("g:",x) # 1

f()
print("main:",x) # 1

對于python的這段代碼來說,這里有兩個值得注意的地方:

  • 調用函數之前,理論上要先定義好函數,但這里g()的調用似乎看上去比g()的定義更先
  • f()中調用g()時,為什么g()輸出的是1而不是3

第一個問題在前文已經解釋過了,再解釋一遍:雖然f()里面有g()的調用語句,但def f()只是聲明,但在調用f()之前,是不會去調用g()的。所以,只要f()的調用語句在def g()之后,就是正確的。

第二個問題,python是詞法作用域,所以:

(1).首先聲明def f(),在此期間會創建一個本地變量x,并且print("f:",x)中的x指向這個本地變量;

(2).然后聲明g(),在此期間,g()的定義語句不在f()內部,而是在全局范圍,所以它看見的是x是全局x,所以print("g:",x)中的x指向全局變量x;

當調用f()的時候,執行到g()時,g()中所指向的是全局范圍的x,而非f()段中的x。所以,輸出1。

再看一個嵌套在函數內部的示例:

x=3

def f1():
 x=4
 def f2():
 print(x)
 x=5
 f2()
f1() # 輸出5

這里的問題是f2()中的print為什么不輸出4,而是輸出5。

其實也很容易理解,因為def f2()是定義在f1()內部的,所以f2()看見的x是f1()中的x,也就是說print(x)中的x指向的是f1()中的x。但在調用f2()之前,重新賦值了x=5,等到調用f2()的時候,根據x的指向,將找到新的x的值。

也就是說,前面的示例中,有兩個獨立的變量x:全局的和f()本地的。后面這個示例中只有一個變量x,屬于f()。

代碼塊細述(必看)

代碼塊可以使得一段python代碼作為一個單元、一個整體執行。以下是 官方手冊 的描述。

A Python program is constructed from code blocks. A block is a piece of Python program text that is executed as a unit. The following are blocks: a module, a function body, and a class definition. Each command typed interactively is a block. A script file (a file given as standard input to the interpreter or specified as a command line argument to the interpreter) is a code block. A script command (a command specified on the interpreter command line with the ‘-c' option) is a code block. The string argument passed to the built-in functions eval() and exec() is a code block.

所以,有以下幾種類型的代碼塊:

  1. 模塊文件是一個代碼塊
  2. 函數體是一個代碼塊
  3. class的定義是一個代碼塊
  4. 交互式(python idle)的每一個命令行都是一個獨立的代碼塊
  5. 腳本文件是一個代碼塊
  6. 腳本命令是一個代碼塊(python -c "xxx")
  7. eval()和exec()中的內容也都有各自的代碼塊

代碼塊的作用是組織代碼,同時意味著退出代碼區塊范圍就退出了作用域范圍。例如退出函數區塊,就退出了函數的作用域,使得函數內的本地變量無法被函數的外界訪問。

此外,python是解釋性語言,讀一行解釋一行,這意味著每讀一行就忘記前一行。但實際上更嚴格的說法是讀一個代碼塊解釋一個代碼塊,這意味著讀代碼塊中的內容時,是暫時記住屬于這個代碼塊中所讀內容的,讀完整個代碼塊后再以統籌的形式解釋這個代碼塊。

先說明讀一行解釋一行的情況,也就是每一行都屬于一個代碼塊,這個只能通過python的交互式工具idle工具來測試:

>>> x=2000
>>> y=2000
>>> x is y
False
>>> x=2000;y=2000
>>> x is y
True

理論上分號是語句的分隔符,并不會影響結果。但為什么第一個x is y為False,而第二個x is y為True?

首先分析第一個x is y。由于交互式工具idle中每一個命令都是一個單獨的語句塊,這使得解釋完x=2000后立刻就忘記了2000這個數值對象,同時忘記的還有x變量本身。然后再讀取解釋y=2000,因為不記得剛才解釋的x=2000,所以會在內存中重新創建一個數值結構用來保存2000這個數值,然后用y指向它。換句話說,x和y所指向的2000在內存中是不同的數據對象,所以x is y為False。

下面的x is y返回True:

>>> x=2000;y=2000
>>> x is y
True

因為python按行解釋,一個命令是一個代碼塊。對于x=2000;y=2000,python首先讀取這一整行,發現x和y的數值對象都是2000,于是做個簡單優化,等價于x,y=2000,2000,這意味著它們屬于一個代碼塊內,由于都是2000,所以只會在內存中創建一個數據對象,然后x和y都引用這個數據對象。所以,x is y返回True。

idle工具中每個命令都是獨立的代碼塊,但是py文件卻是一個完整的代碼塊,其內還可以嵌套其它代碼塊(如函數、exec()等)。所以,如果上面的分行賦值語句放在py文件中,得到的結果將是True。

例如:

x = 2000
y = 2000
print(x is y) # True
def f1():
 z=2000
 z1=2000
 print(x is z) # False
 print(z is z1) # True

f1()

python先讀取x=2000,并在內存中創建一個屬于全局作用域的2000數據對象,再解釋y=2000的時候,發現這個全局對象2000已經存在了(因為x和y同處于全局代碼塊內),所以不會再額外創建新的2000對象。這里反映出來的結果是"同一個代碼塊內,雖然仍然是讀一行解釋一行,但在退出這個代碼塊之前,不會忘記這個代碼塊中的內容,而且會統籌安排這個代碼塊"。

同理def f1()內的代碼塊,因為z是本地作用域的變量,更標準的是處于不同代碼塊內,所以會在本地作用域內存區創建新的數據對象2000,所以x is z返回False。根據前面的解釋,z1 is z返回True。

再回顧前文多次出現的一個異常:

x = 3
def f1():
 print(x)
 x=4
f1()

報錯信息:

UnboundLocalError: local variable 'x' referenced before assignment

當執行到def語句的時候,因為def聲明函數,函數體是一個代碼塊,所以按照代碼塊的方式讀取屬于這個代碼塊中的內容。首先讀取print(x),但并不會直接解釋,而是會記住它,并繼續向下讀取,于是讀取x=4,這意味著x是一個本地變量。然后統籌安排整個代碼塊,將print(x)的x認為是本地變量而非全局變量。注意,直到def退出的時候都還沒有進行x的賦值,而是記錄了本地變量x,賦值操作是在函數調用的時候進行的。當調用函數f()的時候,發現print(x)中的x是本地變量,但因為還沒有賦值,所以報錯。

但是再看下面的,為什么又返回True?

>>> x=256
>>> y=256
>>> x is y
True

因為Python在啟動的時候就在內存中預先為常用的較小整數值(-5到256)創建好了對象,因為它們使用的非常頻繁(有些在python的內部已經使用了)。所以,對于這個范圍內的整數,都是直接引用,不會再在內存中額外創建新的數值對象,所以x is y總是返回true。甚至,這些小值整數可以跨作用域:

x = 3 
def f1():
 y=3
 print(x is y) # True

f1()

再看前文循環內的函數的問題。

def f1():
 for i in range(5):
 def n():
 print(i)
 return n

f1()()

前面對現象已經解釋過,內部函數n()中print(i)的i不會隨循環的迭代而改變,而是固定的值i=4。

python首先解釋def f1()的代碼塊,會記錄屬于這個代碼塊作用域內的變量i和n,但i和n都不會賦值,也就是說暫時并不知道變量n是一個函數變量。

同理,當需要解釋def n()代碼塊的時候,將記住這個代碼塊涉及到的變量i,只不過這個變量i是屬于外層函數的,但不管如何,這個代碼塊記住了i,且記住了它是外部函數作用域的。

注意,函數的聲明過程中,所有涉及到變量i的作用域內都不會對i進行賦值,僅僅只是保存了這個i變量名,只有在調用函數的時候才會進行賦值操作。

當開始調用f1()的時候,開始執行函數體中的代碼,于是開始循環迭代,且多次聲明函數n(),每一次迭代生成的n()都會讓原先已記錄的變量n指向這個新聲明的函數體(相當于賦值的操作,只不過是變量n引用的對象是函數體結構,而不是一般的數據對象),由于只是在循環中聲明函數n(),并沒有進行調用,所以不會對n()中的i進行賦值操作。而且,每次循環迭代都會讓變量n指向新的函數體,使得先前迭代過程中定義的函數被丟棄(覆蓋),所以最終只記住了最后一輪循環時聲明的函數n(),并且i=4。

當調用f1()()時,表示調用f1()中返回的函數n(),直到這個時候才會對n()內的i進行賦值,賦值時將搜索它的外層函數f1()作用域,發現這個作用域內的i指向內存中的數值4,于是最終輸出4。

再看下面的代碼:

def f1():
 for i in range(5):
 def n():
 print(i)
 n()
 return n

f1()

輸出結果:

0
1
2
3
4

調用f1()的時候,執行循環的迭代,每次迭代時都會調用n(),意味著每次迭代都要對n()中的i進行賦值。

另外注意,前面說過,函數的默認參數是在函數聲明時進行賦值的,所以下面的列表L中每個元素所代表的函數,它們的變量i都指向不同的數值對象。

def f1():
 L = []
 for i in range(5):
 def n(i=i):
 print(i)
 L.append(n)
 return L

f1()[0]()
f1()[1]()
f1()[2]()
f1()[3]()
f1()[4]()

執行結果:

0
1
2
3
4

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

朝阳区| 当阳市| 疏勒县| 攀枝花市| 赞皇县| 汉沽区| 大同县| 班玛县| 云梦县| 廊坊市| 黑水县| 安丘市| 德州市| 甘德县| 长丰县| 桐柏县| 镇安县| 芮城县| 奉贤区| 梁山县| 隆回县| 黄平县| 古浪县| 秦安县| 富平县| 普安县| 安康市| 崇左市| 光山县| 海宁市| 兴安盟| 康乐县| 青州市| 鹿泉市| 筠连县| 上杭县| 临潭县| 夏邑县| 临夏县| 蕲春县| 比如县|