Python 函數(shù)之二(返回值异希、作用域、LEGB绒瘦、銷毀)

一称簿、函數(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ù)制語句的問題

再看下例


示例.png

仔細觀察函數(shù) 2 返回的錯誤指向 x += 1诺舔,原因是什么呢?

x = 5
def foo():
    x += 1
foo()   # 報錯如下
示例.png

原因分析

  • 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() 的本地變量 cindex 為 0 的元素的值,而不是重新定義 c 變量
  • 第八行打印什么結(jié)果习柠?
    • 打印 1 2
  • 第十行打印什么結(jié)果匀谣?
    • 打印 3
    • 第九行的 ccounter 中的 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)用外部局部作用域中的自由變量,形成閉包

示例.png

上例是錯誤的拱绑,nonlocal 聲明變量 a 不在當前作用域综芥,但是往外就是全局作用域了,所以報錯

三猎拨、默認值的作用域

示例.png

示例.png

為何第二次調(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)融柬,printopen 都是內(nèi)置的變量

所以一個名詞的查找順序就是 LEGB


示例.png

五死嗦、函數(shù)的銷毀

  • 定義一個函數(shù)就是生成一個函數(shù)對象,函數(shù)名指向的就是函數(shù)對象
  • 可使用 del 語句刪除函數(shù)粒氧,使其引用計數(shù)減一
  • 可使用同名標識符覆蓋原有定義越除,本質(zhì)上也是使其引用計數(shù)減一
  • Python 程序結(jié)束時,所有對象銷毀
  • 函數(shù)也是對象外盯,也不例外摘盆,是否銷毀,還是看引用計數(shù)是否減為 0
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饱苟,一起剝皮案震驚了整個濱河市孩擂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌箱熬,老刑警劉巖类垦,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囤锉,死亡現(xiàn)場離奇詭異,居然都是意外死亡护锤,警方通過查閱死者的電腦和手機官地,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烙懦,“玉大人驱入,你說我怎么就攤上這事÷任觯” “怎么了亏较?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掩缓。 經(jīng)常有香客問我雪情,道長,這世上最難降的妖魔是什么你辣? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任巡通,我火速辦了婚禮,結(jié)果婚禮上舍哄,老公的妹妹穿的比我還像新娘宴凉。我一直安慰自己,他們只是感情好表悬,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布弥锄。 她就那樣靜靜地躺著,像睡著了一般蟆沫。 火紅的嫁衣襯著肌膚如雪籽暇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天饭庞,我揣著相機與錄音戒悠,去河邊找鬼。 笑死但绕,一個胖子當著我的面吹牛救崔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捏顺,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼六孵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了幅骄?” 一聲冷哼從身側(cè)響起劫窒,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拆座,沒想到半個月后主巍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冠息,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年孕索,在試婚紗的時候發(fā)現(xiàn)自己被綠了逛艰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡搞旭,死狀恐怖散怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肄渗,我是刑警寧澤镇眷,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站翎嫡,受9級特大地震影響欠动,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惑申,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一具伍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硝桩,春花似錦沿猜、人聲如沸枚荣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橄妆。三九已至衙伶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間害碾,已是汗流浹背矢劲。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留慌随,地道東北人芬沉。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像阁猜,于是被迫代替她去往敵國和親丸逸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容