函數的返回值和作用域
1、返回值
def guess(x):
if x > 3:
return "> 3"
else:
return "<= 3"
print(guess(10))
1> Python 函數使用 return 語句返回 "返回值”
2> 所有函數都有返回值,如果沒有 return 語句,隱式調用 return None
3> return 語句並不一定是函數的語句塊的最后一條語句
4> 一個函數可以存在多個 return 語句,但是只有一條可以被執行。如果沒有一條 return 語句被執行到,隱式調用 return None
5> 如果有必要,可以顯示調用return None,可以簡寫為return
6> 如果函數執行了 return 語句,函數就會返回,當前被執行的 return 語句之后的其它語句就不會被執行了
7> 返回值的作用:結束函數調用、返回 "返回值”
2、能一次返回多個值嘛?
def showvalues(): return 1, 3, 5 print(showvalues()) # 返回 (1, 3, 5)
函數不能同時返回多個值
return 1, 3, 5 看似返回多個值,隱式的被 python 封裝成了一個元組
x, y, z = showvalues() 使用解構提取返回值更為方便
3、函數作用域**
3.1 作用域
一個標識符的可見范圍,這就是標識符的作用域,一般常說變量的作用域。
x = 20 def fn(): x = 100 # x 的作用域:當前函數 fn() print(x) # x = 20
注意:每一個函數都會開辟一個作用域。
3.2 作用域分類
全局作用域:
在整個程序運行環境中都可見
全局作用域中的變量稱為全局變量
局部作用域:
在函數、類等內部可見
局部作用域中的變量稱為局部變量,其使用范圍不超過其所在局部作用域
# 局部變量 def fn1(): x = 1 # 局部作用域,x 為局部變量,使用范圍在 fn1 內 def fn2(): print(x) # x 能打印嗎?不能 print(x) # x 能打印嗎?不能
# 全局變量 x = 5 # 全局變量,也在函數外定義 def foo(): print(x) # 可見嗎?可以 foo()
一般來講外部作用域變量在函數內部可見,可以使用
反過來,函數內部的局部變量,不能在函數外部看到
4、函數嵌套
在一個函數中定義另一個函數
def outer(): def inner(): print('inner') print('outer') inner() outer() inner() # 不可以
內部函數 inner 不能在外部直接使用,會拋出 NameError 異常,因為它在函數外部不可見。
其實,inner 不過就是一個標識符,就是一個函數 outer 內部定義的變量而已。
5、嵌套函數的作用域
def outer(): o = 65 # 局部變量、本地 local 變量、臨時變量 def inner(): o = 97 print('inner', o) print('outer 1 ', o) inner() print('outer 2 ', o) outer() # 1:outer 1 65 2:inner 97 3:outer 2 65
外層變量在內部作用域可見。
內層作用域中,如果定義了和外層相同的變量,相當於在當前函數作用域中重新定義了一個新的變量,這個內層變量並不能覆蓋掉外部作用域中的變量。
6、一個賦值語句的問題
x = 100 def fn(): y = x + 200 print(y) fn()
x = 100 def fn(): x += 1 # 報錯! 賦值即定義,即 x = x + 1 (局部變量 = 局部變量 + 1)! print(x) fn()
x = 100 def fn(): print(x) # 報錯!該步執行不了! x += 1 # 只要在該作用域內賦值定義('=')局部變量,在該作用域內的所有該變量都為局部變量! print(x) fn()
能否解決呢?可以,使用 global 語句
x = 100 def fn(): global x # 聲明全局變量 print(x) # 100 x += 1 print(x) # 101 fn() print(x) # 101
注意:全局變量一般情況不推薦修改,一旦在作用域中使用 global 聲明全局變量,那么相當於在對全局變量賦值、定義。
global 使用原則:
1> 外部作用域變量會在內部作用域可見,但也不要在這個內部的局部作用域中直接使用,因為函數的目的就是為了封裝,盡量與外界隔離。
2> 如果函數需要使用外部全局變量,請盡量使用函數的形參定義,並在調用傳實參解決。
3> 一句話:不用 global,學習它就是為了深入理解變量作用域。
# 不建議直接傳入全局變量! y = [] def foo(): # x 就是標識符,就是變量,就是本地變量 y.append(1) foo() foo() print(y)
# 建議使用傳參的方式,在函數內使用全局變量 y = [] def foo(x): # x 就是標識符,就是變量,就是本地變量 x.append(1) foo(y) foo(y) print(y)
7、閉包**
自由變量:未在本地作用域中定義的變量。例如定義在內層函數外的外層函數的作用域中的變量。
閉包:就是一個概念,出現在嵌套函數中,指的是內層函數引用到了外層函數的自由變量,就形成了閉包。很多語言都有這個概念,最熟悉就是 JavaScript。
# python 2 實現閉包 def counter(): c = [0] def inc(): c[0] += 1 # 是賦值即定義嘛?不是!是修改值 return c[0] return inc # 返回標識符,即函數對象 m = counter() m() # 調用函數 inc(),但是 c 消亡了嘛?沒有,內層函數沒有消亡,c 不消亡(閉包) m() m() print(m())
# 不推薦使用 global ! def counter(): global c c = 0 def inc(): global c c += 1 # 不是閉包! return c return inc m = counter() m() m() m() print(m())
# 推薦使用 nonlocal,python 3 實現閉包 def counter(): c = 0 def inc(): nonlocal c # 非當前函數的本地變量,當前函數之外的任意層函數的變量,絕非 global c += 1 # 是閉包嗎?是! return c return inc m = counter() m() m() m() print(m())
nonlocal 語句:將變量標記為不在本地作用域定義,而是在上級的某一級局部作用域中定義,但不能是全局作用域中。
8、默認值的作用域
def foo(x=1): x += 1 print(x) foo() # 2 foo() # 2 def bar(x=[]): # x = [],引用類型 x.append(1) # [1] print(x) bar() # [1] bar() # [1, 1]
為什么上列 bar 函數第二次調用打印的是 [1, 1]?
因為函數也是對象,每個函數定義被執行后,就生成了一個函數對象和函數名這個標識符關聯。
python 把函數的默認值放在了函數對象的屬性中,這個屬性就伴隨着這個函數對象的整個生命周期。
# 查看 foo.__defaults__ 屬性,它是個元組 def bar(x=[]): x.append(1) print(x) print(bar.__defaults__) bar() # [1] print(bar.__defaults__) bar() # [1, 1] print(bar.__defaults__) # 執行結果: ([],) [1] ([1],) [1, 1] ([1, 1],) # 元組不變,記錄的是地址,引用類型變化
def foo(x, m=123, n='abc'): m=456 n='def' print(x) print(foo.__defaults__) # (123, 'abc') foo('yang') print(foo.__defaults__) # (123, 'abc')
def foo(x, m=123, *, n='abc', t=[1,2]): m=456 n='def' t.append(12) #t[:].append(12) # t[:],全新復制一個列表,避免引用計數 print(x, m, n, t) print(foo.__defaults__, foo.__kwdefaults__) #(123,) {'n': 'abc', 't': [1, 2]} foo('yang') print(foo.__defaults__, foo.__kwdefaults__) #(123,) {'n': 'abc', 't': [1, 2, 12]}
def x(a=[]): a = a + [5] # 加法的本質:返回新列表、新地址;賦值即定義 print(x.__defaults__) # ([],) x() x() print(x.__defaults__) # ([],) def y(a=[]): a += [5] # += 即 extend => a.extend([5]) print(y.__defaults__) # ([],) y() y() print(y.__defaults__) # ([5, 5],) # 列表的 + 和 += 的區別: # + 表示兩個列表合並並返回一個全新的列表。 # += 表示,就地修改前一個列表,在其后追加后一個列表,就是 extend 方法。 # 例: l1 = [1, 2] l2 = [3, 4] l3 = l1 + l2 print(id(l1), id(l2), id(l3)) l3 += l2 # 就地修改 print(id(l3)) 執行結果: 2279905055304 2279905055816 2279906334216 2279906334216
9、變量名解析原則 LEGB**
Local,本地作用域、局部作用域的 local 命名空間,函數調用時創建,調用結束消亡。
Enclosing,Python2.2 時引入了 嵌套函數,實現了閉包,這個就是嵌套函數的外部函數的命名空間。
Global,全局作用域,即一個模塊的命名空間。模塊被 import 時創建, 解釋器退出時消亡。
Build-in,內置模塊的命名空間,生命周期從 python 解釋器啟動時創建到解釋器退出時消亡。例如 print(open),print 和 open 都是內置的變量。
所以一個名詞的查找順序就是 LEGB
10、函數的銷毀
定義一個函數就是生成一個函數對象,函數名指向的就是函數對象。
可以使用del語句刪除函數,使其引用計數減 1。
可以使用同名標識符覆蓋原有定義,本質上也是使其引用計數減 1。
Python程序結束時,所有對象銷毀。
函數也是對象,也不例外,是否銷毀,還是看引用計數是否減為 0。