1. 程式人生 > 實用技巧 >Python函式的基本使用

Python函式的基本使用

一、函式的定義

函式的基本使用:
    什麼是函式:
        在程式中,具備某一功能的‘工具’指的就是函式,‘事先準備工具’的過程即函式的定義,‘拿來就用’即函式的呼叫。

    為什麼要用函式:
        1、組織結構不清晰,可讀性差
        2、程式碼冗餘
        3、可維護性、擴充套件性差

    函式的定義:
        函式的使用必須遵循’先定義,後呼叫’的原則。
        函式的定義就相當於事先將函式體程式碼儲存起來,然後將記憶體地址賦值給函式名,函式名就是對這段程式碼的引用,這和變數的定義是相似的。沒有事先定義函式而直接呼叫,就相當於在引用一個不存在的’變數名’。
            
def 函式名(引數1,引數2,...): """文件描述""" 函式體 return1、def: 定義函式的關鍵字; 2、函式名:函式名指向函式記憶體地址,是對函式體程式碼的引用。函式的命名應該反映出函式的功能; 3、括號:括號內定義引數,引數是可有可無的,且無需指定引數的型別; 4、冒號:括號後要加冒號,然後在下一行開始縮排編寫函式體的程式碼; 5、"""文件描述"""
:描述函式功能,引數介紹等資訊的文件,非必要,但是建議加上,增強函式的可讀性; 6、函式體:由語句和表示式組成; 7、return 值:定義函式的返回值,return是可有可無的。 定義函式的三種方式: 無參函式: 在函式體內沒有需要傳入的引數 def func(): pass 有參函式: 在函式體內有需要傳入的引數
def func(x,y): print(x,y) 空函式: 函式體程式碼為pass def func(x, y): pass 定義函式的過程: 1、申請記憶體空間儲存函式體程式碼 2、將上述記憶體地址繫結函式名 3、定義函式不會執行函式體程式碼,但是會檢測函式體語法 呼叫函式的過程: 1、通過函式名找到函式的記憶體地址 2、然後加口號就是在觸發函式體程式碼的執行 呼叫函式的三種方式: 1、語句的形式: 直接加括號呼叫即可; foo() 2、表示式的形式: m=func(1,2) #將呼叫函式的返回值賦值給x n=10*func(1,2) #將呼叫函式的返回值乘以10的結果賦值給 3、函式呼叫可以當做引數 res=add(add(1,2),10) #將函式作為引數傳入 print(res) 函式的返回值: return是函式結束的標誌,即函式體程式碼一旦執行到return會立刻終止函式的執行,並且會將return後的值當做本次執行的結果返回,一般情況下返回值有三種情況: 1、返回None 表示函式體內沒有return或者定義return為空 2、返回一個值 return3、返回多個值: 用逗號分隔開多個值,會被return返回成元組

二、函式的引數

函式的引數:
    函式的引數分為形式引數和實際引數,簡稱形參和實參
        形參即在定義函式時,括號內宣告的引數。形參本質就是一個變數名,用來接收外部傳來的值;
        實參即在呼叫函式時,括號內傳入的值,實參值可以是常量、變數、表示式或三者的組合;

    形參與實參的關係:
        1、在呼叫階段,實參(變數值)會繫結給形參(變數名
        2、這種繫結關係只能在函式體內使用
        3、實參與形參的繫結關係在函式呼叫時生效,函式呼叫結束後解除繫結關係

    位置引數:
        按照從左到右的順序依次定義的引數稱之為位置引數
            def register(name,age,sex):                       #定義位置形參:name,age,sex,三者都必須被傳值
                print('Name:%s Age:%s Sex:%s' %(name,age,sex))
            register()                          #若不傳值,則會丟擲異常TypeError:缺少3個位置引數 
        在呼叫函式時,按照從左到右的順序依次定義實參,稱為位置實參,凡是按照這種形式定義的實參會按照從左到右的順序與形參一一對應

    關鍵字引數:
        在函式呼叫階段,按照key=value的形式傳入的值,凡是按照這種形式定義的實參,可以完全不按照從左到右的順序定義,但仍能為指定的形參賦值
            register(sex='male',name='lili',age=18)
                # Name:lili  Age:18  Sex:male
        需要注意在呼叫函式時,實參也可以是按位置或按關鍵字的混合使用,但必須保證關鍵字引數在位置引數後面,且不可以對一個形參重複賦值

    預設引數:
        在定義函式階段,就已經被賦值的形參,稱之為預設引數,預設引數可以在呼叫階段不為其賦值
            def register(name,age,sex='male'):     #預設sex的值為male
                print('Name:%s Age:%s Sex:%s' %(name,age,sex))
        雖然預設引數在定義階段就已經為形參賦值,但是若在呼叫階段想修改的情況下,只需重新賦值即可
        雖然預設值可以被指定為任意資料型別,但是不推薦使用可變型別,通常應設為不可變型別
        預設引數必須在位置引數之後
        預設引數的值僅在函式定義階段被賦值一次
            x = 1
            def foo(args=x):
                print(args)
            x = 2         #定義階段args已被賦值為1,此處的修改與預設引數args無任何關係
            foo()
        函式最理想的狀態:函式的呼叫只跟函式本身有關係,不被外界程式碼的影響

    可變長度的引數(*與**的用法)
        可變長度指的是在呼叫函式時,傳入的值(實參)的個數不固定
        而實參是用來為形參賦值的,所以對應著,針對溢位的實參必須有對應的形參來接收
        可變長度的位置引數:
            *形參名:用來接收溢位的位置實參,溢位的位置實參會被*儲存成元組的格式然後賦值緊跟其後的形參名
            *後跟的可以是任意名字,但是約定俗成應該是args
            *可以用在實參中,實參中帶*,先把*後的值打散成位置實參
            如果在傳入多個值的時候沒有加*,那就只是一個普通的位置引數
            如果形參為常規的引數(位置或預設),實參仍可以是*的形式

        可變長度的關鍵字引數
            **形參名:用來接收溢位的關鍵字實參,**會將溢位的關鍵字實參儲存成字典格式,然後賦值給緊跟其後的形參名
            **後跟的可以是任意名字,但是約定俗成應該是kwargs
            **可以用在實參中(**後跟的只能是字典),實參中帶**,先把**後的值打散成關鍵字實參
            如果在傳入多個值的時候沒有加**,那就只是一個普通的位置引數
            如果形參為常規引數(位置或預設),實參仍可以是**的形式

    命名關鍵字引數:
        在定義函式時,*後定義的引數,稱之為命名關鍵字引數
        命名關鍵字實參必須按照key=value的形式為其傳值
            def func(x,y,*,a,b):        # 其中,a和b稱之為命名關鍵字引數
                print(x,y)
                print(a,b)
        此外,如果形參中已經有一個args了,命名關鍵字引數就不再需要一個單獨的*作為分隔符號了

    組合使用:
        綜上所述所有引數可任意組合使用,但定義順序必須是:位置引數、預設引數、args、命名關鍵字引數、*kwargs
        可變引數*args與關鍵字引數kwargs通常是組合在一起使用的,如果一個函式的形參為*args與kwargs,那麼代表該函式可以接收任何形式、任意長度的引數
            >>> def wrapper(*args,**kwargs):
            ...     pass
        注意:*args、**kwargs中的args和kwargs被替換成其他名字並無語法錯誤,但使用args、kwargs是約定俗成的。

三、名稱空間與作用域

名稱空間與作用域:
    什麼是名稱空間:
        名稱空間即存放名字與物件對映/繫結關係的地方,是對棧區的劃分
        有了名稱空間之後,就可以在棧區中存放相同的名字,詳細的,名稱空間

    名稱空間的分類:
        內建名稱空間:
            伴隨python直譯器的啟動/關閉而產生/回收,因而是第一個被載入的名稱空間,用來存放一些內建的名字,比如內建函式名
            存活週期:python直譯器啟動則產生,python直譯器關閉則銷燬

        全域性名稱空間:
            伴隨python檔案的開始執行/執行完畢而產生/回收,是第二個被載入的名稱空間,檔案執行過程中產生的名字都會存放於該名稱空間中
            存活週期:python檔案執行則產生,python檔案執行完畢後銷燬

        區域性名稱空間:
            伴隨函式的呼叫/結束而臨時產生/回收,函式的形參、函式內定義的名字都會被存放於該名稱空間中
            存活週期:在呼叫函式時存活,函式呼叫完畢後則銷燬

        名稱空間的載入順序:
            內建名稱空間->全域性名稱空間->區域性名稱空間
        名稱空間的查詢順序:
            區域性名稱空間->全域性名稱空間->內建名稱空間
        名稱空間的"巢狀"關係是以函式定義階段為準,與呼叫位置無關

    什麼是作用域:
        作用域指的是生效的作用範圍
    作用域的分類:
        全域性作用域:內建名稱空間、全域性名稱空間
            位於全域性名稱空間、內建名稱空間中的名字屬於全域性範圍,該範圍內的名字全域性存活(除非被刪除,否則在整個檔案執行過程中存活)、全域性有效(在任意位置都可以使用)
        區域性作用域:區域性名稱空間的名字
            位於區域性名稱空間中的名字屬於區域性範圍。該範圍內的名字臨時存活(即在函式呼叫時臨時生成,函式呼叫結束後就釋放)、區域性有效(只能在函式內使用)

    作用域與名字查詢的優先順序:
        區域性作用域:
            先查詢區域性作用域,沒有再查詢全域性作用域
                x=100 #全域性作用域的名字x
                def foo():
                    x=300 #區域性作用域的名字x
                    print(x) #在區域性找x
                foo()#結果為300

        全域性作用域:
            先查詢全域性名稱空間,沒有找到,再查詢內建名稱空間,再沒有找到,則丟擲異常
                x=100
                def foo():
                    x=300 #在函式呼叫時產生區域性作用域的名字x
                foo()
                print(x) #在全域性找x,結果為100
        注:
            可以呼叫內建函式locals()和globals()來分別檢視區域性作用域和全域性作用域的名字,檢視的結果都是字典格式。在全域性作用域檢視到的locals()的結果等於globals()

        Python支援函式的巢狀定義,在內嵌的函式內查詢名字時,會優先查詢自己區域性作用域的名字,然後由內而外一層層查詢外部巢狀函式定義的作用域,沒有找到,則查詢全域性作用域

        在函式內,無論巢狀多少層,都可以檢視到全域性作用域的名字,若要在函式內修改全域性名稱空間中名字的值,當值為不可變型別時,則需要用到global關鍵字;若實參的值為可變型別時,函式體內對該值的修改將直接反應到原值,

            x=1
            def foo():
                global x #宣告x為全域性名稱空間的名字
                x=2
            foo()
            print(x) #結果為2

        對於巢狀多層的函式,使用nonlocal關鍵字可以將名字宣告為來自外部巢狀函式定義的作用域(非全域性)
            def  f1():
                x=2
                def f2():
                    nonlocal x
                    x=3
                f2() #呼叫f2(),修改f1作用域中名字x的值
                print(x) #在f1作用域檢視x

            f1()    #結果為3
        nonlocal x會從當前函式的外層函式開始一層層去查詢名字x,若是一直到最外層函式都找不到,則會丟擲異常。

四、全域性變數和區域性變數

#區域性變數:函式內部的變數是區域性變數,作用域僅在函式內部可見(區域性名稱空間)
#全域性變數:函式外部的變數是全域性變數,作用域橫跨整個檔案(全域性名稱空間)
#內建函式:內建名稱空間

-- globals() :返回字典,存放著全域性作用域所有內容
-- locals()  :返回字典,當前作用域所有內容(locals呼叫之前的變數)
-- global    :關鍵字:宣告全域性變數獲修改全域性變數
-- nonlocal  :關鍵字:修改區域性變數(當前函式上一層的區域性變數)

五、函式物件和閉包

函式物件和閉包:
    函式物件:
        函式物件指的是函式可以被當做’資料’來處理
        即函式可以被引用,可以作為容器型別的元素,可以作為容器型別的元素,可以作為引數傳入另一個函式,也可以作為另一個函式的返回值
    函式巢狀:
        在一個函式內定義其它的函式
    閉包函式:
        閉包函式=名稱空間與作用域+函式巢狀+函式物件
        核心點:名字的查詢關係是以函式定義階段為準
        什麼是閉包函式:
            ""函式指的該函式是內嵌函式
            ""函式指的該函式包含對外層函式作用域名字的引用(不是對全域性作用域)
            “閉”代表函式是內部的,“包”代表函式外’包裹’著對外層作用域的引用。因而無論在何處呼叫閉包函式,使用的仍然是包裹在其外層的變數。
                x=1
                def outer():
                    x=2
                    def inner():
                        print(x)
                    return inner

                func=outer()
                func() # 結果為2

        可以通過函式的closure屬性,檢視到閉包函式所包裹的外部變數
            >>> func.__closure__
            (<cell at 0x10212af78: int object at 0x10028cca0>,)
            >>> func.__closure__[0].cell_contents
            2
        閉包函式的用途:
            兩種為函式體傳值的方法:
                一、直接將函式值以引數的形式傳入
                    def get(url):
                        return requests.get(url).text

                二、利用閉包函式,將值包給函式
                    def page(url):
                        def get():
                            return requests.get(url).text
                        return get
            閉包函式的這種特性有時又稱為惰性計算。使用將值包給函式的方式,在接下來的裝飾器中也將大有用處