Python基礎05-函數


函數

數學中的函數指一種映射的變換關系,如f(x)=2x+1,轉換為Python函數為:

def f(x):
    return x*2 + 1

Python中的函數可以理解為一種預先設定的處理過程。
一般過程都會包含輸入、處理、和輸出三個部分。

  • 輸入,即函數參數,可以有多個參數;
  • 處理:函數內部的處理過程,可以調用其他函數及模塊;
  • 輸出:即返回值,也有可以返回多個。

函數定義和調用

函數分為函數定義和函數調用兩部分。

  • 函數定義即設計函數,是對參數、處理過程和返回值的描述。
  • 函數調用及使用函數,是使用實際的數據,運行函數並得到實際的返回值。

當然也可以直接導入其他模塊,使用中他人設計的函數。

定義函數

定義一個函數使用def關鍵字,格式如下。

def 函數名(參數1,參數2, ...):
    處理過程

如一個加法函數的定義如下。

def add(x, y): # 定義函數
    s = x+y     # 處理過程
    return s   # 返回結果

調用函數

定義的函數需要調用才能執行,調用是按定義的格式傳入和參數對應的實際數據,調用方式如下

函數名(數據1,數據2,...)

如果需要獲取函數的返回結果,可以使用將函數調用復制給變量

變量 = 函數名(數據1,數據2,...) 

以下示例演示了函數的定義和使用。

sum = add(1,3)  # 調用函數,得到返回結果
print('1+3 =', sum)  # 打印函數結果

在編程語言里,單元函數(Unit)是程序的基本單位,很多復雜的流程都是通過不同的函數組合來實現的。
下面的例子演示了使用函數實現的注冊、登錄功能。

案例: 用戶注冊/登錄函數

users = {"張三": "123456"}

def reg(username, password):
    if users.get(username): # 如果用戶中存在username這個key
        print("用戶已存在")
    else:
        users[username] = password # 向users字典中添加元素
        print("添加成功")

def login(username, password):
    if not users.get(username):
        print("用戶不存在")
    elif users['username'] == password:
        print("登錄成功")
    else:
        print("密碼錯誤")

參數

參數是指使用函數時需要提供的信息,如一個登錄函數add(a, b),需要提供加數和被加數,才能進行運算。
這里的a和b便是函數的參數。
函數的參數規定了調用函數時需要提供的信息及格式,從另一種角度函數參數可以稱為一種“函數簽名”,即函數的使用方式。

有些函數不需要提供"額外信息"便可以運行,即函數也可以沒有參數,如

def hi():
     print('hi~')

調用時也不需要傳入參數,直接使用hi()即可,括號是調用操作符,不能省略。

形參和實參

函數分為定義和調用,在定義函數時的參數稱為形式參數,如def add(x, y): ...,這里的xy便是形式參數,形式參數是函數內部使用的。
在調用函數時需要傳入實際的數據,如add(3,5),這里的35便是實際參數。實際參數也可以是預先定義好的變量,如。

a, b = 3, 5
add(a, b)

這里的ab也是實際參數。

參數類型

Python中的函數參數支持任意類型,包含數字,支付串,列表,元組,也可以是函數和類。
作為一種動態語言,函數參數不需要提前指定數據類型,但實際函數在處理過程中是需要參數是期望的類型的。
比如調用加法函數實際期望a和b都是數字,假設調用加法時a,b給了兩個字符串,就會變成字符串連接,和期望結果不符。
Python3.5版本以后提供了類型注解,示例如下:

def add(x: int, y: int):   # 說明x,y應為整型
    s = x+y 
    return s

注解只是提供一種使用說明,並不做強制的限制,實際上你依然可以使用add('hello', 'world')得到'helloworld'
類型注釋也可以指定多種類型,同時也可以注釋函數返回值類型。示例如下。

def add(x: (int, float), y: (int, float)) -> (int, float):  # ->指返回值類型
    s = x+y
    return s

要強制進行類型檢查,可以使用Pydantic庫
如果要注釋參數支持泛類型或嵌套類型,可以使用typeing庫中對應的類型。

參數默認值-必選和可選參數

在定義函數時可以為部分參數提供默認值,如

def add(x, y=1): ...   

此時在調用函數時,必須傳入x對應的數據,y對應的數據可以傳入也可以不傳(不傳默認為1)。
因此x稱為必選參數y稱為可選參數

注意:提供默認值的參數必須寫到后面。

如果加上類型注解,寫法為def add(x: (int,float), y: (int, float) = 1): ...

位置參數和關鍵字參數

參數在定義時只有必選和可選之分,可選的參數需要寫到后面。
但是在函數調用時就有如下兩種傳參方式

add(3,5)
add(x=3,y=5)

這兩種方式得到的結果一致。
第一種,參數按位置循序移除傳入,3對應add(x,y)的第一個參數,5對應第二個參數y。這種參數稱為位置參數
第二中,參數直接指定對應的參數值,這種可以不按順序傳入add(y=5,x=3)也可以得到相同的結果。這種稱為關鍵字參數

不定參數

當一個函數使用方式不確定,需要設計其支持任意多個、任意方式(位置/關鍵字形式)傳入時。可以使用*args**kwargs

args即參數(復數):arguments的縮寫,kwargs即關鍵詞參數(復數):keyword arguments的縮寫。

def add(*args, **kwargs):
    s = 0
    for num in args:   # args得到一個元祖類型,沒有位置參數時為空元祖
        s += num
    for num in kwargs.values():  # kwargs得到一個字典類型,無關鍵詞參數時為空字典
        s += num
    return s

在調用時給任何形式的參數都能得到響應結果(無參返回0)。

add()   # args 為 空元祖 ()  kwargs 為空字典 {}  結果為 0
add(3, 5) # args 為 元祖 (3,5)  kwargs 為空字典 {}  結果為 8
add(x=3, y=5)  # args 為 空元祖 ()  kwargs 為空字典 {'x': 3, 'y': 5}  結果為 8
add(3, 5, z=6)   # 此時 args為(3,5)  kwargs為 {'z': 6}, 得到 14

返回值

參數是調用函數需要提供的信息,返回值則是調用函數后輸出給調用方的信息。當然,函數也可以只做處理,沒有返回值。
需不要有返回值要根據具體功能來訂,一個函數"把大象放進冰箱",就可以操作完沒有返回值,而另一個函數"查詢成績"就需要提供返回結果。
函數中使用關鍵return返回結果,return操作后,函數結束(后面的語句不會再執行)。

返回類型

函數返回結果同樣支持各種對象,包含數字,支付串,列表,元組,也可以是函數和類。
返回值注解方式如下,沒有返回值,可以注解其返回None。

def  add(x, y)--> (int, float): ...   
def  hi() --> None: ...

return后終止

def add(x, y):
    s = x + y
    return s  # 返回操作結果
    print(s)   # 不會執行

如果想函數每次調用都返回一個值后並不終止,而是暫停等待下次調用,可以使用yield代替return,這樣便得到一個生成器函數。

有返回值的函數調用時可以通過賦值變量獲取到函數結果

sum = add(3, 5)

如果函數沒有寫return語句,執行后返回None。

return多個數據

return也可以一次return多個數據,

def calc(x, y):
    s = x + y
    d = x - y
    return s, d

調用時可以用一個變量接收調用結果result = calc(3, 5), result得到一個元祖,值為(5, -2)。
也可以使用兩個變量接收函數調用結果sum, diff = calc(3, 5),此時sum得到兩數之和5,diff得到兩數之差-2。

多處return

程序中也可以通過if分支有多個return,即多條邏輯,每個邏輯返回不同的結果,示例如下。

def calc(x: int, y: int, type='add'):   # type為計算類型,支持add加,sub減,mul乘,div除,默認為加。
    if type == 'add': 
         return x + y
    if type == 'sub':   # 不需要elif,因為如果滿足上個條件,就return終止了。
        return x - y
    if type == 'mul': 
        return x * y
     if type == 'div':
         return x / y

注意:如果type傳的值四個都不是,則會返回None

變量的作用域

不同函數中的變量是相互隔離的,如

def add(x, y):
    return x + y

def sub(x, y):
    return x - y

add中的x和y同sub函數中的x和y是先后隔離的。函數內部的變量被稱為局部變量,局部變量是私有變量,一般情況下,一個函數無法訪問其他函數的局部變量。
如果需要在一個函數中聲明一個變量,讓所有函數都可以使用,可以使用global關鍵字聲明其為全局變量。

def add(x, y):
    global z
    z = 3
    return x + y + z

def sub(x, y):
    return x - y -z  # 可以使用全局變量z

由於函數都可以訪問和改變全局變量,這會導致全局變量的值不可預測,因此需要謹慎使用全局變量。

默認情況下模塊中的變量也是全局變量,但函數中在修改全局變量時很容易出現局部變量覆蓋現象。

a = 3   # 模塊中的全局變量

def add():
    a = a + 3
    print(a)

調用add()便會報錯,說局部變量a沒定義,原因是=號左邊出現同名變量a,這里邊會把所有的a當做局部變量,而右邊在使用a時由於還沒有定義a,所以就會報錯。
解決辦法就是顯性的使用global a聲明變量。
當只讀取全局變量,不試圖修改時,不會出現此問題。

a = 3   # 模塊中的全局變量

def add():
    global a
    a = a + 3
    print(a)

而對於列表、字典這種可變類型的全局變量,則沒有這種問題,不聲明global也可以正常讀寫。

l = []   # 模塊中的全局變量

def add():
    l.append(1)
    print(l)

調用其他函數

函數中可以調用其他函數,如假設我們要設計一個函數,計算(x+y)*2的結果,

def add(x, y):
    return x + y

def mul(x, y):
    return x * y

def func(x,y):
    sum = add(x,y)  # 調用加法函數
    result = mul(sum, 2)   # 調用乘法函數
    return result

同樣可以導入和調用其他模塊的函數。

匿名函數

當我們需要臨時使用一個操作比較簡單的函數時,可以使用匿名函數:lambda表達式,格式如下。

lambda 參數: 返回值

當然,匿名函數也可以賦值給一個變量,稱為"具名函數"。
如,加法函數用lambda表達式可以表述為:

add = lamda x,y: x+y

高階函數

上面提到,函數的參數可以是數字字、字符串、列表、字典,同樣也可以是函數對象。已函數為參數的函數稱為高階函數
如下的函數用於顯示一個函數的信息。

def info(func):
    print('函數名稱:', func.__name__)
    print('函數描述:', func.__doc__)

使用方式為

def add(x,y):
    """加法函數"""
    return x + y

info(add) 

打印結果為

函數名稱: add
函數描述: 加法函數

裝飾器

裝飾器也是一種典型的以函數為參數的函數,裝飾器旨在通過包裝,來為函數來增加響應的功能。
如上例中,可以直接使用@info為add函數加上裝飾器。

@info
def add(x,y):
    """加法函數"""
    return x + y

在調用函數add時會自動打印相應的函數信息。

常用高階函數

map, filter和reduce是Python中常用的3個高階函數。

map用於使用一個函數,對一個序列進行批量操作,示例如下。

add1 = lambda x: x + 1   # 處理函數,由於一次處理一個,所有只能有一個參數
data = [1, 3, 5, 7, 9]
new_data = list(map(add1, data)   # map(add1,  data)  實際上是一個生成器,不會自動執行,必須遍歷或者轉成列表才會執行
print(new_data)  # 得到列表 [2, 4, 8 , 10]

filter使用一個函數來過濾數據。
當某個數據傳入函數時返回非"假"值(Python中False,None,0, '0', '',[], {}, (,)都被視為假),則保留。否則拋棄。示例如下。

is_even = lambda x: x % 2 == 0   #  過濾函數,x是偶數是 x對2取模==0,返回True,奇數時不等於0,返回False。
data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
new_data = list(filter(is_even, data))   # filter同樣是一個生成器,需要轉列表才會執行。
print(new_data)  # 得到列表 [2, 4, 6, 8]

reduce使用一個函數,對序列進行累積操作,不同的是,這個函數接受兩個參數,操作完將結果作為下一輪的第一個參數,再讀入下一個參數。
如累加[1, 2, 3, 4, 5,6 ,7, 8, 9, 10],先傳入add(1,2)得到和3,第二輪結果3作為第一個參數,再傳入下一個參數3得到add(3,3)結果6,下一輪則為add(6,4)...
使用reduce函數的方式如下。

from fuctools import reduce  # 不同於map/filter,reduce需要導入方可使用

add = lambda x,y: x + y
data = 1, 2, 3, 4, 5,6 ,7, 8, 9, 10]
result = reduce(add, data)  # reduce調用即執行,返回序列操作完的最后結果
print(result)  # 得到 55

函數嵌套

同在模塊(Python腳本)中定義函數一樣,在函數內部也可以定義函數,這種稱為內部函數或者閉包
這種操作的好處是,內部函數可以使用外部函數中的參數。

ddef check(add):  # 外部函數,接受一個add函數

    def new_add(x, y):  # 內部函數
        if not isinstance(x, int) or not isinstance(y, int):   # 參數類型校驗
            raise TypeError('x,y兩個參數必須是整數類型')
        result = add(x,y)   # 可以使用外部函數參數add並得到結果
        # return result

    return new_add   # 返回替換后的new_add函數,具有函數add的功能,還加了參數檢查功能

這樣便得到一個參數類型,檢查裝飾器@check
操作流程為

graph LR A[原add函數]-->B([check裝飾器]) B-->C[同樣功能的new_add函數]

使用方式如下

@check
def add(x, y):
    return x + y

當調用add(3,5)可以正常返回,當調用add('a', 5)就會拋出類型錯誤。

嵌套的內部函數可以訪問外部函數的參數,但是外部函數無法訪問內部函數的參數。
如果想在內部函數內聲明一個具有外部函數范圍的參數可以使用nolocal關鍵字聲明其為自由變量。

遞歸函數

函數中可以調用其他函數,同樣也可以調用自身這個函數。
調用自身的函數稱為遞歸函數
遞歸常用於使用同樣的邏輯進行推導運算。
有一種稱為"動態規划"的逆向推導算法,如計算N的階乘。
我們不從開頭進行逐步計算,而從結果處進行逆向推導。

  • 如果我有上一個數N-1的階乘結果,則我只要乘以N就行,然后問題交給N-1
  • N-1層也覺得,如果我有上一個數N-2的結果,則我只要曾以N-1就好,然后問題交給N-2層
  • ...
  • 推到最后一層,1,我的階乘為1,然后逐層向上返回就得到了N的階乘

這種算法有3個要點:

  1. 必須有出口條件,如最后一層1,有明確的結果1。
  2. 每層只負責乘以本層數字,調用自己推給下一層
  3. 整個推導過程要逐步趨於出口

具體實現代碼如下。

def factorial(n):
    if n < =1:   # 出口條件
        return 1
    else 
        return n * factorial(n-1)   # 本層乘以n,然后遞歸調用自身處理下一層


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM