02-python 基礎語法知識-02-函數

02-python 基礎語法知識-02-函數

總體 要講的大綱內容 如下

  • 循環- for while
  • 流程中斷 continue break
  • 邏輯操作 and ,or ,not
  • 函數
  • python中的異常處理

​ 今天 是 我們開始學習python基礎語法知識 中比較重要的一個概念 函數 。有了函數可以讓代碼 寫起來 更加方便 ,更加可以複用。

函數是什麼?

​ 你可以認爲是 一段代碼段,只不過這段代碼 寫了一個小功能,你只需要 通過名稱就可以獲取 調用這個函數裏面的代碼段 。 有點抽象 ,來看一個例子,比如我想完成一個功能 ,這個功能 就是要計算兩個數 的值。 這個計算兩個數的值 就是一個小小的功能。 來我們嘗試一下寫一個函數。

def my_sum(a, b):
    print(f"a={a},b={b}")
    return a + b
>>> def my_sum(a, b):
...     print(f"a={a},b={b}")
...     return a + b 
... 
>>> my_sum(5,7)
a=5,b=7
12
	

我通過 mysum(5,7) 就可以 調用 , 函數下面的代碼段 。 你是不是 覺得 這樣也沒有什麼啊? 不用函數 也能實現這個功能啊? 爲啥要寫 個函數呢?

沒有關係,你現在還不知道 函數的厲害的地方。如果 有這個疑問你先保留,之後聽我慢慢解釋。

函數定義

下面 說下如何定義一個函數 關鍵 def

def fun_name():
    pass

這裏 def 空格 然後 跟函數名稱 ,然後括號 ,最後 有冒號, 下面就要開始縮進 四個空格.

還記得 之前我們寫的一些代碼嗎? 猜數字的遊戲,打印9*9 乘法口訣表等。

咱們 可以用函數 來重新寫一下

比如猜數字 那個遊戲的代碼,我可以用函數 就可以重新 修改一下,把代碼 寫到 函數體 內就可以了。

這裏 難點 def 後面的函數 名稱,最好的根據名稱 知道這個函數的功能。我一直再說 函數的功能性,請好好體會一下,

猜數字 小遊戲

from random import randrange


def guess_number():
    # 記錄猜的次數
    count = 0
    rand_nums = randrange(0, 51)

    num = int(input("請輸入你要猜的數字 [0-50]: "))

    while True:
        if num > rand_nums:
            print("猜的太大了。")
            count += 1
            num = int(input("請輸入你要猜的數字 [0-50]: "))
        elif num < rand_nums:
            print('猜的太小了')
            count += 1
            num = int(input("請輸入你要猜的數字 [0-50]: "))
        else:
            count += 1
            print(f'恭喜你,猜對了. 你一共猜了{count} 次')
            # 猜對了要終止 循環 條件
            break

打印乘法口訣表

def print_multiplication():
    """
    打印 9*9 乘法口訣表
    :return:
    """
    for i in range(1, 10, 1):
        for j in range(1, i + 1, 1):
            print(f"{i}*{j} = {i * j} ", end=' ')

        print()

注意:

​ 給函數命名 一定要 比較清晰, 不要出現 aaa , bbb ,ccc, ddd , x, y ,z 等這樣的函數名. 首先 從語法角度來說

這樣命名沒有錯誤,可以正常 執行對應的功能,但是 如果這個代碼,過了一個月,你在看這樣的代碼, 你完全不知道 這裏 名字 是什麼意思? 而你要不得不 去要讀 函數的每一行代碼,才知道 這個函數幹了什麼事情。

感覺是不是有點簡單啊, 只是把代碼 放到一個 函數定義體內就可以了。 但是 突然有一天,以打印 乘法口訣表爲例 , 現在我不想打印那麼多, 我只想要5*5 的口訣表, 你說這挺簡單啊, 只要把 range(1,10,1) 換成range(1,6,1) 不可以了。

def print_multiplication():
    """
    打印 5*5 乘法口訣表
    :return:
    """
    # 這裏改動了
    for i in range(1, 6, 1):
        for j in range(1, i + 1, 1):
            print(f"{i}*{j} = {i * j} ", end=' ')

        print()

if __name__ == '__main__':
    # 這裏 調用函數
    print_multiplication()
    pass

上面 確實是可以的, 但是每一次都要 修改 函數體的代碼,如果哪一天 有人說 我要 6*6 的乘法表, 你還要在改代碼嗎?

有一個更好的方法,就是參數,在函數裏面可以定義參數。以上爲例,我來改一下 這個函數

def print_multiplication(number: int):
    """
    打印number*number 乘法口訣表
    :return:
    """
    for i in range(1, number + 1, 1):
        for j in range(1, i + 1, 1):
            print(f"{i}*{j} = {i * j} ", end=' ')

        print()

然後 這樣調用

if __name__ == '__main__':
    print_multiplication(6)
    print_multiplication(5)
    pass

這樣就可以打印 6*6 乘法表了。 如果之後需要變化 打印的 尺寸 範圍 ,我們只要改變 要 傳入number 的值就可以了。 是不是很方便呢?

函數的參數傳遞

​ 說一下 函數的參數, 從上面的例子中,可以看到 有了參數 可以 讓代碼 更加靈活,方便使用,並且 可以支持不同人 對 打印乘法表的需求。

python中函數參數 有幾種, 位置參數,關鍵字參數, 冗餘參數(我自己起的名字),

位置參數

下面我定義一個函數

def fun(a,b,c,d):
    print(f"a={a},b={b},c={c},d={d}")

直接 傳入 四個數字 ,就會分別按照順序 一次賦值給 a b c d 這四個值, 所以 這個就是叫位置參數,按照位置賦值。 看下面的例子

>>> 
... def fun(a,b,c,d):
...     print(f"a={a},b={b},c={c},d={d}")
...     
>>> 
>>> fun(1,2,3,4)
a=1,b=2,c=3,d=4
>>> fun(1,10,10,4)
a=1,b=10,c=10,d=4

關鍵字參數

下面的 我用另一種方式 ,

傳遞參數, 參數名=數字 這種方式 ,賦值。 這種方式 就可以 不按照順序 傳遞參數了。

>>> fun(b=1,c=10,a=2,d=20) 
a=2,b=1,c=10,d=20

我能不能 位置 和關鍵字 一起使用呢? 當然可以啦。

>>> fun(1,2,d=10,c=4)
a=1,b=2,c=4,d=10

這裏有一個要求, 就是 關鍵字參數 一定要放在 位置參數的後面。 這樣python 解釋器 就明白如何對應傳值,碰到位置參數 直接賦值,然後在賦值 關鍵字參數。

注意下面 這種情況 是不被允許的.

>>> fun(1,d=10,2,c=4)
  File "<input>", line 1
SyntaxError: positional argument follows keyword argument

原因就是 因爲 你想 2 賦值給b , 但是python 解釋器 並沒有那麼智能,它並不能理解你的想法。 從報錯的信息,可以看出, 位置參數 放在了 關鍵字參數的前面了 。所以這種會報錯的,不能這樣調用。

​ 你可能 有疑問,位置參數不香嗎? 爲啥要用關鍵字參數, 多寫了一些代碼, 沒有必要。 其實原因 很簡單, 想想 之前我有講到 list 和dict 嗎? list 雖然可以通過位置 找到元素, 但是 如果有很多元素,我很難記住每個元素的位置,如果 位置改變了,代碼 又要改動呢? 而字典 就是通過key 來找到這個元素的, 這個key 的名稱 是我們自己定義的, xiaoming ,xiaozhang,xiaowang 這樣通過key 就可以找到這個元素了。我們 可讀性就會很高。

這裏我再次強調一下, 隨着代碼 量的增加,一定要主要一些變量的命名,儘量 知道它的含義。

好 ,言歸正傳, 關鍵字參數 也是這個作用,我不需要 去關注 位置,我只要對應參數 給上對應的值就可以了。 這個就是關鍵字參數的意義。

冗餘參數

看下面的例子, 這個函數有兩個參數

def fun(a,b):
    print(f"a={a},b={b}")

正常這樣調用 沒有任何問題。

>>> fun(1,2)
a=1,b=2
>>> fun(1,b=2)
a=1,b=2

但是 假設 有一天 不知道是誰 的粗心,傳入了 超過兩個的參數.

>>> fun(1,2,3,4)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: fun() takes 2 positional arguments but 4 were given

顯然這裏 報錯,這裏報錯信息,說我需要兩個參數,而你卻給我4個參數。 所以你給的太多,我不知道怎麼辦啊? 所以我選擇報錯 。

爲了兼容這種情況, 如果傳入多了 ,只取前面對應的參數 ,後面的參數忽略,就行。這個就是需要冗餘參數來解決這個問題.

在參數列表裏面,加入 *args 這樣 發現 函數就可以正常運行了。

>>> def fun(a,b,*args):
...     print(f"a={a},b={b}")
...     
>>> fun(1,2,3,4)
a=1,b=2

我們參入的3,4 這兩個參數 去哪裏呢? 讓我來一探究竟

>>> 
... def fun(a,b,*args):
...     print(f"a={a},b={b}")
...     print(f'args:{args}')
... 
>>> 
>>> fun(1,2,3,4)
a=1,b=2
args:(3, 4)

>>> fun(1,2,3,4,5,6,7)
a=1,b=2
args:(3, 4, 5, 6, 7)

我打印args 發現了 3,4 這兩個參數的值。原來 3,4 被賦值到 args 這個元祖類型的變量 上面。

好了 現在 這個函數 好像 已經很完美了,可以按照 位置或者關鍵字傳入參數,用args 可以 吸收 傳入太多的參數,並保存在元素裏面。

來看下 下面的調用方式,我傳入了一個關鍵字參數 c=5

>>> 
... def fun(a,b,*args):
...     print(f"a={a},b={b}")
...     print(f'args:{args}')
... 

>>> fun(1,2,3,4,c=5)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: fun() got an unexpected keyword argument 'c'

你想 這個肯定報錯啊, 因爲 如果要傳入關鍵字,只能傳入a =xx,b =xx, 你傳入 c=xx, python解釋器 當然不知道 c=5 這個參數怎麼辦, 所以 解釋器 又報錯了, 你得到 一個不期待的關鍵字參數c 。

你可能 會吐槽,這不是有病嗎? 我都沒有定義 這樣的參數c,你就傳進來,報錯是正常的。

但是 有時候 程序員 要承受很多,要明白 有時候人沒有那麼聰明,或者其他人 沒有仔細看你的代碼參數的要求 。

如果要解決這種問題 ,怎麼辦呢? 其實 和剛剛方法類似的。

在函數定義的時候 加一個 **kwargs 這樣如果有多餘的關鍵字參數 傳進來,就會被賦值到這個 字典中

看下面的例子

>>> def fun(a,b,*args,**kwargs):
...     print(f"a={a},b={b}")
...     print(f'args:{args}, kwargs:{kwargs}')
...     
>>> fun(1,2,3,4,c=5)
a=1,b=2
args:(3, 4), kwargs:{'c': 5}

>>> fun(1,2,3,4,c=5,f=6)
a=1,b=2
args:(3, 4), kwargs:{'c': 5, 'f': 6}

​ 我傳入 多餘的關鍵字參數 c, f ,這些值 會被 賦值到 kwargs 這個參數裏面,這個參數是一個 字典。

看這樣是不是就完美了, 我可以 吸收多餘的位置參數, 多餘的關鍵字參數,這個函數 好像兼容性 更好了。

所以程序員 是不是 要默默承受很多 ,這裏要好好抱住自己,我不該承受那麼多。 哈哈哈。

總結一下:通過 *args, **kwargs 來吸收多餘的位置參數 和 關鍵字參數 ,在一定程度上保證了代碼的容錯能力。

僅僅支持關鍵字參數

如果你想定義的函數, 只希望傳入關鍵字參數, 可以 * 然後定義 參數名稱,這樣的話, 就不能用位置參數 傳入了。

注意: 在python函數定義裏面,*後面的參數 一定要以關鍵字的形式 傳入進來,否則就會 報錯。

>>> def fun(*, name, hobby):
...     print(f"name={name},hobby={hobby}")
...     

>>> fun('frank','swim')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: fun() takes 0 positional arguments but 2 were given
    
>>> fun('frank',hobby='swim')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: fun() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
>>> fun(name='frank',hobby='swim')
name=frank,hobby=swim

函數的返回值

函數 可以理解成 一個 擁有參數, 完成一定功能的代碼段,並且這個代碼段可以通過 函數名稱() 來調用 ,

調用這個函數,函數 總要給調用者一個結果吧,這個結果 我們叫 返回值

現在 來一個簡單的累加

獲取1 + 2 + …+n 求和 完成 函數功能的編寫.

很簡單的函數,我就直接寫了。

def my_sum(n):
    """
    求 1 +2 +...+n
    :param n:
    :return:
    """
    result = 0
    for i in range(1, n + 1, 1):
        result += i

    print(f"result={result}")

>>> 
... def my_sum(n):
...     """
...     求 1 +2 +...+n 
...     :param n: 
...     :return: 
...     """
...     result = 0
...     for i in range(1, n + 1, 1):
...         result += i
... 
...     print(f"result={result}")
...     
>>> my_sum(10)
result=55
>>> my_sum(5)
result=15

看起來功能正常的,也可以打印出 結果。

讓我 試一下 ,調用一下這個函數看下。

image-02-02-function-01

結果如下:

image-02-02-function-02

結果 發現 results 爲空, 並不是 55 ,而第一行打印 而是函數體裏面的打印結果, 所以說這樣調用 也沒有問題,爲啥沒有結果呢?

其實原因就是 這個函數沒有 把 result 返回給 調用方。

所以如何讓函數 返回一個值呢? 只要加上 return xxx 就代表函數結束了,並且 返回了。

所以 修改一下代碼

def my_sum(n):
    """
    求 1 +2 +...+n
    :param n:
    :return:
    """
    result = 0
    for i in range(1, n + 1, 1):
        result += i
    # print(f"result={result}")
    # 把結果返回回去
    return result

if __name__ == '__main__':
    results = my_sum(10)
    print(f"results={results}")
    pass

這個時候 就有結果了, 這個 就是返回值的意思,你要明白 如果沒有顯示的return 語句,函數只會一個None,這樣調用方 就沒有拿到這個已經計算好的結果, 所以這個函數 辛辛苦苦 寫了 很多計算工作,結果沒有把結果 return 回去 ,這樣比較恐怖的。

這裏我這樣解釋 不知道 你理解了嗎? print , 和return 的區別, print 只是讓我看到的結果,如果要把結果傳入到調用方,需要我們顯示的 return語句。 這就是 return 語句的作用 。

匿名函數

我更喜歡用 lambda 表達式 , 而不是用匿名函數 。 有的時候我們發現 我們不想定義一個函數,因爲這個函數的複用性不強,還有這個函數比較簡單,這個時候 我們 可以使用 lambda 來創建一個 函數

python 使用 lambda 來創建匿名函數。所謂匿名,意即不再使用 def 語句這樣標準的形式定義一個函數。

lambda [arg1 [,arg2,.....argn]]:expression

lambda 後面 跟着 函數參數,然後冒號後面是 一個表達式,這個表達式 的值 作爲 這個函數返回。

用法,調用 和 函數一樣,只是這個函數沒有名字,我們把這個表達式賦值 給 f 。

>>> f  = lambda x,y:x+y 
>>> f(1)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'y'
>>> f(1,2)
3
>>> f(1,2)
3
>>> f(10,20)
30

你可能覺得這個沒有啥用, 我剛剛舉例 沒有看出這個lambda 表達式的好處,所以你覺得 沒有啥用。 之後再比較 複雜的 場景中 我們會用到這個 lambda 表達式,你現在先記住這個 語法, 會有用的。

函數的類型是什麼?

現在 我們來看下 函數是什麼類型? 還記得用什麼方法查看一個 類型吧,type(元素)

看例子:

>>> type([1,2,3])
<class 'list'>

>>> def fun():
...     pass
... fun
>>> fun
<function fun at 0x0000019D28045288>
>>> type(fun)
<class 'function'>

其實函數 和 基礎的數據類型一樣 , 也是一種 函數類型 。 注意看 類型 前面有個class 代表這是一個類, list 的類型 也是一個類。 其實 在python中 函數,一些基礎類型都是類, 由類變成一個對象 。 所以 在python裏面

基礎的數據類型其實都是對象, 然後函數也是。

這些類型 基礎類型,包括數字,字符串 都是由 類 生成的 。 由類生成的東西,我們 叫它 對象 . 你可能 對這個概念還比較模糊,沒有關係, 你現在大概知道 這些基礎類型 生成的東西,都可以叫做對象。以後我會慢慢 講解 什麼是對象。 這其實是一種編程思想,面向對象編程,今天只是 提一下,瞭解即可。

>>> type(list())
<class 'list'>
>>> type(tuple())
<class 'tuple'>
>>> type(dict())
<class 'dict'>
>>> type(set())
<class 'set'>
>>> type(str())
<class 'str'>
>>> type(frozenset())
<class 'frozenset'>
>>> type(1)
<class 'int'>

總結

​ 今天 我們 主要學習了 函數,如何用函數讓你的代碼更加的提高可讀性,可複用性。 知道函數函數如何傳遞參數,如何 規定只傳遞關鍵字參數,函數返回值等, 還有lambda 表達式 瞭解。 以及知道函數 究竟是什麼?

這些都是基礎知識,這些是爲我們之後 學習 寫更大程序的一個基石 ,一定要好好掌握,多多練習,看看你有沒有經常寫的代碼段 可以變成一個函數呢, 現在就開始行動吧。 加油!

參考文檔

functions

分享快樂,留住感動. 2020-03-28 14:15:19 --frank
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章