1 函數的定義
函數是一段具有特定功能的、可復用的語句組。python中函數用函數名來表示,並通過函數名進行功能調用。它是一種功能抽象,與黑盒類似,所以只要了解函數的輸入輸出方式即可,不用深究內部實現原理。函數的最突出優點是:
- 實現代碼復用:減少重復性工作
- 保證代碼一致:只需要修改該函數代碼,則所有調用均能受影響
在python中可以把函數分為:系統自帶函數、第三方庫函數、自定義函數。需要重點掌握的是「自定義函數」。
自定義函數
自定義函數語法:
def 函數名([參數列表]):
函數體
return語句
# 示例
def add1(x):
x = x + 1
return x
函數通過「參數」和「返回值」來傳遞信息,並通過「參數列表」和「return語句」實現對兩者的控制,詳見下圖:
注意事項:
- 函數定義時無需聲明形參類型(由調用時的實參類型確定);也無需指定返回值類型(由return語句確定)
- 自定義函數即使沒有任何參數,也必須保留一隊空括號()
- 括號后面的冒號(:)必不可少
- 函數體相對於def關鍵字必須有縮進關系
- python允許嵌套定義函數
- return語句作用是結束函數調用,並將結果返回給調用者
- return語句是可選的,可以出現在函數體任意位置
- 無return語句、有return語句沒有執行、有return語句而沒有返回值三種情況,函數都返回None
2 函數的調用
在定義好函數之后,有兩種方式對其進行調用:
- 從本文件調用:直接使用函數名 + 傳入參數,如add1(9)
- 從其他文件調用:這種方法有兩種實現手段
- 先指定文件路徑 + import 文件名,再用文件名.函數名(參數列表)調用
- 先指定文件路徑 + from 文件名 import 函數名,再用文件名.函數名(參數列表)調用
# 從本文件調用
def add1(x):
x = x+2
return x
add1(10)
# 從其他文件調用:從名為addx的文件調用已經定義好的add1函數
import os
os.chdir('D:\\data\\python_file')
# 從其他文件調用方法1
import addx
addx.add1(4)
# 從其他文件調用方法2
from addx import add1
add1(9)
3 函數的參數
3.1 形參與實參
從上面可知,函數最重要的三部分就是參數、函數體、返回值,而參數分為形參和實參:
- 形參:定義函數時,函數名后面圓括號中的變量
- 實參:調用函數時,函數名后面圓括號中的變量
注意事項:
- 形參只在函數內部有效,一個函數可以沒有形參,但必須有括號()
- 通常修改形參不影響實參;但如果傳遞給函數的是「可變序列」(列表、字典、集合),修改形參會影響實參
def printmax(a,b): # a, b是形參
if a>b:
print(a)
printmax(3,4) # 3, 4是實參
# 形參修改不影響實參
def add2(x):
x = x+2
return x
x = 10
print(add2(x))
print(x)
# 形參修改影響實參
def add2(x):
x.append(2)
return x
y = [1, 1] # 實參y是變序列(列表、字典、集合)
print(add2(y)) # 函數返回值
print(y) # 修改形參影響實參
3.2 參數的傳遞
在定義函數時無需指定形參類型,在調用函數時,python會根據實參類型來自動推斷。而定義函數和調用函數的過程,可以簡化為下面圖中三步,實質就是通過參數和返回值傳遞信息,而參數的傳遞發生在第一和第三步。

函數參數多種多樣,根據參數傳遞發生的先后順序,可以從兩個角度學習常見的一些參數:
- 定義函數(形參):默認值參數、可變參數
- 調用函數(實參):位置參數、關鍵字參數、命名關鍵字參數
同時,這些參數可以組合使用(可變參數無法和關鍵字參數組合),且參數定義的順序從左至右分別是:位置參數 >> 默認值參數 >> 可變參數 / 關鍵字參數 / 命名關鍵字參數。參數傳遞還有一種高級用法——參數傳遞的序列解包。
3.2.1 默認值參數
默認參數就是在調用函數的時候使用一些包含默認值的參數。
# 默認值參數b=5, c=10
def demo(a, b=5, c=10):
print(a, b, c)
demo(1, 2)
注意事項:
- 默認值參數必須出現在參數列表最右端
- 調用帶有默認值參數的函數時,可以對默認值參數進行賦值,也可以不賦值
- 默認值參數只能是不可變對象,使用可變序列作為參數默認值時,程序會有邏輯錯誤
- 可以使用 “函數名.defaults” 查看該函數所有默認參數的當前值
3.2.2 可變參數
可變參數就是允許在調用參數的時候傳入多個(≥0個)參數,可變參數分為兩種情況:
- 可變位置參數:定義參數時,在前面加一個*,表示這個參數是可變的,可以接受任意多個參數,這些參數構成一個「元組」,只能通過位置參數傳遞
- 可變關鍵字參數:定義參數時,在前面加**,表示這個參數可變,可以接受任意多個參數,這些參數構成一個「字典」,只能通過關鍵字參數傳遞
# 可變位置參數
def demo(*p):
print(p)
demo(1, 2, 3) # 參數在傳入時被自動組裝成一個元組
# 可變關鍵字參數
def demo(**p):
print(p)
demo(b='2', c='5', a='1') # 參數在傳入時被自動組裝成一個字典
3.2.3 位置參數
位置參數特點是調用函數時,要保證實參和形參的順序一致、數量相同。
# 位置參數
def demo(a, b, c):
print(a, b, c)
demo(1, 2, 3)
3.2.4 關鍵字參數
關鍵字參數允許在調用時以字典形式傳入0個或多個參數,且在傳遞參數時用等號(=)連接鍵和值。關鍵字參數最大優點,就是使實參順序可以和形參順序不一致,但不影響傳遞結果。
# 關鍵參數
def demo(a, b, c):
print(a, b, c)
demo(b=2, c=5, a=1) # 改變參數順序對結果不影響
3.2.5 命名關鍵字參數
命名關鍵字參數是在關鍵字參數的基礎上,限制傳入的的關鍵字的變量名。和普通關鍵字參數不同,命名關鍵字參數需要一個用來區分的分隔符*,它后面的參數被認為是命名關鍵字參數。
# 這里星號分割符后面的city、job是命名關鍵字參數
def person_info(name, age, *, city, job):
print(name, age, city, job)
person_info("Alex", 17, city="Beijing", job="Engineer")
3.2.6 參數傳遞的序列解包
參數傳遞的序列解包,是通過在實參序列前加星號(*)將其解包,然后按順序傳遞給多個形參。根據解包序列的不同,可以分為如下5種情況:
序列解包 | 示例 |
---|---|
列表的序列解包 | *[3,4,5] |
元組的序列解包 | *(3,4,5) |
集合的序列解包 | *{3,4,5} |
字典的鍵的序列解包 | 若字典為dic={'a':1,'b':2,'c':3},則解包代碼為:*dic |
字典的值的序列解包 | 若字典為dic={'a':1,'b':2,'c':3},則解包代碼為:*dic.values() |
注意事項:
- 對實參序列進行序列解包后,得到的實參值就變成了位置參數,要和形參一一對應
- 當序列解包和位置參數同時使用時,序列解包相當於位置參數,且會優先處理
- 序列解包不能在關鍵字參數解包之后,否則報錯
"""函數參數的序列解包"""
def demo(a, b, c):
print(a+b+c)
demo(*[3, 4, 5]) # 列表的序列解包
demo(*(3, 4, 5)) # 元組的序列解包
demo(*{3, 4, 5}) # 集合的序列解包
dic = {'a': 1, 'b': 2, 'c': 3}
demo(*dic) # 字典的鍵的序列解包
demo(*dic.values()) # 字典的值的序列解包
"""位置參數和序列解包同時使用"""
def demo(a, b, c):
print(a, b, c)
demo(*(1, 2, 3)) # 元組的序列解包
demo(1, *(2, 3)) # 位置參數和序列解包同時使用
demo(c=1, *(2, 3)) # 序列解包相當於位置參數,優先處理,正確用法
demo(*(3,), **{'c': 1, 'b': 2}) # 序列解包必須在關鍵字參數解包之前,正確用法
4 全局變量與局部變量
變量起作用的代碼范圍稱為「變量的作用域」。不同作用域內變量名可以相同,但互不影響。從變量作用的范圍分類,可以把變量分類為:
- 全局變量:指函數之外定義的變量,在程序執行全過程有效
- 局部變量:指在函數內部使用的變量,僅在函數內部有效,當函數退出時變量將不存在
需要特別指出的是,局部變量的引用比全局變量速度快,應考慮優先使用。
全局變量聲明
有兩種方式可以聲明全局變量:
- 方式一:在函數外聲明
- 方式二:在函數內部用global聲明,又分為兩種情況:
- 情況1:變量已在函數外定義,使用global聲明。若進行了重新賦值,則賦值結果會覆蓋原變量值
- 情況2:變量未在函數外定義,使用global在函數內部聲明,它將增加為新的全局變量
特殊情況,若局部變量和全局變量同名,那么全局變量會在局部變量的作用域內被隱藏掉,即在自定義函數內生效的是局部變量。
d = 2 # 全局變量
def func(a, b):
c = a*b
return c
func(2, 3)
def func(a, b):
c = a*b
d = 2 # 局部變量
return c
func(2, 3)
"""聲明的全局變量,已在函數外定義"""
n = 1
def func(a, b):
global n
n = b
c = a*b
return c
s = func("knock~", 2)
print(s, n)
"""聲明的全局變量,未在函數外定義,則新增"""
def func(a, b):
c = a*b
global d # 聲明d為全局變量
d = 2
return c
func(2, 3)
"""局部變量和全局變量同名,則全局變量在函數內會被隱藏"""
d = 10 # 全局變量d
def func(a, b):
d = 3 # 局部變量d
c = a+b+d
return c
func(1, 2)
d
5 lambda函數
lambda函數,又稱匿名函數,即沒有函數名字臨時使用的小函數。其語法如下:
lambda 函數參數:函數表達式
注意:
- 匿名函數只能有一個表達式
- 該表達式的結果,就是函數的返回值
- 不允許包含其他復雜語句,但表達式中可以調用其他函數
lambda函數的使用場景,主要在兩方面:
- 尤其適用於需要一個函數作為另一個函數參數的場合,比如排序
- 把匿名函數賦值給一個變量,再利用變量來調用該函數
def f(x, y, z):
return x+y+z # 位置參數
f(1, 2, 3)
def f1(x, y=10, z=10):
return x+y+z # 默認值參數
f(1)
"""把匿名函數賦值給一個變量,再利用變量來調用該函數,作用等價於自定義函數"""
f=lambda x,y,z:x+y+z
f(y=1,x=2,z=3) #關鍵值參數
L = ['ab', 'abcd', 'dfdfdg', 'a']
L.sort(key=lambda x: len(x)) # 按長度排序
L
L=[('小明',90,80),('小花',70,90),('小張',98,99)]
L.sort(key=lambda x:x[1],reverse=True) # 降序排序
L
6 遞歸
在函數內部,可以調用其他函數。如果一個函數在內部調用自身本身,這個函數就是遞歸函數。但不是的函數調用自己都是遞歸,遞歸有其自身的特性:
- 必須有一個明確的遞歸結束條件,稱為遞歸出口(基例),一般使用if……else結構控制遞歸的進入和退出
- 相鄰兩次重復之間有緊密的聯系,前一次要為后一次做准備(通常前一次的輸出就作為后一次的輸入)
- 每一次遞歸,整體問題都要比原來減小,並且遞歸到一定層次時,要能直接給出結果

從上圖可知,遞歸過程是函數調用自己,自己再調用自己,...,當某個條件得到滿足(基例)的時候就不再調用,然后再一層一層地返回,直到該函數的第一次調用。遞歸函數的優點是邏輯簡單清晰,缺點是過深的調用會導致棧溢出,在Python中,通常情況下,這個深度是1000層,超過將拋出異常。
案例:用遞歸實現階乘

# 案例一:用遞歸實現階乘
def fact(n):
if n==0: # 遞歸退出
return 1
else:
return n*fact(n-1) # 遞歸進入
fact(5)
# 案例二:實現字符串反轉
# 方法1,先轉成列表,調用列表的revers方法,再把列表轉成字符串
s = 'abcde'
l = list(s)
l.reverse()
''.join(l)
# 方法2,切片的方法
s = 'abcde'
s[::-1]
# 方法3,遞歸的方法
s = 'abc'
def reverse1(s):
if s == '':
return s
else:
print(s)
return reverse1(s[1:])+s[0]
reverse1(s)