一称簿、函數(shù)返回值
先看幾個例子
# return 語句之后可以執(zhí)行么?
def showplus(x):
print(x)
return x + 1
print('~~end~~')
showplus(5)
# 多條 return 語句都會執(zhí)行么惰帽?
def showplus(x):
print(x)
return x + 1
return x + 2
showplus(5)
# 下例多個 return 可執(zhí)行么憨降?
def guess(x):
if x > 3:
return "> 3"
else:
return "<= 3"
print(guess(10))
# 下面函數(shù)執(zhí)行的結(jié)果是什么
def fn(x):
for i in range(x):
if i > 3:
return i
else:
print("{} is not greater than 3".format(x))
print(fn(5)) # 打印 4
print(fn(3)) # 打印 None
總結(jié)
- Python 函數(shù)使用
return
語句返回 “返回值” - 所有函數(shù)都有返回值,若沒有
return
語句该酗,隱式調(diào)用return None
-
return
語句并不一定是函數(shù)的語句塊的最后一條語句 - 一個函數(shù)可存在多個
return
語句券册,但只有一條可被執(zhí)行,若沒有一條returm
語句被執(zhí)行垂涯,隱式調(diào)用return None
- 若有必要烁焙,可顯示調(diào)用
return None
,可簡寫為return
- 若函數(shù)執(zhí)行了
return
語句耕赘,函數(shù)就會返回骄蝇,當前被執(zhí)行的return
語句之后的其他語句就不會被執(zhí)行了 - 返回值的作用:結(jié)束函數(shù)調(diào)用、返回 “返回值”
能夠一次返回多個值么操骡?
def showvalues():
return 1, 3, 5
showvalues()
- 函數(shù)不能同時返回多個值
-
return 1, 3, 5
看似返回多個值九火,隱式的被python
封裝成了一個 元組 -
x, y, z = showlist()
使用解構(gòu)提取返回值更為方便
二、函數(shù) 作用域
2.1 作用域
一個標識符的可見范圍册招,這就是標識符的作用域岔激,一般常說的是變量的作用域
def foo():
x = 100
print(x) # 可以訪問到么?
上例中 x
不可被訪問到是掰,會拋出異常 NameError: name 'y' is not defined
虑鼎,原因在于函數(shù)是一個封裝,它會開辟一個 作用域键痛,x
變量被限制在這個作用域中炫彩,所以在函數(shù)外部 x
變量 不可見
注意:每一個函數(shù)都會開辟一個作用域
2.2 作用域分類
- 全局作用域
- 在整個程序運行環(huán)境中都可見
- 全局作用域中的變量稱為全局變量
- 局部作用域
- 在函數(shù)、類等內(nèi)部可見
- 局部作用域中的變量稱為局部變量絮短,其使用范圍不能超過其所在局部作用域
# 局部變量
def fn1():
x = 1 # 局部作用域江兢,x 為局部變量,使用范圍在 fn1 內(nèi)
def fn2():
print(x)
print(x)
# 全局變量
x = 5 # 全局變量丁频,也在函數(shù)外定義
def foo():
print(x)
foo()
- 一般來講外部作用域變量在函數(shù)內(nèi)部可見杉允,可以使用
- 反過來邑贴,函數(shù)內(nèi)部的局部變量,不能在函數(shù)外部看到
2.3 函數(shù)嵌套
在一個函數(shù)中定義了另一個函數(shù)
def outer():
def inner():
print("inner")
print("outer")
inner()
outer() # 可行么叔磷?
inner() # 可行么拢驾?
內(nèi)部函數(shù) inner
不能在外部直接使用,會拋 NameError
異常世澜,因為它在函數(shù)外部不可見
其實独旷, inner
不過就是一個標識符署穗,就是一個函數(shù) outer
內(nèi)部定義的變量而已
嵌套結(jié)構(gòu)作用域
對比下面嵌套結(jié)構(gòu)寥裂,代碼執(zhí)行的效果
def outer1():
o = 65
def inner():
print("inner {}".format(o))
print(chr(o))
inner()
print("outer {}".format(o))
outer1()
def outer2():
o = 65
def inner():
o = 97
print("inner {}".format(o))
print(chr(o))
inner()
print("outer {}".format(o))
outer2()
從執(zhí)行的結(jié)果來看
- 外層變量在內(nèi)部作用域可見
- 內(nèi)層作用域
inner
中,若定義了o = 97
案疲,相當于在當前函數(shù)inner
作用域中 重新定義了一個新的變量o
封恰,但 這個o
并不能覆蓋外部作用域outer2
中的變量o
,只不過對于inner
函數(shù)來說褐啡,其只能可見自己的作用域中定義的變量o
了
2.4 一個復(fù)制語句的問題
再看下例
仔細觀察函數(shù) 2 返回的錯誤指向 x += 1
诺舔,原因是什么呢?
x = 5
def foo():
x += 1
foo() # 報錯如下
原因分析
-
x += 1
其實是x = x + 1
- 相當于在
foo
內(nèi)部定義了一個局部變量x
备畦,那么foo
內(nèi)部所有x
都是這個局部變量x
了 -
x = x + 1
相當于使用了局部變量x
低飒,但是這個x
還沒有完成賦值,就被右邊拿來做加 1 操作了
如何解決這個常見問題懂盐?
2.5 global
語句
x = 5
def foo():
global x # 全局變量
x += 1
print(x)
foo()
- 使用
global
關(guān)鍵字的變量褥赊,將foo
內(nèi)的x
聲明為使用 外部的全局作用域 中定義的x
- 全局作用域中必須有
x
的定義
若全局作用域中沒有 x
定義會怎樣?
注意莉恼,下面實驗若在 ipython拌喉、jupyter 中做,上下文運行環(huán)境中有可能有 x
的定義俐银,稍微不注意尿背,就測試不出效果
# 有錯么?
def foo():
global x
x += 1
print(x)
foo()
# 有錯么捶惜?
def foo():
global x
x = 10
x += 1
print(x)
foo()
print(x) # 可以么田藐?
使用 global
關(guān)鍵字定義的變量,雖然在 foo
函數(shù)中聲明吱七,但是這將告訴當前 foo
函數(shù)作用域坞淮,這個 x
變量將使用外部全局作用域中的 x
即使實在 foo
中又寫了 x=10
,也不會在 foo
這個局部作用域中定義局部變量 x
了
使用了 global
陪捷,foo
中的 x
不再是局部變量了回窘,它是全局變量
總結(jié)
-
x+=1
這種是特殊形式產(chǎn)生的錯誤的原因?先應(yīng)用后賦值市袖,而 Python 動態(tài)語言是賦值才算定義啡直,才能被引用烁涌。解決辦法,在這條語句前增加x=0
之類的賦值語句酒觅,或使用global
告訴內(nèi)部作用域撮执,去全局作用域查找變量定義 - 內(nèi)部作用域使用
x = 10
之類的賦值語句會重新定義局部作用域使用的變量x
,但是舷丹,一旦這個作用域?qū)櫴褂?global
聲明x
為全局的抒钱,那么x=5
相當于在為全局作用域的變量x
賦值
global
使用原則
- 外部作用域變量會在內(nèi)部作用域可見,但也不要在這個內(nèi)部的局部作用域中直接使用颜凯,因為函數(shù)的目的就是為了封裝谋币,盡量與外界隔離
- 若函數(shù)需要使用外部全局變量,請盡量使用函數(shù)的形參定義症概,并在調(diào)用傳實參解決
- 一句話:不用
global
蕾额。學(xué)些它就是為了深入理解變量作用域
2.6 閉包
自由變量:未在本地作用域中定義的變量,例如定義在內(nèi)層函數(shù)外的外層函數(shù)的作用域中的變量
閉包:就是一個概念彼城,出現(xiàn)在嵌套函數(shù)中诅蝶,至的是 內(nèi)層函數(shù)引用到了外層函數(shù)的自由變量,就形成了閉包募壕。
def counter():
c = [0]
def inc():
c[0] += 1
return c[0]
return inc
foo = counter()
print(foo(), foo())
c = 100
print(foo())
上例代碼分析
- 第七行會執(zhí)行
counter()
并 返回inc
對應(yīng)的函數(shù)對象调炬,注意這個函數(shù)對象并不釋放,因為有foo
記住 - 第四行會報錯么舱馅?
- 不會報錯缰泡,因為修改的是
counter()
的本地變量c
中index
為 0 的元素的值,而不是重新定義c
變量
- 不會報錯缰泡,因為修改的是
- 第八行打印什么結(jié)果习柠?
- 打印
1 2
- 打印
- 第十行打印什么結(jié)果匀谣?
- 打印
3
- 第九行的
c
和counter
中的c
不一樣,而inc()
引用的是自由變量counter()
中的變量c
- 打印
這是 Python 2 中實現(xiàn)閉包的方式资溃,Python 3 還可使用 nonlocal
關(guān)鍵字
def counter():
count = 0
def inc():
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
上例一定出錯武翎,使用 global
可解決
def counter():
global count
count = 0
def inc():
global count
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
上例使用 global
解決,這是全局變量的實現(xiàn)溶锭,而不是閉包了
若對這個普通變量使用閉包宝恶,Python 3 中可使用 nonlocal
關(guān)鍵字
2.7 nonlocal
語句
nonlocal
:將變量標記為不在本地作用域定義,而是在 上級的某一級局部作用域 中定義趴捅,但 不能是全局作用域 中定義
def counter():
count = 0
def inc():
nonlocal count # 聲明變量 count 不是本地變量
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
count
是外層函數(shù)的局部變量垫毙,被內(nèi)部函數(shù)引用
內(nèi)部函數(shù)使用 nonlocal
關(guān)鍵字聲明 count
變量在上級作用域而非本地作用域中定義
代碼中內(nèi)層函數(shù)應(yīng)用外部局部作用域中的自由變量,形成閉包
上例是錯誤的拱绑,nonlocal
聲明變量 a
不在當前作用域综芥,但是往外就是全局作用域了,所以報錯
三猎拨、默認值的作用域
為何第二次調(diào)用 foo()
打印的值 [1, 1]
- 因為函數(shù)也是對象膀藐,每個函數(shù)定義被執(zhí)行后屠阻,就生成一個函數(shù)對象和函數(shù)名這個標識符關(guān)聯(lián)
- Python 把函數(shù)的默認值放在了函數(shù)對象的屬性中,這個屬性就伴隨著這個函數(shù)對象的整個生命周期
- 查看
foo.__defaults__
屬性為元祖
def foo(xyz=[], m=123, n='abc'):
xyz.append(1)
print(xyz)
print(id(foo), foo.__defaults__)
foo()
print(id(foo), foo.__defaults__)
foo()
print(id(foo), foo.__defaults__)
函數(shù)地址并沒有變额各,就是說 foo()
這個函數(shù)對象沒有變過国觉,調(diào)用它,他的屬性 __defaults__
中使用元祖保存默認值 xyz
的默認值為引用類型虾啦,引用類型的元素變動麻诀,并不是元祖的變化
# 非引用類型缺省值
def foo(xyz, m=123, n='abc'):
m = 456
n = 'def'
print(xyz)
print(foo.__defaults__)
foo('rookie')
print(foo.__defaults__)
屬性 __defaults__
中使用元祖保存所有位置參數(shù)默認值,不會因為在函數(shù)體內(nèi)改變了局部變量(形參)的值而發(fā)生改變
# keyword-only 參數(shù)的缺省值
def foo(xyz, m=123, *, n='abc', t=[1, 2]):
m = 456
n = 'def'
t.append(300)
print(xyz, m, n, t)
print(foo.__defaults__, foo.__kwdefaults__)
foo('rookie')
print(foo.__defaults__, foo.__kwdefaults__)
屬性 __defaults__
中使用元組保存所有位置參數(shù)默認值
屬性 __kwdefaults__
中使用字典保存所有 keyword-only
參數(shù)的默認值
def x(a=[]):
a += [5]
print(x.__defaults__)
x()
x()
print(x.__defaults__)
def y(a=[]):
a = a = [5]
print(y.__defaults__)
y()
y()
print(y.__defaults__)
輸出結(jié)果不一致
-
+
表示兩個列表合并并返回一個新列表 -
+=
表示就地修改一個列表傲醉,在其后追加一個列表蝇闭。就是extend
方法
四、變量名解析原則 LEGB
- Local需频,本地作用域丁眼、局部作用域的
local
命名空間筷凤。函數(shù)調(diào)用時創(chuàng)建昭殉,調(diào)用結(jié)束消亡 - Enclosing,Python 2.2 時引入了嵌套函數(shù)藐守,實現(xiàn)了閉包挪丢,這個就是嵌套函數(shù)的外部函數(shù)的命名空間
- Global,全局作用域卢厂,即一個模塊的命名空間乾蓬。模塊被
import
時創(chuàng)建,解釋器退出時消亡 - Build-in慎恒,內(nèi)置模塊的命名空間任内,生命周期從 Python 解釋器啟動時創(chuàng)建到解釋器退出時消亡。例如
print(open)
融柬,print
和open
都是內(nèi)置的變量
所以一個名詞的查找順序就是 LEGB
五死嗦、函數(shù)的銷毀
- 定義一個函數(shù)就是生成一個函數(shù)對象,函數(shù)名指向的就是函數(shù)對象
- 可使用
del
語句刪除函數(shù)粒氧,使其引用計數(shù)減一 - 可使用同名標識符覆蓋原有定義越除,本質(zhì)上也是使其引用計數(shù)減一
- Python 程序結(jié)束時,所有對象銷毀
- 函數(shù)也是對象外盯,也不例外摘盆,是否銷毀,還是看引用計數(shù)是否減為 0