Python 函數返回值、作用域


函數返回值

多條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__)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM