8. Pythin語言的函數(上)

1. 函數簡介

  函數也是一個 對象
  函數用來保存一些可執行的代碼,並且可以在需要時,對這些語句進行 多次調用
  函數能提高應用的模塊性,和代碼的重複利用率。你已經知道Python提供了許多內建函數,比如print()。但你也可以自己創建函數,這被叫做用戶自定義函數。

1.1定義一個函數

  你可以定義一個由自己想要功能的函數,以下是定義函數的 基礎規則
   1) 函數代碼塊以 def 關鍵詞開頭,後接 函數標識符名稱圓括號 ()
   2) 函數的命名要符合 標識符命名規範
   3) 任何 傳入參數自變量 必須放在圓括號中間,圓括號之間可以用於 定義參數;圓括號中的參數與參數之間用 , 進行分隔。
   4) 函數的第一行語句可以 選擇性地使用文檔字符串,—用於存放函數說明。
   5) 函數內容以冒號後 換行起始,並且要和對應的函數名保持 縮進
   6) return [表達式] 結束函數選擇性地返回一個值給調用方不帶表達式的 return 相當於返回 None(這一點很重要,在後面“14章 模塊的使用”中我會給出一個我當時在測試調用不帶 return 的模塊函數時不知道爲什麼返回 None 的案例)。

  Python 定義函數使用 def 關鍵字,一般格式如下:

# 語法:

def 函數名([形參1,形參2,形參3....]):
    代碼塊

  默認情況下,參數值參數名稱按函數聲明 中定義的順序匹配起來的。

注意:
  函數名必須符合標識符的規範(可以包含字母、數字、下劃線但是不能以數字開頭)
  print 是函數對象;print() 是調用函數。

1.2 操作函數

  讓我們使用函數來輸出"Hello World!":

def hello() :
   print("Hello World!")

hello 				# 
print(hello)  		# <function hello at 0x00000243592BE160>
hello()				# Hello World!

  通過以上實例發現
   1) 函數中保存的代碼 不會立即執行,需要 調用函數 代碼纔會執行;
   2) 調用函數方法 : 函數名x()
   3) 單純的一個 函數名x 沒什麼特別的意義,它只是一個 函數對象,它就是一個字符串;函數名後面跟一個 () 的意思是:實現 函數名x 的功能

  

2. 函數的參數

  接下來我們通過實現 “定義一個函數,函數功能是:求 任意 兩個數。” 這個任務要求來理解什麼是 函數的參數

  那怎麼實現 “定義一個函數,函數功能是:求任意兩個數的和。” ?
  首先我們先定義一個 求和函數,案例一:

def an():
    print(1+3)

an()		# 4

  通過上面這個案例我們確定了一個 求和函數 怎麼定義的;但上面案例輸出的結果並不正式的算 兩個數的和,和 任意 完全沒哈關係。那接下來,我們通過嘗試着 定義兩個變量 來試試看能否補充這兩點,案例二:

def an():
    a = 3
    b = 4
    print(a + b)

an()		# 7

  雖然最後輸出的結果是不一樣了,但說實話,第二個案例雖然比第一個案例多了兩個變量,而且 print() 的內容也變了;但細琢磨,其實和第一個案例 沒啥區別,換湯不換藥;就是把兩個數先拉出來放在外面,打印出來的函數的計算結果是兩個固定變量裏面值的和,不能改變的還是不能改變。那我們怎麼來實現 任意 這個效果?

  在思考怎麼來實現 任意 這個效果前,我們要先確定:在調用函數時,print() 裏的變量是 在 定義函數 時候 賦值 呢 還是 在 調用函數 的時候 賦值 呢?這個”求任意兩個數的和“的函數希望它實現怎麼一個功能呢?
  那麼我們首先要理解清晰:計算的兩個數 能確定嗎?是不是 不能確定。換個思路是不是就是 調用函數的時候賦值。那怎麼實現這個 調用函數的時候賦值?這個時候,我們就需要使用到函數的參數。來看案例三:

def an(a,b):
    # a = 3
    # b = 4
    print(a + b)

an(100,2)		# 102

  在定義函數的時候,可以在函數後面的 () 裏定義 數量不等形參,多個形參之間使用 , 分隔開。

2.1 形參和實參

  形參(形式參數) 定義:形參就相當於在函數內部聲明瞭 變量,但是 並不是賦值

def an(a,b):
    a = None
    b = None
    print(a + b)

an()		# TypeError: an() missing 2 required positional arguments: 'a' and 'b'

  實參(實際參數) 指定對應 了函數在 創建時定義形參。在調用函數時必須傳遞 實參實參將會賦值給對應的形參,簡單來說:有幾個形參就要有幾個實參

  實參與形參之間傳遞的一些操作:

def ints(a,b,c,d):

    print(a,b,c,d)

ints(1,4,7,5)			# 1 4 7 5

#--------------------------------------------------------

def ints(a,b,c,d):

    print(a,b,c,d)

ints(1,4,7,)			# TypeError: ints() missing 1 required positional argument: 'd'

#--------------------------------------------------------

def ints(a,b,c,d):

    print(a,b,c,d)

ints(1,4,7,5,8,9)		# TypeError: ints() takes 4 positional arguments but 6 were given

#--------------------------------------------------------

def ints(a,b,*c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# 1 4 (7, 5, 8, 9)

#--------------------------------------------------------

def ints(*a,b,c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# TypeError: ints() missing 2 required keyword-only arguments: 'b' and 'c'

#--------------------------------------------------------

def ints(**a,b,c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# SyntaxError: invalid syntax

2.2 函數的傳遞方式

  定義形參時,可以爲形參指定默認值。指定了默認值以後,如果用戶傳遞了參數則默認值 不會生效。如果用戶沒有傳遞,則默認值 會自動生效
  位置參數:位置參數就是將對應位置的實參賦值給對應位置的形參;
  關鍵字參數 : 關鍵字參數可以不按照形參定義的順序去傳遞,而根據參數名進行傳遞;
  混合使用位置參數和關鍵字參數的時候,必須將位置參數寫到關鍵字參數前面去

def fn(a=10, b=20, c=30):		# 默認值傳參
    print('a =', a)
    print('b =', b)
    print('c =', c)
    print('a + b + c =', a + b + c)

fn()
# a = 10
# b = 20
# c = 30
# a + b + c = 60

fn(40, 60, 80)					# 位置傳參
# a = 40
# b = 60
# c = 80
# a + b + c = 180

fn(b=90, c=80, a=70)			# 關鍵字傳參
# a = 70
# b = 90
# c = 80
# a + b + c = 240

fn(40, c=60)					# 混合傳參
# a = 40
# b = 20
# c = 60
# a + b + c = 120

fn(c=60, 80)					# 混合傳參
#   File "G:/lianxi/測試.py", line 10
#     fn(c=60, 80)			# 混合傳參
#              ^
# SyntaxError: positional argument follows keyword argument

2.3 實參的類型

  計算機數據有很多類型,如:整型(int)、浮點型(float)、字符串類型(str)、列表類型(list)、字典類型(dict)、布爾類型(bool) 等等;那 實參 能傳遞那些類型呢?不同類型之間的傳遞又有哪些限制和注意呢?

  1. 傳遞一個整數(int):
def fn2(a):
    print('a =',a)

fn2(123)			# a = 123
  1. 傳遞一個變量:
def fn2(a):
    print('a =',a)
    
b = 456
fn2(b)			# a = 456
  1. 傳遞一個 布爾值(bool)
def fn2(a):
    print('a =' ,a)

b = False
fn2(b)			# a = False
  1. 傳遞一個 空值(None)
def fn2(a):
    print('a =' ,a)

b = None
fn2(b)			# a = None
  1. 傳遞一個 字符串(str)
def fn2(a):
    print('a =' ,a)

b = 'Python'
fn2(b)			# a = Python
  1. 傳遞一個 方法(fn)
def fn(a=10, b=20, c=30):
    print('a =', a)
    print('b =', b)
    print('c =', c)
    print('a + b + c =', a + b + c)


def fn2(a):
    print('a =', a)


fn2(fn)			# a = <function fn at 0x00000215E875E160>
				# 記住:<> 的意思是“對象”。
				# 打印返回的內容解釋的意思是:返回對象的類型是一個 function ,這個 function 的名字叫 fn ,這個對象所在的內存位置是 0x00000215E875E160(十六進制地址)
  1. 老生常談,不同的數據類型之間不能進行數據運算;
def fn2(a,b):
    print(a + b)


fn2(1,'2')      # TypeError: unsupported operand type(s) for +: 'int' and 'str'

  1. 可變對象不可變對象
def fn1(a):

    print('a =', a)

c = 10

fn1(c)				# a = 10
print('c =', c)		# c = 10

#---------------------------------------------------------

def fn1(a):
    
    a = 40			# 在函數中對形參進行賦值,不會影響到其他的變量。
    print('a =', a)

c = 10

fn1(c)				# a = 40
print('c =', c)		# c = 10

#---------------------------------------------------------

def fn1(a):
    
    print('a =', a)

c = [1,2,3]

fn1(c)				# a = [1, 2, 3]
print('c =', c)		# c = [1, 2, 3]

#---------------------------------------------------------

# 一個列表一個對象,當我們通過 形參 去修改對象的時候,將會影響到所有指向該對象的 變量。
# 改變量,改的只是變量內部的值;改變量 值會變,id不變。
# 改對象,改的是整個對象的 存儲id。改對象 真正的值不會變,id改變。與其說是“改對象”不如說是:基於原先的數據基礎新建一個對象。

def fn1(a):
    
    a[0] = 40			# a 是一個列表,a 修改列表中的一個元素,a 修改了一整個對象
   						
    print('a =', a)

c = [1,2,3]

fn1(c)				# a = [40, 2, 3]
print('c =', c)		# c = [40, 2, 3]

#---------------------------------------------------------

# 如果我們傳遞的是一個 可變對象 時,而我們又不希望因爲函數內部的操作而影響到了函數外部的 可變對象 ,那麼我們可以使用 copy() 或 切片 等方法制造可變對象·副本來保持 可變對象 原本的值。

def fn1(a):
    a[0] = 40
    print('a =', a, id(a))


c = [1, 2, 3]

fn1(c[:])                   # a = [40, 2, 3] 2272265997376
fn1(c.copy())               # a = [40, 2, 3] 2272265997376
print('c =', c, id(c))      # c = [1, 2, 3] 2272265769600

  

3. 不定長參數

  你可能需要 一個函數能處理比創建函數是==聲明更多的參數==。這個參數叫做==不定長參數==,和上述幾種參數不同,聲明時 不定長參數不會命名。而是在形參前面加一個 * ,這樣這個形參可以獲取到所有的實參,它會將所有的實參保存到一個元組中。這種方法簡稱爲:裝包。

  基本語法如下:

def fun(形參1, 形參2, *形參3):
   代碼塊

  語法實例:

def fun(*a):
   print ('a = ',a,type(a))

fun(1,2,3,4,5)		# a =  (1,2,3,4,5) <class 'tuple'>

  帶 * 號的形參只能有一個,可以和其他參數配合使用

def fun(a, b, *c):
   print ('a = ',a)
   print ('b = ',b)
   print ('c = ',c)

fun( 70, 60, 50, 40, 30, 20, 10 )

# a =  70
# b =  60
# c =  (50, 40, 30, 20, 10)

#--------------------------------------------------------------------------

def fun(*a):
    # 定義一個變量,用來保存結果
    result = 0
    # 遍歷元組,並將元組中的元素進行累加
    for x in a:
        result += x
		# print(result)
    print(result)           # 在循環外打印最後的結果,和在循環內打印結果是不一樣的。不理解的自己嘗試。

fun(1,2,3,4,5,6,7)			# 28

   * 形參只能接受位置參數,不能接受關鍵字參數

def ints(*a):
    print('a =',a)

ints(b=8,c=9,d=7)			# TypeError: ints() got an unexpected keyword argument 'b'

   最後就是注意了。
    注意1. 帶 * 號的參數只能有一個,如果出現多個就會報錯:

def ints(a,*b,*c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# SyntaxError: invalid syntax

    注意2. 不定長參數不一定都寫在最後面,但是要注意帶 * 的參數後面的所有參數必須以關鍵字的形式來傳遞,否則 帶 * 的參數只能做最後一個參數:

def ints(a,*b,c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# TypeError: ints() missing 1 required keyword-only argument: 'c'

#--------------------------------------------------------------------------

def ints(*a,b,c):

    print(a,b,c)

ints(1,4,7,5,8,9)		# TypeError: ints() missing 2 required keyword-only arguments: 'b' and 'c'

#----------------------      but      ------------------------------------

def ints(a,*b,c):

    print(a,b,c)

ints(1,4,7,5,8,c=9)		# 1 (4, 7, 5, 8) 9

#----------------------      but      ------------------------------------

def ints(*a,b,c):

    print(a,b,c)

ints(1,4,7,5,b=8,c=9)		# (1, 4, 7, 5) 8 9

#--------------------------------------------------------------------------

def ints(*a,b,c):

    print(a,b,c)

ints(1,4,b=7,5,8,c=9)
#   File "G:/lianxi/測試.py", line 5
#     ints(1,4,b=7,5,8,c=9)		#
#                  ^
# SyntaxError: positional argument follows keyword argument

  還有一種就是參數帶兩個星號 **基本語法如下:

def ints(**a):
    print('a =',a,type(a))

ints(b=8,c=9,d=7)			# a = {'b': 8, 'c': 9, 'd': 7} <class 'dict'>

  ** 形參可以接收其他的 關鍵字參數,它會將這些參數統一保存到 字典 當中。字典的 鍵(key) 就是參數的名字,字典的 值(value) 就是參數的值

  帶 ** 的形參只有一個,並且必須寫在所有參數的後面。

  總結: *a 處理的是位置參數,**a 處理的是關鍵字參數。

  最後還有附加一點,聲明函數時,參數中星號 * 可以單獨出現,例如:

def f(a,b,*,c):
    return a+b+c

  如果單獨出現星號 * 後的參數必須用關鍵字傳入。

def f(a,b,*,c):
    print(a+b+c)

# f(1,2,3)		# TypeError: f() takes 2 positional arguments but 3 were given

f(1,2,c=3)

  

4. 參數的解包

  前面提到過一個詞,叫 裝包。即然有 裝包,那麼就一定有 解包 了。接下來我們說一說 參數的解包

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 創建一個元組
t = (20,30,40)

fn(t)			# TypeError: fn() missing 2 required positional arguments: 'b' and 'c'

#--------------------------------------------------------------------------

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 創建一個元組
t = (20,30,40)

fn(t[0],t[1],t[2])
# a = 20
# b = 30
# c = 40

#--------------------------------------------------------------------------

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 創建一個元組
t = (20,30,40)

fn(*t)
# a = 20
# b = 30
# c = 40

  傳遞實參時,也可以 在序列類型的參數前 添加 * 星號,這樣它會 自動 的將序列中元素依次作爲參數傳遞;
  並且添加 * 星號的序列類型的參數中 序列的元素個數 必須和 形參的個數 保持一致。

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 創建一個元組
t = (20,30,40,50)

fn(*t)			# TypeError: fn() takes 3 positional arguments but 4 were given

#--------------------------------------------------------------------------

def fn(a,b,*c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 創建一個元組
t = (20,30,40,50)

fn(*t)
# a = 20
# b = 30
# c = (40, 50)

  除了 *t 對元組的解包外,還有 **d 對字典的解包操作:

def fn(a,b,c):
    print('a =', a)
    print('b =', b)
    print('c =', c)

# 創建一個字典
d = {'a':1,'b':2,'c':3}

fn(**d)
# a = 1
# b = 2
# c = 3

  老生常談的解包注意點:添加 ** 星號的序列類型的參數中 字典的鍵·值對個數 必須和 形參的個數 保持一致。

  

總結小便條

本篇文章主要講了以下幾點內容:

  1. 函數簡介
     函數也是一個對象。
     函數用來保存一些可執行的代碼,並且可以在需要時,對這些語句進行多次調用。
    注意:
     函數名必須符合標識符的規範(可以包含字母、數字、下劃線但是不能以數字開頭);
     print 是函數對象,而 print() 是調用函數。
  2. 函數的參數
    2.1 形參 和 實參
     形參(形式參數) 定義形參就相當於在函數內部聲明瞭變量,但是並不是賦值;
     實參(實際參數)指定了形參,那麼在調用函數時必須傳遞實參,實參將會賦值給對應的形參,簡單來說有幾個形參就要有幾個實參。
    2.2 函數的傳遞方式
     定義形參時,可以爲形參指定默認值。指定了默認值以後,如果用戶傳遞了參數則默認值不會生效。如果用戶沒有傳遞,則默認值就會生效;
     位置參數:位置參數就是將對應位置的實參賦值給對應位置的形參;
     關鍵字參數 : 關鍵字參數可以不按照形參定義的順序去傳遞,而根據參數名進行傳遞;
     混合使用位置參數和關鍵字參數的時候必須將位置參數寫到關鍵字參數前面去。
  3. 不定長參數
     定義函數時,可以在形參前面加一個 * ,這樣這個形參可以獲取到所有的實參,它會將所有的實參保存到一個元組中。
     帶 * 號的形參只能有一個,可以和其他參數配合使用;
      * 形參只能接受位置參數,不能接受關鍵字參數;
      ** 形參可以接收其他的關鍵字參數,它會將這些參數統一保存到字典當中。字典的 鍵(key) 就是參數的名字,字典的 值(value) 就是參數的值;
      ** 形參只有一個,並且必須寫在所有參數的後面。
  4. 參數的解包
     傳遞實參時,也可以在序列類型的參數前添加星號 * 或雙星號 ** ,這樣它會自動的將序列中元素依次作爲參數傳遞;
     要求序列中的元素的個數必須和形參的個數一致。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章