函數(shù)

Python 中的函數(shù)是由若干語句組成的語句塊、函數(shù)名稱、參數(shù)列表構(gòu)成,它是組織代碼的最小單元,可以完成一定的功能

一.函數(shù)的作用

  • 結(jié)構(gòu)化編程對代碼的最基本的封裝,一般按照功能組織一段代碼
  • 封裝的目的是為了復(fù)用,減少冗余代碼
  • 代碼更加簡潔美觀,可讀易懂

二.函數(shù)的分類

  • 內(nèi)建函數(shù) : max(),reversed()
  • 庫函數(shù):math.ceil()

三.函數(shù)定義、調(diào)用

1.def關(guān)鍵字定義函數(shù)

def 函數(shù)名(形參列表):
    函數(shù)體(代碼塊)
    [return 返回值]
  • 函數(shù)名就是標(biāo)識符,只能由英文,數(shù)字和下劃線組成,不能以數(shù)字開頭.
  • python函數(shù)使用return語句返回“返回值”
  • 所有函數(shù)都有返回值,如果沒有return語句,隱式調(diào)用return None
  • return語句并不一定是函數(shù)的語句塊的最后一條語句
  • 一個函數(shù)可以存在多個return 語句,但是只有一條可以被執(zhí)行,如果沒有return語句被執(zhí)行,使用隱式調(diào)用return None
  • 如果有必要,可顯示調(diào)用return None ,可以簡寫為return
  • 如果函數(shù)執(zhí)行了return 語句,函數(shù)就會返回,當(dāng)前被執(zhí)行的return語句之后其他的語句就不會被執(zhí)行
  • return語句的作用:結(jié)束函數(shù)調(diào)用,返回值
  • 函數(shù)不能同時返回多個值
  • 函數(shù)是可調(diào)用的對象 callable()

2.函數(shù)調(diào)用

  • 函數(shù)定義,只是聲明了一個函數(shù),它是不會被執(zhí)行的,需要調(diào)用
  • 函數(shù)調(diào)用,需要在函數(shù)名后名加上()
  • 調(diào)用函數(shù)的時候在()中寫入的參數(shù)是實際參數(shù),簡稱實參。
  • 實參只有兩種傳參方式:按照形參位置順序傳參和關(guān)鍵字傳參

關(guān)鍵字傳參必須放在最后

3.函數(shù)形參

①位置參數(shù)
  • def fn(x,y) 可以使用 fn(1,2) 和fn(x=1,y=2)調(diào)用
  • 按照形參位置順序傳參,傳參的順序必須形參順序和個數(shù)一致。
  • 關(guān)鍵字傳入實參的時候,傳參時的關(guān)鍵字必須和定義的形參名和個數(shù)一致,傳參的順序可以和形參順序不一致。


    位置參數(shù).gif
② 參數(shù)默認(rèn)值(缺省值)

在定義形參的時候可以給形參賦予一個默認(rèn)值.

def fn(x=3,y=2):
    print("x={},y={}".format(x,y))
  • 參數(shù)的默認(rèn)值可以在未傳入足夠的實參的時候,對沒有給定的參數(shù)賦值為默認(rèn)值
  • 設(shè)置參數(shù)的缺省值,可以簡化函數(shù)調(diào)用
③可變位置參數(shù)
  • 在位置參數(shù)前面使用“*” 表示該形參是可變參數(shù),可以接受0到多個實參
  • 收集多個實參為一個元組tuple
  • 不接受關(guān)鍵字傳參
  • 可變位置參數(shù)必須在位置參數(shù)后面
④keyword-only參數(shù)(python3 后加入)
  • 在可變位置參數(shù)后面,出現(xiàn)的形參
  • 在傳參的時候一定要使用關(guān)鍵字傳參
⑤ 可變關(guān)鍵字參數(shù)
  • 在形參簽名使用“**”符號,表示可以接受0到多個關(guān)鍵字參數(shù)
  • 收集的實參的關(guān)鍵字和值組成一個字典dict,該字典在函數(shù)中是可以改變的
  • 如果形參中有keyword-only參數(shù),一定要在該參數(shù)后面
⑥總結(jié)
  • 形參一般順序是,位置參數(shù),缺省位置參數(shù),可變位置參數(shù),keywork-only參數(shù)(可以帶缺省值),可變關(guān)鍵字參數(shù)

函數(shù)參數(shù)解構(gòu)

  • 給函數(shù)提供實參的時候,可以在可迭代對象中使用* 或者**,把集合類型的解構(gòu)解開,提取出所有元素作為函數(shù)的實參
  • 非字典類型使用*解構(gòu)成位置參數(shù)
  • 字典類型使用 ** 解構(gòu)成關(guān)鍵字參數(shù)
  • 提出出來的元素樹木要和參數(shù)的要求匹配,也要和參數(shù)的類型匹配
  • 參數(shù)解構(gòu)只能用在函數(shù)中,print() 函數(shù)等
參數(shù)解構(gòu).gif

四. 函數(shù)嵌套

在一個函數(shù)中定義了另外一個函數(shù),內(nèi)部的函數(shù)不能在外部直接使用,會拋出NameError 異常。
函數(shù)有可見范圍,這就是作用域的概念


函數(shù)嵌套.gif

1.作用域

  • 一個標(biāo)識符的可見范圍,這就是標(biāo)識符的作用域。一般常說的是變量的作用域。
    作用域分為:全局作用域,和局部作用域
  • 全局作用域
    在整個程序運行環(huán)境中可見
  • 局部作用域
    在函數(shù),類等內(nèi)部可見,局部變量使用范圍不能超過其所在的局部作用域
def outer2():
    o = 65
    def inner():
        o = 97
        print("inner {}".format(o))
    print("outer {}".format(o))
    inner()
outer2()

#結(jié)果
#outer 65
#inner 97

通過上述嵌套結(jié)構(gòu)例子可以看出

  • 外層變量作用域在內(nèi)層作用域可見
  • 內(nèi)層作用域inner中,如果定義了o = 97,相當(dāng)于當(dāng)前作用域中重新定義了一個新的變量o,但是這個o并沒有 覆蓋外層作用域outer中的o。
①.全局變量globle
x = 5
def foo():
##如果不使用global,會出現(xiàn)先引用后賦值的異常報錯
    global x 
    x += 1 
    print("x =",x)
foo()
print("x =",x)
###結(jié)果
#x = 6
#x = 6
  • 使用global 關(guān)鍵字的變量,將函數(shù)內(nèi)的x聲明為外部的全局作用域定義的x
  • 全局作用作用域中必須要有x的定義
  • 使用global可以告訴內(nèi)部作用域,去全局作用域查找變量的定義,之后在函數(shù)內(nèi)對x的所有操作,都相當(dāng)于在為全局作用域的變量x操作賦值
x = 5
def foo():
    global x
    x += 1
    print("x =",x)
    x = 9
foo()
print("x =",x)

##結(jié)果
#x = 6
#x = 9

globle 使用原則

  • 外部作用域變量會內(nèi)部作用域可見,但也不要在這個內(nèi)部的局部作用域中直接使用,因為函數(shù)的目的就是為了封裝,盡量與外界隔離
  • 如果函數(shù)需要使用外部全局變量,請使用函數(shù)的形參傳參解決

不要用global,學(xué)習(xí)它,只是為了深入理解變量作用域?。?!

對“作用域”可以進(jìn)行如下理解:
1.在最頂層,比如shell層,有一個符號表會跟蹤記錄這一層所有的名稱定義和綁定
2.調(diào)用函數(shù)的時候,會建立一個新的符號表(常稱為棧幀)。這個表跟蹤記錄函數(shù)中所有的名稱定義(包括形參)和它們當(dāng)前的綁定。如果函數(shù)體內(nèi)又調(diào)用了一個函數(shù),就再建立一個棧幀。
3.函數(shù)結(jié)束時候,它的棧幀也隨之消息。

2.閉包

  • 自由變量:未在本地作用域定義的變量。
    例如:定義在內(nèi)層函數(shù)外的外層函數(shù)的作用域中的變量
  • 閉包:就是一個概念,出現(xiàn)在嵌套函數(shù)中,指的是內(nèi)層函數(shù)引用到了外層函數(shù)的自由變量,這就形成了閉包.
def counter():
    c = [0]
    def inc():
        c[0] += 1  #應(yīng)用的是自由變量正式counter的變量c
        return c[0]
    print(c[0])
    return inc
  
#counter()()
foo = counter()
print(foo(),foo())   #調(diào)用的是inner()
print(foo())

這是python2 實現(xiàn)閉包的方式,python3 還可以使用nonlocal 關(guān)鍵字

①nonlocal 關(guān)鍵字

使用nonlocal 關(guān)鍵字,將變量標(biāo)記為不再本地作用域定義,而在上級的某一級局部作用域中定義,但不能是全局作用域中的定義

def counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc
foo = counter()
print(foo())
print(foo())
  • count 是外層函數(shù)的局部變量,被內(nèi)部函數(shù)引用
  • 內(nèi)部函數(shù)使用nonlocal 關(guān)鍵字聲明count變量在上級作用域而非 本地作用域中定義
  • 形成了閉包
②默認(rèn)值的作用域
def foo(xyz=[]):
    xyz.append(1)
    print(xyz)
foo()
foo()

###result
#[1]
#[1, 1]
  • 函數(shù)也是對象,python 把函數(shù)的默認(rèn)值放在了屬性中,這個屬性就伴隨著這個函數(shù)對象的整個生命周期
  • 查看foo.__defaults__屬性 ([1, 1],)
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__)
  • 函數(shù)地址并沒有變,就是說函數(shù)這個對象沒有變,調(diào)用它,它的屬性__defaults__ 中使用元組保存默認(rèn)值
  • xyz 默認(rèn)值是引用類型,引用類型的元素變動,并不是元組的變化

非引用類型例子

def foo(w,u='abc',z=123):
    u = 'xyz'
    z = 789
    print(w,u,z)
print(foo.__defaults__)
foo('wing')
print(foo.__defaults__)

屬性__defaults__中使用元組保存所有位置參數(shù)默認(rèn)值,它不會因為在函數(shù)體內(nèi)使用了它而發(fā)生改變

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('wing')
print(foo.__kwdefaults__)
  • 屬性__defaults__ 中使用元組保存所有位置參數(shù)默認(rèn)值
  • 屬性__kwdefaults__ 中使用字典保存所有keyword-only 參數(shù)的默認(rèn)值
  • 使用可變類型作為形參默認(rèn)值的時候,就可能修改這個默認(rèn)值

1.函數(shù)體內(nèi),不改變默認(rèn)值

def foo(xyz=[],u='abc',z=123):
    xyz = xyz[:]     #影子拷貝
    xyz.append(1)
    print(xyz)
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])    
foo()
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
  • 函數(shù)中的xyz 都是傳入?yún)?shù)或者默認(rèn)參數(shù)的副本,如果就想修改原參數(shù),不可以

2.使用不可變類型的默認(rèn)值

def foo(xyz=None,u='abc',z=123):
    if xyz is None:
        xyz = []
    xyz.append(1)
    print(xyz)
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])    
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
  • 如果使用缺省值None就創(chuàng)建一個列表
  • 如果傳入一個列表,就修改這個列表

3.總結(jié)

  • 第一種方法
    使用影子拷貝,創(chuàng)建了一個新的對象,永遠(yuǎn)不能改變傳入的參數(shù)。
  • 第二種方法
    通過值的判斷就可以靈活的選擇創(chuàng)建或者修改傳入的參數(shù),這種方式靈活,應(yīng)用廣泛。
    很多函數(shù)的定義,都可以看到使用None這個不可變的值作為默認(rèn)參數(shù),這可以說是一種慣用法

四.變量名解析原則LEGB

  • Local,本地作用域,局部作用域的local命名空間。函數(shù)調(diào)用時創(chuàng)建,調(diào)用結(jié)束消亡
  • Enclosing,Python2.2時引入的嵌套函數(shù),實現(xiàn)了閉包,這個就是嵌套函數(shù)的外部函數(shù)的命名空間
  • Global 全局作用域,即一個模塊的命名空間。模塊被import時創(chuàng)建,解釋器退出時消亡
  • Build-in,內(nèi)置模塊的命名空間,生命周期從python解析器啟動的時創(chuàng)建到解釋器退出時消亡。例如print(open),print 和open 都是內(nèi)置的變量
  • 所以一個名詞的查找順序就是LEGB

五.函數(shù)的銷毀

全局函數(shù)銷毀

  • 重新定義同名函數(shù)
  • del語句刪除函數(shù)對象
  • 程序結(jié)束時
  • 局部函數(shù)
局部函數(shù)銷毀
  • 重新在上級作用域定義同名函數(shù)
  • del語句刪除函數(shù)名稱,函數(shù)對象的引用計數(shù)減1
  • 上級作用域銷毀時

五.文檔字符串:

""" text """
三引號之間的文本在Python中稱為文檔字符串。按照慣例,使用文檔字符串提供函數(shù)的規(guī)范??梢允褂脙?nèi)置的函數(shù)help訪問這些字符串。

文檔字符串.gif

六.遞歸

2.定義

** 函數(shù)直接或者間接調(diào)用自身就是遞歸**

  • 遞歸需要邊界條件,遞歸前進(jìn)段,遞歸返回段
  • 遞歸一定要有邊界條件
  • 當(dāng)邊界條件不滿足的時候,遞歸前進(jìn)
  • 當(dāng)邊界條件滿足的時候,遞歸返回

2.遞歸要求

  • 遞歸一定要有退出條件,遞歸調(diào)用一定要執(zhí)行到這個退出條件。沒有退出條件的遞歸調(diào)用,就是無限調(diào)用
  • 遞歸深度不宜過深
    可以通過sys.getrecursionlimit() 查看解釋器的深度限制

3.遞歸的性能

  • 循環(huán)稍微復(fù)雜一些,但是只要不是死循環(huán),可以多次迭代直至算出結(jié)果。
  • 遞歸有深度限制,如果遞歸復(fù)雜,函數(shù)反復(fù)亞棧,棧內(nèi)存很快就溢出了。
  • 遞歸中可以通過形參記錄每次遞歸的計算,減少遞歸次數(shù)

4.間接遞歸:

def foo1():
    foo2()
def foo2():
    foo1()
foo1()

間接遞歸,是通過別的函數(shù)調(diào)用了函數(shù)自身。
但是,如果構(gòu)成了循環(huán)遞歸抵用是非常危險的,但是往往這種情況在代碼復(fù)雜的情況下,還是可能發(fā)生這種調(diào)用。要用代碼的規(guī)范來避免這種遞歸調(diào)用的發(fā)生。

5.遞歸總結(jié)

  • 遞歸是一種很自然的表達(dá),符合邏輯思維
  • 遞歸相對運行效率低,每一次調(diào)用函數(shù)都要開辟棧幀
  • 遞歸有深度限制,如果遞歸層次太深,函數(shù)反復(fù)壓棧,棧內(nèi)存很快就溢出了
  • 如果是有限次數(shù)的遞歸,可以使用遞歸調(diào)用,或者使用循環(huán)代替,循環(huán)代碼稍微復(fù)雜一點,但是只要不是死循環(huán),可以多次迭代直至算出結(jié)果
  • 絕大多數(shù)遞歸,都可以使用循環(huán)實現(xiàn)
  • 即使遞歸代碼間接,不建議使用遞歸

七.匿名函數(shù)

  • 沒有函數(shù)名
  • 借助lambda 表達(dá)式構(gòu)建匿名函數(shù)
  • 格式:lambda 參數(shù)列表:表達(dá)式
  • 使用lambda 關(guān)鍵字來定義匿名函數(shù)
  • 參數(shù)列表不需要小括號
  • 冒號是用來分割參數(shù)列表和表達(dá)式的
  • 不需要使用return ,表達(dá)式的值,就是匿名函數(shù)返回值
  • lambda表達(dá)式(匿名函數(shù)) 只能寫在一行上,被稱為單行函數(shù)
  • 用途:在高階函數(shù)傳參時,使用lamdba表達(dá)式,往往能簡化代碼。
print((lambda :0)())
print((lambda x,y=3:x+y)(5))
print((lambda x, y=3: x + y)(5, 6))
print((lambda x, *, y=30: x + y)(5))
print((lambda x, *, y=30: x + y)(5, y=10))
print((lambda *args: (x for x in args))(*range(5)))
print((lambda *args: [x+1 for x in args])(*range(5)))
print((lambda *args: {x+2 for x in args})(*range(5)))
[x for x in (lambda *args: map(lambda x: x+1, args))(*range(5))] 
[x for x in (lambda *args: map(lambda x: (x+1,args), args))(*range(5))]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 函數(shù)和對象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對于任何一門語言來說都是核心的概念。通過函數(shù)可以封裝任意多條語句,而且...
    道無虛閱讀 4,926評論 0 5
  • 第5章 函數(shù)和函數(shù)式編程 5.1 引言函數(shù)是組織好的,可重復(fù)使用的,用來實現(xiàn)單一,或相關(guān)聯(lián)功能的代碼段。函數(shù)...
    VIVAFT閱讀 1,072評論 0 5
  • 函數(shù)只定義一次,但可能被執(zhí)行或調(diào)用任意次。JS函數(shù)是參數(shù)化的,函數(shù)的定義會包括一個稱為形參的標(biāo)識符列表,這些參數(shù)在...
    PySong閱讀 368評論 0 0
  • 周五大盤再現(xiàn)普跌格局。下跌個股2448只,上漲個股僅有751只,前期還領(lǐng)漲的板塊,如鄉(xiāng)村振興、通信網(wǎng)絡(luò)、券商、保險...
    d719f6b05f08閱讀 255評論 0 0
  • 媽媽是溫暖的港灣,她敞開的懷抱和接納的雙臂是孩子安全感最重要的來源! 爸爸是力量的源泉,他與孩子的連接和支持是建構(gòu)...
    劉瀘閱讀 2,040評論 0 1

友情鏈接更多精彩內(nèi)容