豬行天下之Python基礎——5.1 函式(上)

coder-pig發表於2019-04-06

內容簡述:

  • 1、函式定義
  • 2、形參與實參
  • 3、關鍵字引數與預設引數
  • 4、可變引數
  • 5、全域性變數與區域性變數
  • 6、內部函式
  • 7、閉包
  • 8、lambda表示式
  • 9、遞迴

1、函式定義

我們可以將一些實現特定功能重複使用到的「程式碼片段」抽取出來,封裝成一個函式。比如求兩個數和的函式:

def plus(a, b):
    """
    計算兩個數的和
    :param a: 第一個引數
    :param b: 第二個引數
    :return: 兩個引數的和
    """

    return a + b

if __name__ == '__main__':
    print("1 + 2 = %d" % plus(12))
複製程式碼

執行結果如下

1 + 2 = 3
複製程式碼

從上面的求和函式可以初窺函式定義的一些端倪,接著具體說下規則:

  • 函式定義格式def 函式名(傳入引數)
  • 使用 `return` 返回值,不寫的話預設返回 None 值。
  • Python函式的 返回值可以有多個 ,本質上利用的元組
  • Python傳遞的 引數有多個 的話,可以用逗號隔開
  • 一個建議:函式定義時,可在函式的第一行語句中選擇性地使用文件字串編寫函式說明,除了方便閱讀,使用help(函式名)也可以拿到這個函式的說明資訊。

2、形參與實參

定義函式時,函式名後傳入的引數叫「形參」,呼叫函式時,函式名後傳入的引數叫「實參」。這裡還涉及到一個「傳值與傳址」的問題,傳值就是傳入一個引數的值,而傳址則是傳入一個引數的記憶體地址。兩者的區別是:如果函式是傳值型別的,在函式裡修改了引數的值,外部的變數(實參)是不會改變的,比如下面這樣一段程式碼:

def f(a):
    a = 1

b = 0
f(b)
print(b)
複製程式碼

執行結果如下

0
複製程式碼

儘管我們修改了傳入引數的值,但是實參卻依舊是0,沒有改變,這就是傳參,如果傳遞的是記憶體地址,修改的了話實參也會受影響。但是Python和其他程式語言有點不同(比如C語言裡可以用&引數名傳地址)。

Python不允許開發者選擇採用傳值還是傳址,而是採用「傳物件引用」的方式,如果傳入的引數是一個不可變物件數字,字串和元組)的引用,就不能修改原始物件的值;如果傳入的引數是一個可變物件列表,字典)的引用,就能直接修改原始物件的值。

比如下面這樣一串程式碼:

def f(a):
    b[0] = 1

b = [0]
f(b)
print(b)
複製程式碼

執行結果如下

[1]
複製程式碼

3、關鍵字引數與預設引數

關鍵字引數」:當函式需要傳入的引數有多個的時候,怕引數混淆傳錯,可以在傳入的時候指定形參的引數名,比如:plus(a = 1, b = 2)

預設引數」:在 定義形參的時候賦予初始值,呼叫的時候就可以不帶引數去呼叫函式,比如:
def plus(a=1, b = 2),呼叫的時候直接plus()或者只傳入一個引數plus(3)都是可以的,還可以配合關鍵字引數指定傳入的是哪個引數。另外,預設引數也稱作「預設引數」。

4、可變引數

有時傳入函式中的 引數數目 可能是 不固定 的,比如,要你計算一堆數字的和,而具體有多少
個數字不知道,這個時候就可以使用可變引數了。只需要在函式定義時在引數前加* 星號,
就代表這個引數是可變引數(其實是隻是把資料打包成了一個元組)。

另外,如果除了可變引數外還有其他的引數,那麼寫在可變引數後的引數要用關鍵字引數指定
否則會加入可變引數的範疇。還有一點要注意,如果傳入的引數是列表或者元組,會被再次
打包成元組,如果想解包的話,需要在實參前加*,程式碼示例如下:

def plus(*a):
    result = 0
    for b in a:
        print(b, end='\t')

if __name__ == '__main__':
    a = [12345]
    plus(a)
    print()
    plus(*a)
複製程式碼

執行結果如下

[12345
1   2   3   4   5 
複製程式碼

另外,如果想把引數打包成字典的方式,可在函式形參前使用兩個**標識


5、全域性變數與區域性變數

全域性變數:定義在最外部,可在函式內部進行訪問,但不能直接修改
區域性變數:定義在函式內部,在函式外部無法訪問的引數和變數。

區域性變數無法在外部訪問的原因

Python在執行函式時,會利用棧(Stack)來儲存資料,執行完函式後,所有資料會被自動刪除。

函式中無法修改全域性變數的原因

試圖在函式裡修改全域性變數的值時,Python會自動在函式內部新建一個名字一樣的區域性變數代替。如果硬是要修改,可以在函式內部使用global關鍵字修飾全域性變數,但是不建議這樣做,會使得程式維護成本的提高。


6、內部函式

所謂的內部函式其實就是「函式巢狀」,在一個函式中巢狀另一個函式,要注意:

內部函式的作用域,只在內部函式的「直接外部函式內」,外部是無法呼叫的沒,外部呼叫內部函式會直接報:函式找不到的錯誤!

內部函式無法直接修改外部函式中的變數,否則會報UnboundLocalError錯誤!如果想在內部函式中直接修改,可以把直接外部函式中的變數通過容器型別來存放,或者使用Python提供的 nonlocal關鍵字 修飾。程式碼示例如下:

def fun_x():
    x = [10]
    y = 10
    def fun_y():
        x[0] += x[0]
        nonlocal y
        y *= y
        return x[0] * y
    return fun_y()

if __name__ == '__main__':
    print(fun_x())
複製程式碼

執行結果如下

2000
複製程式碼

7、閉包

在函式內巢狀了另一個函式,如果「內部函式引用了外部函式的變數」,則可能產生閉包。
Python中形成閉包的三個條件:

  • 函式巢狀
  • 內部函式引用外部變數
  • 外部函式返回內部函式

一個函式閉包的程式碼示例如下:

def outer(a):
    b = 1
    def inner():
        print(a + b)
    return inner

if __name__ == '__main__':
    test_1 = outer(2)
    test_1()
複製程式碼

執行結果如下

2
複製程式碼

在上面的程式碼中,直接把內部函式當做返回值返回了,b是一個區域性變數,按理來說,生命週期在呼叫完outer()函式後就完結了。但是載上面的程式碼中,呼叫test_1時,b變數的值卻正常輸出了,函式閉包使得函式的「區域性變數資訊」得以儲存。

Python中通過__closure__屬性儲存閉包中的區域性變數,把上面test_1函式裡的東東
列印出來,程式碼如下:

print(test_1.__closure__)
print(test_1.__closure__[0].cell_contents)
print(test_1.__closure__[1].cell_contents)
複製程式碼

執行結果如下

(<cell at 0x000001D09ACF85E8int object at 0x00000000667D6C30>, <cell at 0x000001D09ACF8648int object at 0x00000000667D6C10>)
2
1
複製程式碼

8、lambda表示式

在Python中可以使用lambda關鍵字來建立匿名函式直接返回一個函式物件,而不用去糾結給函式起什麼名字,省去了定義函式的步驟從而簡化程式碼,一個對比大小簡單的lambda表示式程式碼示例如下:

big = lambda x, y: x > y
print("第一個引數比第二個引數大:%s" % big(1, 2))
複製程式碼

執行結果如下

第一個引數比第二個引數大:False
複製程式碼

9、遞迴

所謂的遞迴就是「函式呼叫自身」,最簡單的遞迴求和程式碼示例如下:

def sum(n):
    if n == 1:
        return 1
    else:
        return n + sum(n - 1)

print("1到100的求和結果是: %d" % sum(100))
複製程式碼

執行結果如下

1到100的求和結果是: 5050
複製程式碼

另外要注意兩點

  • 遞迴要有結束條件,以避免遞迴的無休止呼叫!
  • 遞迴可以簡化程式,但不一定能提高程式的執行效率!

如果本文對你有所幫助,歡迎
留言,點贊,轉發
素質三連,謝謝?~


相關文章