1. 程式人生 > 實用技巧 >Python:函式基礎

Python:函式基礎

Blog:https://www.cnblogs.com/Rohn

目錄

函式基礎

除了可以直接使用的內建函式外,Python還支援自定義函式,即將一段有規律的、可重複使用的程式碼定義成函式,從而達到一次編寫、多次呼叫的目的。

函式的本質就是一段有特定功能、可以重複使用的程式碼,這段程式碼已經被提前編寫好了,並且為其起一個好聽的名字。在後續編寫程式過程中,如果需要同樣的功能,直接通過起好的名字就可以呼叫這段程式碼。

函式的作用

  • 結構化程式設計對程式碼的最基本的封裝,一般按照功能組織一段程式碼;
  • 封裝的目的為了複用,減少冗餘程式碼;
  • 程式碼更加簡潔美觀、可讀易懂

函式的定義

Python函式:能完成一定的功能,由若干語句組成的語句塊、函式名稱、引數列表構成,它是組織程式碼的最小單元。

語法格式

def 函式名(引數1,引數2,引數3,...):
    '''註釋'''
    函式體
    return 返回的值

需要注意的地方:

  • 函式名後面必須加冒號;
  • 函式名即識別符號,命名規範:小寫字母,多個單詞用_間隔;
  • 如果函式體和def不在同一行,必須縮排,約定4個空格;
  • 若沒有return,則隱式返回一個None
    值;
  • 如果函式體body語句只有一行,或者可以簡寫為一行,則可以寫在def的同行。例如:
def myfunc(x,y,z): print(x+y+z)

函式的呼叫

如何呼叫

函式宣告好之後,就可以執行函式,執行函式也稱為呼叫函式,方式為func_name(args),例如:

myfunc(1,2,3)

呼叫規則

函式的使用,必須遵循原則:先定義,後呼叫

若在前面呼叫了,後面再定義,是否會報錯呢?

測試一:

def bar():
    print('from bar')

def foo():
    print('from foo')
    bar()
foo() # 正常

測試二:

def foo():
    print('from foo')
    bar()
    
def bar():
    print('from bar')
foo() # 也正常

測試三:

def foo():
    print('from foo')
    bar()

foo()    
    
def bar():
    print('from bar')
# 報錯
NameError: name 'bar' is not defined

函式的返回值

函式返回值:

  • 沒有返回值:預設返回None
  • 返回一個值:函式結束了且返回一個值
  • 返回多個值:多個值之間用逗號隔開,接收的時候可以用一個變數接收(返回元組),也可以用等量的多個變數接收

什麼時候需要有返回值?

呼叫函式,經過一系列的操作,最後要拿到一個明確的結果,則必須要有返回值。

通常有參函式需要有返回值,輸入引數,經過計算,得到一個最終的結果。

什麼時候不需要有返回值?

呼叫函式,僅僅只是執行一系列的操作,最後不需要得到什麼結果,則無需有返回值。

通常無參函式不需要有返回值。

多個返回值

Python的函式支援返回多個值。返回多個值時,預設以tuple的方式返回。例如,下面兩個函式的定義是完全等價的。

def f():
    return 1,2

def f():
    return (1,2)

丟棄返回值

# 不進行任何賦值,將丟棄所有返回值
f()

# 可以通過索引取得某個或某幾個返回值
a = f()[0]
b = f()[1]

# 使用雙下劃線__或更多下劃線___________
# 丟棄第二個返回值
a, __ = f()

函式呼叫時的***

除了在def定義函式時,引數中可以使用***收集引數,在函式呼叫的時候也可以使用***分別解包元組(列表或其它物件)、字典。一定要注意區分函式定義和函式呼叫時的***,它們的用法是不通用的。

例如,解包元組:

def f(a,b,c,d):
    print(a)
    print(b)
    print(c)
    print(d)

T=(1,2,3,4)
f(*T)

*除了可以解包元組,還可以解包其它可迭代物件,例如列表。甚至是字典也能解包,只不過*解包的字典得到的是key組成的引數列表,和value無關:

D=dict(a=11,b=22,c=33,d=44)
f(*D)

# 輸出:
a
b
c
d

**解包的字典則是key=value組成的引數列表。以下是函式呼叫時使用**進行解包,字典D中的key名稱必須和def中定義的引數名稱相同:

def f(a,b,c,d):
    print(a)
    print(b)
    print(c)
    print(d)

D=dict(a=11,b=22,c=33,d=44)
f(**D)

# 輸出:
11
22
33
44

在函式呼叫時,可以混合位置引數、關鍵字引數、*解包引數、**解包引數。用法非常的靈活:

def f(a,b,c,d):
    print(a)
    print(b)
    print(c)
    print(d)

f(*(1,2),**{'d':4,'c':3})

f(1,*(2,3),**{'d':4})

f(1,c=3,*(2,),**{'d':4})

f(1,*(2,3),d=4)

f(1,*(2,),c=3,**{'d':4})

# 結果如下
1
2
3
4
1
2
3
4
1
2
3
4
1
2
3
4
1
2
3
4

上面呼叫函式時的效果都等同於f(1,2,3,4)

函式的引數

函式的引數其實也是變數,只不過這些變數是獨屬於函式的本地變數,函式外部無法訪問。在函式呼叫的時候,會將給定的值傳遞給函式的引數,這實際上是變數賦值的過程。例如:

def myfunc(x,y,z):
    print(x,y,z)

myfunc(1,2,3)

形參和實參

形參即變數名,實參即變數值,函式呼叫時,將值繫結到變數名上,函式呼叫結束,解除繫結。

形參:定義函式時,小括號中的引數,是用來接收引數用的,在函式內部作為變數使用。

實參:呼叫函式時,小括號中的引數,是用來把資料傳遞到函式內部用的。

引數的傳遞

引數的傳遞可以分為按指標傳參、按位置傳參、按關鍵字key=value方式傳參。

按指標傳參

Python中變數賦值、引數傳遞都是通過指標拷貝的方式進行的。都只是拷貝了源資料的一個地址,而不會拷貝記憶體中完整的資料物件副本。所以,如果在函式內部修改變數指向的資料物件,會影響函式外部的資料。

例如:

def f(x):
    print(x+3)


a = 4
f(a)

# 輸出結果
7

按位置傳參

如果是多個引數,則按從左到右的順序進行引數變數的賦值:

def f(x, y, z):
    print(x)
    print(y)
    print(z)


f(2, 3, 4)

# 輸出結果
2
3
4

呼叫f(2,3,4)的時候,會按照從左向右的位置方式對本地變數x、y、z賦值:x=2,y=3,z=4

按關鍵字key=value方式傳參

Python還支援key=value的方式設定函式呼叫時的引數,使用key=value的方式賦值時,順序不重要。這種函式呼叫時的傳值方式稱為關鍵字傳值

例如:

def f(x, y, z):
    print(x)
    print(y)
    print(z)


f(x=3, y="haha", z=4)

# 輸出
3
haha
4

也可以打亂順序,輸出結果不變:

f(x=3, z=4, y="haha")

還可以將key=value和位置傳參的方式進行混合:

f(3, "haha", z=4)

但混合按位置傳參方式的時候,位置引數必須在其它傳參方式的前面,不僅此處結合key=value時如此,後文中位置引數結合其它方式傳參也都如此:位置引數必須在最前面。例如,下面的傳參方式是錯的:

f(z=4, 3, "haha")

引數預設值

在def或lambda宣告函式的時候,可以通過var=default的方式指定引數的預設值。

例如:

def f(x=3):
    print(x)


f(4)
f("haha")
f()

# 輸出結果
4
haha
3

上面的f(4)f("haha")都對函式f()的本地變數x進行了賦值。但是最後一個呼叫語句f()未賦值,而是使用引數的預設值3

設定引數預設值時,如果函式有多個引數,則帶預設值引數後面必須放在最後面。例如:

# 正確
def f(x, y, z=4)
def f(x, y=1, z=4)

# 錯誤
def f(x, y=4, z)

只要為引數設定了預設值,那麼呼叫函式的時候,這個引數就是可選的,可有可無的,如果沒有,則採用預設值。

def f(x, y=2, z=4):
    print(x)
    print(y)
    print(z)


# 不採用任何預設值
f(2, 3, 4)

# 採用z的預設值
f(2, 3)

# 採用y的預設值
# 此時z必須按key=value的方式傳值
f(2, z=5)

# y、z都採用預設值
f(2)

# 輸出結果
2
3
4
2
3
4
2
2
5
2
2
4

位置可變引數*

對於任意長度的引數,可以在def宣告的函式中使用*將各位置引數收集到一個元組中。

def f(*args):
    print(args)


f(1, 2, 3, 4)

# 結果輸出:(1, 2, 3, 4)

上面呼叫f(1, 2, 3, 4)的時候,將所有引數都收集到了一個名為args的元組中。

既然是元組,就可以對引數進行迭代遍歷:

def f(*args):
    for arg in args:
        print(arg)

f(1,2,3,4)

# 輸出結果
1
2
3
4

必須注意,*是按位置收集引數的。

def f(x, y, *args):
    print(x)
    print(y)
    for arg in args:
        print(arg)


f(1, 2, 3, 4)

按照從左向右的傳參規則,首先將1賦值給x,將2賦值給y,然後將剩餘所有的位置引數收集到args元組中,所以args=(3,4)

如果*後面還有引數,則呼叫函式的時候,後面的引數必須使用key=value的方式傳遞,否則會收集到元組中,從而導致引數缺少的問題:

def f(x, *args, y):
    print(x)
    print(y)
    for arg in args:
        print(arg)


# 正確
f(1, 3, 4, y=2)

# 錯誤
f(1, 2, 3, 4)

上面呼叫f(1, 3, 4, y=2)的時候,會按照位置引數對x賦值為1,然後將所有位置引數收集到元組args中,因為y=2是非位置引數傳值方式,所以args=(3, 4)

如果為上面的y設定預設值:

def f(x, *args, y=2)

那麼f(1, 2, 3, 4)會將(2, 3, 4)都收集到元組args中,然後y採用預設值2

關鍵字可變引數**

除了可以使用*將位置引數收集到元組中,還可以使用**key=value格式的引數收集到字典中。

例如:

def f(x, **args):
    print(x)
    print(args)


f(1, a=11, b=22, c=33, d=44)

既然是將引數收集到字典中,就可以使用字典類的工具操作這個字典。例如,遍歷字典。

**的後面不能出現任何其它型別的引數。例如,下面的都是錯誤的def定義方式:

def f(x, **args, y)
def f(x, **args, y=3)
def f(x, **args, *t)

只能將位置引數或者*的收集放在**的前面。

def f(x, y, **args)
def f(x, *args1, **args2)

keyword-only引數形式

keyword-only的引數傳值方式表示def中如果使用了*,那麼在呼叫函式時,它後面的引數必須只能使用關鍵字傳值。其實在前面的內容中已經出現過幾次與之相關的說明。

另外注意,*才是keyword-only開關,**不是,雖然**也有自己的一些語法限制:任意型別的引數定義都必須在**之前,包括keyword-only型別的引數。例如:

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

按照keyword-only的規則,被*b收集的位置引數不包括c,這個c必須只能使用關鍵字的方式傳值,否則就被當作位置引數被收集到元組b中。

# 正確
f(1, 2, 3, c=4)

# 錯誤
f(1, 2, 3, 4)

# 錯誤
f(1, c=4, 2, 3)

其中最後一個錯誤和如何def的定義無關,而是函式呼叫時的語法錯誤,前面已經解釋過:位置引數必須放在最前面。

還可以直接使用*而非*args的方式,這表示不收集任何引數,但卻要求它後面的引數必須按照關鍵字傳值的方式。

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

以下是正確和錯誤的呼叫方式示例:

# 正確
f(1, b=2, c=3)
f(1, c=3, b=2)
f(b=2, c=3, a=1)

# 錯誤
f(1, 2, 3)
f(1, 2, c=3)
f(1, b=2, 3)

不過,keyword-only後面的引數可以使用引數預設值。

def f(a, *, b, c=3)

那麼c是可選的,但如果給定,則必須按關鍵字方式傳值。