- Python基礎01-Python簡介
- Python基礎02-Python基本語法
- Python基礎03-基本數據類型
- Python基礎04-分支及循環
- Python基礎05-函數
- Python基礎07-類與對象
- Python基礎08-模塊及包
函數
數學中的函數指一種映射的變換關系,如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): ...
,這里的x
和y
便是形式參數,形式參數是函數內部使用的。
在調用函數時需要傳入實際的數據,如add(3,5)
,這里的3
和5
便是實際參數。實際參數也可以是預先定義好的變量,如。
a, b = 3, 5
add(a, b)
這里的a
和b
也是實際參數。
參數類型
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
操作流程為
使用方式如下
@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。
- 每層只負責乘以本層數字,調用自己推給下一層
- 整個推導過程要逐步趨於出口
具體實現代碼如下。
def factorial(n):
if n < =1: # 出口條件
return 1
else
return n * factorial(n-1) # 本層乘以n,然后遞歸調用自身處理下一層