一、函數(shù)返回值
先看下幾個(gè)例子:
# return之后可以執(zhí)行嗎?
def showplus(x):
print(x)
return x + 1
print('~~~~end~~~~') # return之后會(huì)執(zhí)行嗎? -- 不會(huì)!
showplus(5)
# 多條return語句都會(huì)執(zhí)行嗎?
def showplus(x):
print(x)
return x + 1 # 會(huì)被執(zhí)行
return x + 2 # 不會(huì)被執(zhí)行
print(showplus(5))
# 下例多個(gè)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 > 3
else:
print("{} is not in greater than 3".format(i))
print(fn(5))
print(fn(3))
總結(jié):
- python函數(shù)使用return語句返回 "返回值"
- 所有函數(shù)都有返回值,如果沒有return語句的,隱式調(diào)用return None
- return語句不一定是函數(shù)語句塊的最后一句
- 一個(gè)函數(shù)可以有多個(gè)return語句,但是只有一條可以被執(zhí)行。如果沒有一條被執(zhí)行到,則會(huì)隱式調(diào)用return None
- 如果有必要,可以顯示調(diào)用return None,可以簡寫成return
- 如果函數(shù)執(zhí)行return語句,函數(shù)就會(huì)返回,當(dāng)前被執(zhí)行的return語句之后的其他語句就不再運(yùn)行了
- 返回語句的作用:結(jié)束函數(shù)調(diào)用、返回返回值
能夠一次返回多個(gè)值嗎?
def showvalues():
return 1, 3, 5
print(showvalues()) # 返回了一個(gè)值,是一個(gè)三元組;并不是返回三個(gè)值
- 函數(shù)不能同時(shí)返回多個(gè)值
- return 1,3,5 看似返回多個(gè)值,其實(shí)隱式地被python封裝成了一個(gè)元組
- 使用解構(gòu)
x, y, z = showvalues()提取返回值更加方便
二、函數(shù)作用域
1、作用域
一個(gè)標(biāo)識(shí)符地可見范圍,就是標(biāo)識(shí)符地作用域。一般常說的是變量地作用域。
def foo():
x = 100
print(x) # 可以訪問地到嗎? -- 不行
上例的 x 不可以訪問到,會(huì)拋出異常 (NameError: name 'x' is not defined),原因在于函數(shù)是一個(gè)封裝,它會(huì)開辟一個(gè)作用域,x變量被限制在這個(gè)作用域中,所以在函數(shù)外部,x變量不可見。
注意:每一個(gè)函數(shù)都會(huì)開辟一個(gè)作用域
2、作用域的分類
2.1 全局作用域
- 在整個(gè)程序運(yùn)行環(huán)境中都可見
- 全局作用域中的變量稱為全局變量
2.2 局部作用域
- 在函數(shù)、類等內(nèi)部可見
- 局部作用域中的變量稱為局部變量,其使用范圍不能超過其所在局部作用域
# 局部變量
def fn1():
x = 1 # 局部變量
def fn2():
print(x) # 訪問不到x
print(x) # 訪問不到x
# 全局變量
x = 5 # 全局變量
def foo():
print(x) # 訪問的到x
foo()
- 一般來講,外部作用域變量在函數(shù)內(nèi)部可見,可以使用
- 反過來,函數(shù)內(nèi)部的局部變量,不能再函數(shù)外部看到
3、函數(shù)嵌套
在函數(shù)中定義另一個(gè)函數(shù):
def outer():
def inner():
print("inner")
print("outer")
inner()
outer()
inner() # NameError: name 'inner' is not defined
內(nèi)部函數(shù) inner 不能在外部直接使用,會(huì)拋出NameError異常,因?yàn)樗谄渌瘮?shù)外部不可見。
其實(shí),inner 不過就是一個(gè)標(biāo)識(shí)符,就是一個(gè)函數(shù) outer 內(nèi)部定義的變量而已。
3.1 嵌套結(jié)構(gòu)的作用域
對(duì)比下面嵌套結(jié)構(gòu),代碼執(zhí)行效果的比較
def outer1():
o = 65
def inner():
print("inner {}".format(o))
print(chr(o))
inner()
print("outer {}".format(o))
outer1()
# 運(yùn)行結(jié)果:
inner 65
A
outer 65
def outer2():
o = 65
def inner():
o = 97
print("inner {}".format(o))
print(chr(o))
inner()
print("outer {}".format(o))
outer2()
# 運(yùn)行結(jié)果:
inner 97
a
outer 65
從執(zhí)行結(jié)果可以看出:
- 外部變量在內(nèi)部作用域可見
- 內(nèi)部作用域中,如果定義了
o = 97,相當(dāng)于在當(dāng)前函數(shù)inner作用域中重新定義了一個(gè)新的變量o,但是,這個(gè)o并不能覆蓋掉外部作用域outer2中的變量o。只是對(duì)于inner函數(shù)來說,其只能看見自己作用域中定義的變量o。
4、一個(gè)賦值語句的問題
思考:
| 函數(shù)1 | 函數(shù)2 |
|---|---|
| x = 5 def foo(): print(x) foo() |
x = 5 def foo(): y = x + 1 # 可以運(yùn)行 x += 1 # UnboundLocalError: local variable 'x' referenced before assignment print(x) foo() |
| 正常執(zhí)行,函數(shù)外部的變量在函數(shù)內(nèi)部可見 | 執(zhí)行錯(cuò)誤,為什么?難道函數(shù)內(nèi)部又不可見了嗎? |
原因分析:
-
x += 1等價(jià)于x = x + 1,相當(dāng)于在 foo內(nèi)部定義一個(gè)局部變量x,那么foo內(nèi)部所有x都是局部變量x了 -
x = x + 1相當(dāng)于使用了全局變量x,但是這個(gè)x還沒完成賦值,就被右邊拿來做加1操作了
如何解決這個(gè)問題呢?
5、global語句
x = 5
def foo():
global x # 全局變量聲明
x += 1
print(x)
foo()
- 使用global關(guān)鍵字的變量,將foo內(nèi)的x聲明為使用外部全局作用域定義的x
- 全局作用域中必須有x的定義
如果全局作用域中沒有x的定義會(huì)怎么樣?
# 有錯(cuò)嗎?
def foo():
global x
x += 1
print(x)
foo() # NameError: name 'x' is not defined
# 有錯(cuò)嗎?
def foo():
global x
x = 10
x += 1
print(x)
foo()
print(x) # 可以嗎?-- 可以
使用global關(guān)鍵字定義全局變量,雖然在foo函數(shù)中聲明,但是這將告訴我們當(dāng)前foo函數(shù)作用域,這個(gè)x變量將使用外部全局作用域中的x。
即使是在foo中又寫了 x = 10,也不會(huì)在foo這個(gè)局部作用域中定義局部變量x了。
使用了global,foo中的 x 不再是局部變量了,它是全局變量。
5.1 總結(jié)
-
x += 1這種特殊形式產(chǎn)生的錯(cuò)誤的原因是 -- 先引用后賦值。而python動(dòng)態(tài)語言是賦值才算定義,才能被引用。解決辦法,在這條語句之前加上x = 0之類的賦值語句,或者使用global告訴內(nèi)部作用域,去全局作用域查找變量定義 - 內(nèi)部作用域使用
x = 10之類的賦值語句會(huì)重新定義局部變量作用域使用的變量x,但是,一旦這個(gè)作用域中使用global聲明x為全局的,那么x = 5相當(dāng)于在為全局作用域的變量x賦值
5.2 global的使用原則
- 外部作用域變量會(huì)在內(nèi)部作用域可見,但是也不要在這個(gè)內(nèi)部的局部作用域中直接使用,因?yàn)楹瘮?shù)的目的就是為了封裝,盡量和外界隔離
- 如果函數(shù)需要使用外部全局變量,請(qǐng)盡量使用函數(shù)的形參定義,并在調(diào)用實(shí)參中傳實(shí)參解決
- 一句話,不用global。學(xué)習(xí)他就是為了深入了解作用域而已
6、閉包
自由變量:未在本地作用域中定義的變量。例如定義在內(nèi)層函數(shù)外的外層函數(shù)的作用域中的變量。
閉包:就是一個(gè)概念,出現(xiàn)在嵌套函數(shù)中,指的是內(nèi)層函數(shù)引用到了外層函數(shù)的自由變量,就形成了閉包。很多語言都有這個(gè)概念,最熟悉的就是JavaScript。
def counter():
c = [0]
def inc():
c[0] += 1 # 會(huì)報(bào)錯(cuò)嗎?為什么
return c[0]
return inc
foo = counter()
print(foo(), foo())
c = 100
print(foo())
# 思考:
# 第4行會(huì)不會(huì)報(bào)錯(cuò)?
# 第8行打印結(jié)果是什么?
# 第10行打印結(jié)果是什么?
代碼分析:
- 第7行會(huì)執(zhí)行counter函數(shù),返回inc對(duì)應(yīng)的函數(shù)對(duì)象,注意這個(gè)函數(shù)對(duì)象并不釋放,因?yàn)橛衒oo記著
- 第4行會(huì)報(bào)錯(cuò)嗎?為什么
- 不會(huì)
- 因?yàn)閏已經(jīng)在counter中定義過了,而且inc中的使用方式為c修改元素值,而不是重新定義c變量,所以inc函數(shù)不認(rèn)為c變量是本地變量,而是引用了外部函數(shù)的自由變量c,沒有犯[前面講過的賦值語句引起的錯(cuò)誤](# 4、一個(gè)賦值語句的問題)
- 第8行打印結(jié)果是什么?
- 打印1 2
- 第10行打印結(jié)果是什么?
- 打印3
- 第9行的c和counter定義的c不一樣,而inc使用的是自由變量正是counter中的變量c
這是python 2中實(shí)現(xiàn)閉包的方式,python 3還可以使用nonlocal關(guān)鍵字。
在看下面這段代碼,會(huì)報(bào)錯(cuò)嗎?使用global能解決嗎?
def counter():
count = 0
def inc():
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
肯定會(huì)報(bào)錯(cuò),因?yàn)榈?行犯了前面講過的賦值語句引起的錯(cuò)誤,使用global能解決:
def counter():
global count #
count = 0
def inc():
global count #
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
這是使用全局變量來解決,而不是閉包。
如果要對(duì)這個(gè)普通變量使用閉包,python 3中提供nonlocal關(guān)鍵字。
7、nonlocal語句
nonlocal:將變量標(biāo)記成不是本地的,而是在上級(jí)的的某一級(jí)作用域中定義的,但是不能是全局作用域中的變量。
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變量在上級(jí)作用域而非本地作用域中定義。內(nèi)層函數(shù)引用外層局部作用域中的自由變量,形成閉包。
a = 50
def counter():
nonlocal a # SyntaxError: no binding for nonlocal 'a' found,上級(jí)作用域是全局
a += 1
print(a)
count = 0
def inc():
global count
count += 1
return count
return inc
foo = counter()
foo()
foo()
上例是錯(cuò)誤的,nonlocal聲明a不在當(dāng)前作用域,但是往外就是全局作用域了,所以錯(cuò)誤。
三、默認(rèn)值的作用域
def foo(xyz=1):
print(xyz)
foo() # 1
foo() # 1
print(xyz) # NameError: name 'xyz' is not defined
def foo(xyz=[]):
xyz.append(1)
print(xyz)
foo() # [1]
foo() # [1, 1]
print(xyz) # NameError: name 'xyz' is not defined
為什么第二個(gè)函數(shù)第6行打印出來是 [1, 1] ?
- 因?yàn)楹瘮?shù)也是對(duì)象,每個(gè)函數(shù)執(zhí)行完后,就生成了一個(gè)函數(shù)對(duì)象和函數(shù)名這個(gè)標(biāo)識(shí)符關(guān)聯(lián)
- python把函數(shù)的默認(rèn)值放在了函數(shù)對(duì)象的屬性中,這個(gè)屬性就伴著這個(gè)函數(shù)對(duì)象的整個(gè)生命周期
- 查看foo.__defaults__ 屬性,他是個(gè)元組
def foo(xyz=[]):
xyz.append(1)
print(xyz)
print(id(foo), foo.__defaults__)
foo()
print(id(foo), foo.__defaults__)
foo()
print(id(foo), foo.__defaults__)
# 運(yùn)行結(jié)果
2464804553352 ([],)
[1]
2464804553352 ([1],)
[1, 1]
2464804553352 ([1, 1],)
函數(shù)地址沒有變,就是說foo這個(gè)函數(shù)對(duì)象沒有動(dòng)過,調(diào)用它,它的屬性 _defaults_ 中使用元組保存默認(rèn)值
xyz默認(rèn)值是引用類型,引用類型的元素變動(dòng),并不是元組的變動(dòng)
非引用類型的默認(rèn)值:
def foo(xyz, m=123, n='abc'):
m = 456
n = 'def'
print(xyz)
print(id(foo), foo.__defaults__) # 1370273530504 (123, 'abc')
foo('pinginglab') # pinginglab
print(id(foo), foo.__defaults__) # 1370273530504 (123, 'abc'),和前面一樣
屬性 _defaults_ 中使用元組保存所有參數(shù)的默認(rèn)值,它不會(huì)因?yàn)樵诤瘮?shù)體內(nèi)改變了局部變量(形參)的值而發(fā)生改變
keyword-only參數(shù)的默認(rèn)值:
def foo(xyz, m=123, *, n='abc', t=[1, 2]):
m = 456
n = 'def'
t.append(300)
print(xyz, m, n, t)
print(id(foo), foo.__defaults__, foo.__kwdefaults__)
foo('pinginglab')
print(id(foo), foo.__defaults__, foo.__kwdefaults__)
屬性 _defaults_ 中使用元組保存所有位置參數(shù)的默認(rèn)值
屬性 _kwdefaults_ 中使用字典保存所有keyword-only參數(shù)的默認(rèn)值
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é)果卻不同?
這是因?yàn)?a += [5] 和 a = a + [5] 的區(qū)別:
- + 表示兩個(gè)列表合并并返回一個(gè)新的列表
- += 表示就地修改前一個(gè)列表,在其后面追加一個(gè)列表,就是相當(dāng)于 a.extend([5])
四、變量名解析原則LEGB
- Local:本地作用域,局部作用域的local命名空間。函數(shù)調(diào)用時(shí)創(chuàng)建,調(diào)用結(jié)束消亡
- Enclosing:python 2.2引入了嵌套函數(shù),實(shí)現(xiàn)了閉包,這個(gè)就是嵌套函數(shù)的外部函數(shù)的命名空間
- Global:全局作用域,即一個(gè)模塊的命名空間。模塊被import時(shí)創(chuàng)建,解釋器退出時(shí)消亡
- Build-in:內(nèi)置模塊命名空間,生命周期從python解釋器啟動(dòng)時(shí)創(chuàng)建到解釋器退出時(shí)消亡。例如print(open),print 和 open 都是內(nèi)置的函數(shù)標(biāo)識(shí)符
所以一個(gè)變量的查找順序就是LEGB。
<img src="https://gitee.com/jackyfmy/resource/raw/master/static/20200416003010.png" style="zoom: 67%;" />
五、函數(shù)的銷毀
定義一個(gè)函數(shù)就是生成一個(gè)函數(shù)對(duì)象,函數(shù)名指向的就是函數(shù)對(duì)象。
可以使用del語句刪除函數(shù),使其引用計(jì)數(shù)減一。
可以使用同名標(biāo)識(shí)符覆蓋原有定義,本質(zhì)上也是使其引用計(jì)數(shù)減一。
python程序結(jié)束時(shí),所有對(duì)象銷毀。
函數(shù)也是對(duì)象,也不例外,是否銷毀,還是看引用計(jì)數(shù)是否減為0。