愚公系列2021年12月 Python教學課程 12-Python函數

      網友投稿 803 2022-05-30

      一、什么是函數?

      函數(function)是用于完成特定任務的程序代碼的自包含單元。在面向對象編程的類中,函數通常被稱作方法。不同的函數在程序中扮演著不同的角色,起著不同的作用,執行不同的動作。比如 print()函數可以將對象打印到屏幕上;還有一些函數能夠返回一個值以供程序使用,比如 len()將可計算長度的對象的元素個數返回給程序。

      二、為什么要使用函數呢?

      第一、函數的使用可以重用代碼,省去重復性代碼的編寫,提高代碼的重復利用率。如果程序中需要多次使用某種特定的功能,那么只需要編寫一個合適的函數就可以了。程序可以在任何需要的地方調用該函數,并且同一個函數可以在不同的程序中調用,就像我們經常使用的 print()和 input()函數一樣。

      第二、函數能封裝內部實現,保護內部數據,實現對用戶的透明。很多時候,我們把函數看做“黑盒子”,即對應一定的輸入會產生特定的結果或返回某個對象。往往函數的使用者并不是函數的編寫者,函數的使用者對黑盒子的內部行為并不需要考慮,可以把精力投入到自身業務邏輯的設計而不是函數的實現細節。只有函數的設計者或者說編寫者,才需要考慮函數內部實現的細節,如何暴露對外的接口,返回什么樣的數據,也就是 API 的設計。

      第三、即使某種功能在程序中只使用一次,將其以函數的形式實現也是有必要的,因為函數使得程序模塊化,從“一團散沙”變成“整齊方隊”,從而有利于程序的閱讀、調用、修改和完善。例如,假設你正在編寫一個實現下面功能的程序:

      def read_numbers(): """ 讀入一行數字 :return: """ pass def sort_numbers(lis): """ 對數字進行排序 :return: """ pass def avg_numbers(lis): """ 求數字們的平均值 :return: """ pass def show_number(lis): """ 打印柱狀圖 :return: """ pass if __name__ == '__main__': number_list = read_numbers() sort_numbers(number_list) avg_numbers(number_list) show_number(number_list)

      當然,其中的 4 個函數 read_numbers()、sort_numbers()、avg_numbers()和show_numbers()的實現細節需要你自己編寫。描述性的函數名可以清楚地表明函數的功能和組織結構,然后可以對每個函數進行獨立設計直至完成需要的功能。如果這些函數足夠通用化,那么還可以在其他程序中調用它們。

      三、 定義函數:

      def 函數名(參數): # 內部代碼 return 表達式

      例如:

      def summer(lis): """ 這里是函數的說明文檔,doc 的位置 :param lis: 參數列表的說明 :return: 返回值的說明 """ total = 0 for i in lis: total += i return total

      在定義函數的過程中,需要注意以下幾點:

      -函數代碼塊以 def 關鍵詞開頭,一個空格之后接函數標識符名稱和圓括號(),再接個冒號。

      任何傳入的參數必須放在圓括號中間。

      函數的第一行語句后可以選擇性地使用文檔字符串—用于存放函數說明。

      函數內容以冒號起始,并且縮進。

      使用 return 結束函數。默認返回 None。

      return 語句依然在函數體內部,不能回退縮進。直到函數的所有代碼寫完,才回退縮進,表示函數體結束。

      四、 如何調用函數?

      函數編寫出來就是給人調用的。要調用一個函數,必須使用函數名后跟圓括號的方式才能調用函數。調用的同時要根據函數的定義體,提供相應個數和類型的參數,每個參數之間用逗號分隔。Python 由于動態語言的特點,在做語法和詞法分析檢查的時候,并不會對參數類型進行檢查,但在執行過程中,如果參數類型不符合函數內部運行機制的話,會彈出相應的錯誤,例如:

      >>> all(0, -1, 3) Traceback (most recent call last): File "", line 1, in all(0, -1, 3) TypeError: all() takes exactly one argument (3 given) >>> all([0, -1, 3]) False

      Python 內置函數 all()要求提供一個參數,但我們一開始給了 3 個。后面,我們將三個參數作為一個整體列表提供就沒有問題了。

      1.return 語句:

      return 語句用于表示函數執行到此結束,并且返回 return 后面的對象。有時候,函數不需要返回任何值,此時可以不需要 return 語句,它在后臺默認給你返回個 None,并且不給任何提示。但是更多的時候我們還是需要 return 一些東西。一旦函數執行過程遇到 return 語句,那么之后函數體內的所有代碼都會被忽略,直接跳出函數體。哪怕你現在正在一個循環內。

      def func(): pass return # 此時,后面的代碼其實是永遠無法執行的。 # 但從語法和詞法層面,這些沒有錯誤。 print(1) abs(-1) pass

      2.return 可以返回什么?

      什么都不返回,僅僅 return:return

      數字/字符串/任意數據類型: return ‘hello’

      一個表達式:return 1+2

      一個判斷語句:return 100 > 99

      一個變量:return a

      【愚公系列】2021年12月 Python教學課程 12-Python函數

      一個函數調用:return func()

      多個返回值,以逗號分隔:return a, 1+2, “hello”

      簡而言之,函數可以 return 幾乎任意 Python 對象。

      3. 如何接收函數的返回值?

      我們在調用函數的時候,可以將函數的返回值保存在變量中。

      def func(): pass return "something" result = func()

      而對于同時返回多個值的函數,需要相應個數的變量來接收,變量之間以逗號分隔:

      def func(): return 1, [2, 3], "haha" a, b, c = func()

      4.參數的傳遞

      函數通常都有參數,用于將外部的實際數據傳入函數內部進行處理。但是,在處理不

      同數據類型的參數時,會有不同的情況發生。這一切都是因為以下兩點。

      -Python 的函數參數傳遞的是實際對象的內存地址。

      -Python 的數據類型分可變數據類型和不可變數據類型。

      看下面的例子

      a = 1 def func(a): print("在函數內部修改之前,變量 a 的內存地址為: %s" % id(a)) a = 2 print("在函數內部修改之后,變量 a 的內存地址為: %s" % id(a)) print("函數內部的 a 為: %s" % a) print("調用函數之前,變量 a 的內存地址為: %s" % id(a)) func(a) print("函數外部的 a 為:%s" % a)

      打印結果為:

      調用函數之前,變量 a 的內存地址為: 1401140288 在函數內部修改之前,變量 a 的內存地址為: 1401140288 在函數內部修改之后,變量 a 的內存地址為: 1401140320 函數內部的 a 為: 2 函數外部的 a 為:1

      為什么當 a = 2 之后,函數內外的 a 的內存地址就不一樣了呢?也就是說此后函數內外的 a 是兩個不同的對象了。

      很多時候,我們被這種類似的問題困惑是因為函數參數的命名不恰當造成的。如果我們把上面的參數名改為 b,可能就好理解多了(注意其中文字的變化)。執行結果是一樣的。

      a = 1 def func(b): print("在函數內部修改之前,變量 b 的內存地址為: %s" % id(b)) b = 2 print("在函數內部修改之后,變量 b 的內存地址為: %s" % id(b)) print("函數內部的 b 為: %s" % b) print("調用函數之前,變量 a 的內存地址為: %s" % id(a)) func(a) print("函數外部的 a 為:%s" % a)

      剛才說的是不可變類型參數,如果是可變類型的,比如列表呢?

      a = [1, 2, 3] def func(b): print("在函數內部修改之前,變量 b 的內存地址為: %s" % id(b)) b.append(4) print("在函數內部修改之后,變量 b 的內存地址為: %s" % id(b)) print("函數內部的 b 為: %s" % b) print("調用函數之前,變量 a 的內存地址為: %s" % id(a)) func(a) print("函數外部的 a 為:%s" % a)

      執行結果是:

      調用函數之前,變量 a 的內存地址為: 34875720 在函數內部修改之前,變量 b 的內存地址為: 34875720 在函數內部修改之后,變量 b 的內存地址為: 34875720 函數內部的 b 為: [1, 2, 3, 4] 函數外部的 a 為:[1, 2, 3, 4]

      調用函數時將列表對象 a 的地址傳遞給了函數內部的變量 b。b.append(4)的時候,根據傳進來的內存地址,找到[1,2,3]這個列表對象,在它的后面添加了 4。

      可以看出,此時的 a 和 b 實際指向了同一個對象。為什么會這樣?因為最關鍵的b.append(4)這句代碼,它不同于“=”賦值語句,不會創建新的變量,而列表作為可變類型,具有 append 方法,這個方法只是對列表的一種調用而已。因此,a 和 b 實際還是同一個對象。

      五、 參數類型

      絕大多數函數接收一定數量的參數,然后根據實際調用時提供的參數的值的不同,輸出不同的結果。前面我們說過,將函數內部的參數名字,定義得和外部變量的名字一樣是一種不好的習慣,它容易混淆思維,甚至發生錯誤。通常我們定義和給函數傳遞參數是這樣的:

      x, y, z = 1, 2, 3 def add(a, b, c): return a+b+c add(x, y, x) # 使用變量,傳遞參數 add(4, 5, 6) # 直接傳遞值也是可以的。

      在上面的例子中,a,b,c 叫做形式參數,簡稱形參。而 x,y,z 和 4,5,6 叫做實際參數,簡稱實參,也就是實際要傳遞的值。而我們通常討論的參數,指的都是形參。

      定義函數時,參數的名字和位置確定下來,函數的接口就固定了。對于函數的調用者來說,只需要知道如何傳遞正確的參數,以及函數將返回什么樣的值就夠了,函數內部的復雜邏輯被封裝起來,調用者無需了解。Python 函數的參數定義靈活度非常大。除了正常定義的位置參數外,還可以使用默認參數、動態參數和關鍵字參數,這些都是形參的種類。

      1.位置參數

      也叫必傳參數,順序參數,是最重要的,也是必須在調用函數時明確提供的參數!位置參數必須按先后順序,一一對應,個數不多不少的傳遞!

      上面例子中的 a,b,c 就是位置參數,我們在使用 add(4, 5, 6)調用時,就是將 4 傳給a,5 傳給 b,6 傳給 c 的一一對應傳遞。類似 add(4, 5, 6, 7)、add(4)和 add(5, 4, 6)這種“畫蛇添足”、“缺胳膊少腿”和“嫁錯郎”類型的調用都是錯誤的。其中,add(5, 4, 6)的調用在語法上沒問題,但是輸出結果可能和預期的不一致。

      注意: Python 在做函數參數傳遞的時候不會對數據類型進行檢查,理論上你傳什么類型都可以!

      def add(a, b, c): return a+b+c result = add("haha", 2, 3)

      但是,上面的 add 函數,如果你傳遞了一個字符串和兩個數字,結果是彈出異常,因為字符串無法和數字相加。這就是 Python 的弱數據類型和動態語言的特點。在簡單、方便的時候,需要你自己去實現數據類型檢查。

      2.默認參數

      在函數定義時,如果給某個參數提供一個默認值,這個參數就變成了默認參數,不再是位置參數了。在調用函數的時候,我們可以給默認參數傳遞一個自定義的值,也可以使用默認值。

      def power(x, n = 2): return x**n ret1 = power(10) # 使用默認的參數值 n=2 ret2 = power(10, 4) # 將 4 傳給 n,實際計算 10**4 的值

      上面例子中的 n 就是個默認參數。默認參數可以簡化函數的調用,在為最常用的情況提供簡便調用的同時,還可以在特殊情況時傳遞新的值。但是在設置默認參數時,有幾點要注意:

      [ ] 默認參數必須在位置參數后面!

      如果你違反了這點,在語法層面直接是通不過的。

      # 這是一個錯誤的例子 def power(n = 2,x): return x**n

      [ ] 當有多個默認參數的時候,通常將更常用的放在前面,變化較少的放后面。

      def student(name, sex, age, classroom="101", tel="88880000", address="..."): pass

      [ ] 在調用函數的時候,盡量給實際參數提供默認參數名。

      def student(name, sex, age, classroom="101", tel="88880000", address="..."): pass student('jack','male',17) # 其它全部使用默認值 student('tom','male',18,'102','666666','beijing') # 全部指定默認參數的值 student('mary','female',18,'102',tel='666666') # 挑著來 student('mary','female',18,tel='666666','beijing') # 這是錯誤的參數傳遞方式 student("mary","female",18,tel="666666",address="beijing")

      注意最后兩種調用方式,倒數第二種是錯誤的,而最后一種是正確的。為什么會這樣?因為一切沒有提供參數名的實際參數,都會當做位置參數按順序從參數列表的左邊開頭往右匹配!

      [ ] 使用參數名傳遞參數

      通常我們在調用函數時,位置參數都是按順序先后傳入,而且必須在默認參數前面。但如果在位置參數傳遞時,給實參指定位置參數的參數名,那么位置參數也可以不按順序調用,例如:

      def student(name, age, classroom, tel, address="..."): pass student(classroom=101, name="Jack", tel=66666666, age=20)

      注意指定的參數名必須和位置參數的名字一樣。

      [ ] 默認參數盡量指向不變的對象!

      下面是一道 Python 面試真題:

      def func(a=[]): a.append("A") return a print(func()) print(func()) print(func())

      不要上機測試,僅憑代碼,你能說出打印的結果嗎?

      因為 Python 函數體在被讀入內存的時候,默認參數 a 指向的空列表對象就會被創建,并放在內存里了。因為默認參數 a 本身也是一個變量,保存了指向對象[]的地址。每次調用該函數,往 a 指向的列表里添加一個 A。a 沒有變,始終保存的是指向列表的地址,變的是列表內的數據!我們可以測試一下:

      def func(a=[]): print("函數內部 a 的地址為:%s" % id(a)) a.append("A") return a b = func() print('此時 b 的值為:%s' % b) print("函數外部 b 的地址為:%s" % id(b)) print("-------------") c = func() print('此時 c 的值為:%s' % c) print("函數外部 c 的地址為:%s" % id(c)) print("-------------") d = func() print('此時 d 的值為:%s' % d) print("函數外部 d 的地址為:%s" % id(d))

      打印結果是:

      函數內部 a 的地址為:39287880 此時 b 的值為:['A'] 函數外部 b 的地址為:39287880 ------------- 函數內部 a 的地址為:39287880 此時 c 的值為:['A', 'A'] 函數外部 c 的地址為:39287880 ------------- 函數內部 a 的地址為:39287880 此時 d 的值為:['A', 'A', 'A'] 函數外部 d 的地址為:39287880

      那么如何避免這個問題呢?

      使用不可變的數據類型作為默認值!

      def func(a=None): # 注意下面的 if 語句 if a is None: a = [] a.append("A") return a print(func()) print(func()) print(func())

      將默認參數 a 設置為一個類似 None,數字或字符串之類的不可變對象。在函數內部,將它轉換為可變的類型,比如空列表。這樣一來,不管調用多少次,運行結果都是[‘A’]了。

      3.動態參數

      顧名思義,動態參數就是傳入的參數的個數是動態的,可以是 1 個、2 個到任意個,還可以是 0 個。在不需要的時候,你完全可以忽略動態函數,不用給它傳遞任何值。

      Python 的動態參數有兩種,分別是args 和**kwargs,這里面的關鍵是一個和兩個星號的區別,而不是 args 和 kwargs 在名字上的區別,實際上你可以使用any 或whatever 的方式。但就如 self 一樣,默認大家都使用*args 和kwargs。

      注意:動態參數,必須放在所有的位置參數和默認參數后面!

      def func(name, age, sex='male', *args, **kwargs): pass

      *args

      一個星號表示接收任意個參數。調用時,會將實際參數打包成一個元組傳入形式參數。

      如果參數是個列表,會將整個列表當做一個參數傳入。例如:

      def func(*args): for arg in args: print(arg) func('a', 'b', 'c') li = [1, 2, 3] func(li)

      運行結果是:

      a b c [1, 2, 3]

      通過循環 args,我們可以獲得傳遞的每個參數。但是 li 這個列表,我們本意是讓它內部的 1,2,3 分別當做參數傳遞進去,但實際情況是列表本身被當做一個整體給傳遞進去了。怎么辦呢?使用一個星號!調用函數,傳遞實參時,在列表前面添加一個星號就可以達到目的了。實際情況是,不光列表,任何序列類型數據對象,比如字符串、元組都可以通過這種方式將內部元素逐一作為參數,傳遞給函數。而字典,則會將所有的 key 逐一傳遞進去。

      def func(*args): for arg in args: print(arg) li = [1, 2, 3] func(*li)

      **kwargs

      兩個星表示接受鍵值對的動態參數,數量任意。調用的時候會將實際參數打包成字典。例如:

      def func(**kwargs): for kwg in kwargs: print(kwg, kwargs[kwg]) print(type(kwg)) func(k1='v1', k2=[0, 1, 2])

      運行結果是:

      k1 v1 k2 [0, 1, 2]

      而如果我們這樣傳遞一個字典 dic 呢?我們希望字典內的鍵值對能夠像上面一樣被逐一傳入。

      def func(**kwargs): for kwg in kwargs: print(kwg, kwargs[kwg]) dic = { 'k1': 'v1', 'k2': 'v2' } func(dic)

      實際結果卻是彈出錯誤,為什么?

      Traceback (most recent call last): File "F:/Python/pycharm/201705/func.py", line 10, in func(dic) TypeError: func() takes 0 positional arguments but 1 was given

      因為這時候,我們其實是把 dic 當做一個位置參數傳遞給了 func 函數。而 func 函數并不接收任何位置函數。那怎么辦呢?使用兩個星號!

      def func(**kwargs): for kwg in kwargs: print(kwg, kwargs[kwg]) dic = { 'k1': 'v1', 'k2': 'v2' } func(**dic)

      有了前面一個星號的基礎,這里我們應該很好理解了。兩個星號能將字典內部的鍵值對逐一傳入**kwargs。

      “萬能”參數

      當*args 和kwargs 組合起來使用,理論上能接受任何形式和任意數量的參數,在很多代碼中我們都能見到這種定義方式。需要注意的是,*args 必須出現在kwargs之前。

      def func(*args, **kwargs): for arg in args: print(arg) for kwg in kwargs: print(kwg, kwargs[kwg]) lis = [1, 2, 3] dic = { 'k1': 'v1', 'k2': 'v2' } func(*lis, **dic)

      現在我們結合一下普通參數和萬能參數,看看會有什么情況發生:

      def func(a, b, c=1, *args, **kwargs): for arg in args: print(arg) for kwg in kwargs: print(kwg, kwargs[kwg]) lis = ['aaa', 'bbb', 'ccc'] dic = { 'k1': 'v1', 'k2': 'v2' } func(1, 2, *lis, **dic)

      打印結果是:

      bbb ccc k1 v1 k2 v2

      列表 lis 中的第一個元素‘aaa’怎么沒有打印出來?

      我們改一下代碼,打印一下參數 c 的結果就知道了:

      def func(a, b, c=1, *args, **kwargs): print('c 的值是:', c) for arg in args: print(arg) for kwg in kwargs: print(kwg, kwargs[kwg]) lis = ['aaa', 'bbb', 'ccc'] dic = { 'k1': 'v1', 'k2': 'v2' } func(1, 2, *lis, **dic)

      打印結果為:

      c 的值是: aaa bbb ccc k1 v1 k2 v2

      原來,lis 的第一個元素被傳遞給參數 c 了!這就是 Python 的參數傳遞規則之一。

      為了避免出現這種情況,默認參數在傳遞實參時盡量指定寫上形參的名字。

      Python 的函數參數種類多樣、形態多變,既可以實現簡單的調用,又可以傳入非常復雜的參數。需要我們多下功夫,多寫實際代碼,多做測試,逐步理清并熟練地使用參數。

      5G教育 Python

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:三代開源社區的協作模式
      下一篇:【活動結束】【內容共創系列】感恩有你,一路同行,參與簽約作者征集令,贏高達500元禮包!
      相關文章
      日批日出水久久亚洲精品tv| 国产亚洲成av人片在线观看 | 亚洲AV无码成人精品区蜜桃 | 91亚洲国产在人线播放午夜| 国产亚洲精品a在线无码| 久久精品国产精品亚洲| 亚洲国产精品18久久久久久| 亚洲精品亚洲人成在线| 亚洲精品av无码喷奶水糖心| 亚洲国产精品美女久久久久| 亚洲精品美女久久久久久久| 亚洲欧美日本韩国| 亚洲欧美成人综合久久久| 亚洲中文无码mv| 亚洲精品永久在线观看| 亚洲成在人线在线播放无码| 亚洲av无码专区亚洲av不卡| 色欲色欲天天天www亚洲伊| 国产亚洲男人的天堂在线观看| 天堂亚洲免费视频| 亚洲AV无码成人精品区大在线 | 亚洲一线产区二线产区区| 亚洲熟妇无码av另类vr影视| 亚洲日韩AV无码一区二区三区人| 亚洲一区二区三区丝袜| 亚洲Av永久无码精品一区二区| 小说区亚洲自拍另类| 亚洲精品高清在线| 亚洲色无码一区二区三区| 情人伊人久久综合亚洲| 亚洲综合精品香蕉久久网97| 亚洲精品在线播放| 在线观看亚洲AV每日更新无码| 亚洲av日韩av永久无码电影| 亚洲国产精品碰碰| 亚洲熟女一区二区三区| 亚洲av网址在线观看| 亚洲国产精品午夜电影| 亚洲综合激情五月丁香六月| 国产亚洲女在线线精品| 国产亚洲人成A在线V网站|