函數返回值
多條return語句:
def guess(x): if x > 3: return "> 3" else: return "<= 3"
def showplus(x): print(x) return x + 1 return x + 2 #執行結果 10 11 #從結果來看 出現第一個return后 下面的就不會執行
def fn(x): for i in range(x): if i > 3: return i else: print("{} is not greater than 3".format(x)) #fn(5) 執行結構是4 #fn(3) 3 is not greater than 3 #可以看出,我們可以通過條件控制語句來控制return
總結:
- python函數使用return語句返回“返回值”
- 所有函數都有返回值,如果沒有return語句,隱式調用return None
- return 語句並不一定是函數的語句塊的最后一條
- 一個函數可以存在多個return語句,但只有一條可以被執行,如果沒有一條return語句被執行,隱式調用return None
- 如果有必要,可以顯示調用return None,可以簡寫return
- 如果函數執行了return語句,函數就會返回,當前被執行的return語句之后的其他語句就不會被執行了
- 作用:結束函數調用、返回值
返回多個值:
- 函數不能同時返回多個值
- return[1,3,5] 是指明返回一個列表,是一個列表對象
- return 1,3,5 看似返回多個值,隱式的被python封裝成了一個元組
可以使用解構來提前
def showlist(): return 1, 3, 5 x, y, z = showlist()
函數嵌套
def outer(): def inner(): print("inner") print("outer") inner() outer() #inner() #報錯,inner沒定義。說有函數是有作用域的
- 函數有可見范圍,這就是作用域的概念
- 內部函數不能在外部直接使用,會拋NameError異常,因為它不可見
作用域
- 一個標識符的可見范圍,這就是標識符的作用域。一般常說的是變量的作用域
舉例,對比案例1和案例2
#案例1 x=5 def foo(): print(x) foo() #有執行結構,復用了全局的變量x #案例2 x=5 def foo(): x+=1 print(x) foo() #報錯,x+=1 既是x= x+1 ,這里x被重新賦值了,所有x = x+1 既這里x是沒有值的
全局作用域:
- 在整個程序運行環境中都可見
局部作用域:
- 在函數、類等內部可見
- 局部變量使用范圍不能超過其所在的局部作用域
def fn1(): x = 1 #局部作用域,在fn1內 def fn2(): print(x) #不可見 print(x) #不可見
嵌套結構作用域例子:
#例子1、 def outer1(): # o = 65 def inner(): print("inner {}".format(o)) print(chr(o)) print("outer {}".format(o)) inner() outer1() #例子2、 def outer2(): # o = 65 def inner(): o = 97 print("inner {}".format(o)) print(chr(o)) print("outer {}".format(o)) inner() outer2()
從例子中看出:
- 外層變量作用域在內層作用域可見
- 內層作用域inner中,如果定義了o=97,相當於當前作用域中重新定義了一個新的變量o,但是這個o並沒有覆蓋外層作用域outer中的o
x= 6 def foo(): x += 1 foo()
報錯:
- x += 1 等價於 x = x +1
- 相當於在foo內部定義一個局部變量x,那么foo內部所有x都是這個局部變量x了
- 但是x還沒有完成賦值,就被右邊拿來做加1的操作
解決這個問題 全局變量global 、nonlocal 來解決
全局變量global
案例
#x= 6 def foo(): global x x = 10 x += 1 #不會報錯了, print(x) #打印11 print(x) #報錯,找不到b這個標識符
- 使用global關鍵字變量,將foo內的x聲明為使用外部的全局作用域中定義的x
- 但是,x = 10 賦值既定義,在內部作用域為一個外部作用域的變量x賦值,不是在內部作用域定義個新變量,所有x +=1 不會報錯,注意:這里x的作用域還是全局的
global總結:
- x = 1 這種是特殊形式產生的錯誤原因?先引用后賦值,而python動態語言是賦值才算定義,才能被引用。解決辦法,在這條語句前增加x=0 之類的賦值語句,或者使用global告訴內部作用域,去全局作用域查找變量定義
- 內部作用域使用x=5之類的賦值語句會重新定義局部作用域中使用的變量x,但是,一旦這個作用域中使用global聲明x為全局的,那么x=5相當於在為全局作用域的變量x賦值
global使用原則:
- 外部作用域變量會內部作用域可見,但也不要在這個內部的局部作用域中直接使用,因為函數的目的就是為了封裝,盡量與外界隔離
- 如果函數需要使用外部全局變量,請使用函數的形參傳參解決
- 一句話:不用global。學習它就是為了深入理解變量作用域
閉包
- 自由變量:未在本地作用域中定義的變量。例如定義在內存函數外的外層函數的作用域中的變量
- 閉包:就是一個概念,出現在嵌套函數中,指的是內層函數引用了外層函數的自由變量,就形成了閉包。
案例:
def counter(): c = [0] def inc(): c[0] += 1 #會報錯嗎? return c[0] return inc foo = counter() print(foo(),foo()) #打印結果? c = 100 print(foo()) #打印結果?
代碼解析:
- 不會報錯,c已經在counter函數中定義過了,而且inc中的使用方式是為c的元素修改值,而不是重新定義變量
- 第一個的打印結果是 1, 2
- 最后一行的打印結果是 3
- 倒數第二行的c和counter中的c不一樣,而inc引用的是自由變量正式counter的變量c
- 這是python2中實現閉包的方式,python3還可以使用nonlocal關鍵字
nonlocal關鍵字
使用了nonlocal關鍵字,將變量標記為不在本地作用域定義,而在上級的某一級局部作用域中定義,但不能是全局作用域中定義
例子:
def counter1(): c = 0 def inc(): nonlocal c c += 1 return c return inc foo = counter() foo() foo()
- c 是外層函數的局部變量,被內部函數引用
- 內部函數使用nonlocal關鍵字聲明c變量在上級作用域而非本地作用域中定義
默認值作用域
案例
#案例1
def foo(xyz=1): print(xyz) foo() # 打印 1 foo() # 打印 1 print(xyz) # 報錯 NameError 當前作用域沒有xyz變量 #案例2 def foo(xyz=[]): xyz.append(1) print(xyz) foo() # [1] foo() # [1,1]
案例2中為什么第二個foo()會打印2個1
- 因為函數也是對象,python把函數的默認值放在了屬性中,這個屬性就伴隨着這個函數對象整個生命周期
- 查看foo.__defaults__
運行下面案例
def foo(xyz=[], u='abc', z=123): xyz.append(1) return xyz print(foo(), id(foo)) print(foo.__defaults__) print(foo(), id(foo)) print(foo.__defaults__) #執行結果 [1] 139927086673704 ([1], 'abc', 123) [1, 1] 139927086673704 ([1, 1], 'abc', 123)
總結:
- 函數地址並沒有變,就是說函數這個對象的沒有變,調用它,它的屬性__defaults__中使用元組保存默認值
- xyz默認值是引用類型,引用類型的元素變動,並不是元組的變化
運行下面案例:
def foo(w, u='abc', *, z=123, zz=[456]): u = 'xyz' z = 789 zz.append(1) print(w, u, z, zz) print(foo.__defaults__) foo('magedu') print(foo.__kwdefaults__) 運行結果 ('abc',) magedu xyz 789 [456, 1] {'z': 123, 'zz': [456, 1]}
- 屬性__defaults__中使用元組保存所有位置參數默認值
- 屬性__kwdefaults__中使用字典保存所有keyword-only參數的默認值
按需修改的案例:
例如案例中列表增加的問題,如果我不想讓他增加呢?
def foo(xyz=[], u='abc', z=123): xyz = xyz[:] # 影子拷貝 xyz.append(1) print(xyz) foo() print(foo.__defaults__) foo() print(foo.__defaults__) foo([10]) print(foo.__defaults__) foo([10,5]) print(foo.__defaults__)
- 函數體內,不改默認值
- xyz都是傳入參數或者默認值參數的副本,如果就想修改原參數,無能為力
第二種方法:
def foo(xyz=None, u='abc', z=123): if xyz is None: xyz = [] xyz.append(1) print(xyz) foo() print(foo.__defaults__) foo() print(foo.__defaults__) foo([10]) print(foo.__defaults__) foo([10,5]) print(foo.__defaults__)
- 使用不可變類型默認值
- 如果使用缺省值None就創建一個列表
- 如果傳入一個列表,就地修改這個列表
第一種方法
- 使用影子拷貝創建一個新的對象,永遠不能改變傳入的參數
第二種方法
- 通過值的判斷就可以靈活的選擇創建或者修改傳入對象
- 這種方法靈活,應用廣泛
- 很多函數定義,都可以看到使用None這個不可變的值作為默認參數,可以說這是一種慣用法
變量名解析原則LEGB
- Local,本地作用域、局部作用域的local命名空間。函數調用時創建、調用結束消亡
- Enclosing,Python2.2時引入嵌套函數,實現了閉包,這個就是嵌套函數的外部函數的命名空間
- Global,全局作用域,既一個模塊的命名空間,模塊被import時創建,解釋器退出時消亡
- Build-in,內置模塊的命名空間,生命周期從python解釋器啟動時創建到解釋器退出時消亡。例如:print(open),print和open都是內置變量
- 所以查找順序是LEGB
函數銷毀
全局函數銷毀
- 重新定義同名函數
- del語句刪除函數對象
- 程序退出時
def foo(xyz=[], u='abc', z=123): xyz.append(1) return xyz print(foo(), id(foo), foo.__defaults__) def foo(xyz=[], u='abc', z=123): xyz.append(1) return xyz print(foo(), id(foo), foo.__defaults__) del foo print(foo(), id(foo), foo.__defaults__)
局部函數銷毀
- 重新在上級作用域定義同名函數
- del語句刪除函數名稱,函數對象的引用計數減1
- 上級作用域銷毀時
def foo(xyz=[], u='abc', z=123): xyz.append(1) def inner(a=10): pass print(inner) def inner(a=100): print(xyz) print(inner) return inner bar = foo() print(id(foo),id(bar), foo.__defaults__, bar.__defaults__) del bar print(id(foo),id(bar), foo.__defaults__, bar.__defaults__)