模塊4:常用數據結構 (Organizing Lots of Data)
在前面的模塊中,我們學習了如何使用變量來存儲單個數據,比如一個數字、一個名字或一個布爾值。但很多時候,我們需要處理一組相關的數據,比如班級里所有學生的名字、一本書的所有章節標題、或者一個用戶的各項配置信息。這時,就需要用到數據結構(Data Structures),它們是 Python 中用來組織和存儲多個數據項的方式。
這個模塊我們將學習 Python 中最常用的四種內置數據結構:列表(List)、元組(Tuple)、字典(Dictionary)和集合(Set)。
4.1 列表 (List): 最常用的“數據清單”
列表是 Python 中最常用、最靈活的數據結構。你可以把它想象成一個有序的、可以修改的清單。
- 有序 (Ordered): 列表中的每個元素都有一個固定的位置(索引),就像排隊一樣,順序很重要。
- 可變 (Mutable): 你可以在創建列表后,隨時添加、刪除或修改里面的元素。
1. 創建列表
- 使用方括號
[]
,元素之間用逗號,
分隔。 - 列表可以包含任何類型的元素,甚至混合類型。
- 創建一個空列表:
[]
。
Python
# 一個包含數字的列表
numbers = [1, 2, 3, 4, 5]
print(numbers)# 一個包含字符串的列表
fruits = ["apple", "banana", "cherry"]
print(fruits)# 一個混合類型的列表
mixed_list = [10, "hello", 3.14, True, "banana"]
print(mixed_list)# 一個空列表
empty_list = []
print(empty_list)
2. 訪問列表元素 (Indexing)
- 和字符串一樣,通過索引訪問列表中的元素,索引從
0
開始。 - 也可以使用負數索引,
-1
表示最后一個元素,-2
表示倒數第二個,以此類推。
Python
colors = ["red", "green", "blue", "yellow"]first_color = colors[0] # 'red'
second_color = colors[1] # 'green'
last_color = colors[-1] # 'yellow'
second_last = colors[-2] # 'blue'print(f"第一個顏色是: {first_color}")
print(f"最后一個顏色是: {last_color}")# 如果索引超出范圍,會報錯 IndexError
# print(colors[4]) # 會導致 IndexError
3. 列表切片 (Slicing)
- 和字符串一樣,使用
[start:stop:step]
獲取列表的一部分(子列表)。 - 包含
start
索引處的元素,但不包含stop
索引處的元素。 step
是可選的步長。
Python
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]# 獲取索引 1 到 4 (不含 4) 的元素
sub_list1 = numbers[1:4] # [1, 2, 3]
print(sub_list1)# 獲取從索引 5 到末尾的元素
sub_list2 = numbers[5:] # [5, 6, 7, 8, 9]
print(sub_list2)# 獲取從開頭到索引 3 (不含 3) 的元素
sub_list3 = numbers[:3] # [0, 1, 2]
print(sub_list3)# 獲取所有元素的一個副本
copy_list = numbers[:] # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(copy_list)# 獲取步長為 2 的元素 (隔一個取一個)
step_list = numbers[0:10:2] # [0, 2, 4, 6, 8]
print(step_list)
4. 修改列表元素
因為列表是可變的,你可以直接通過索引來修改元素的值。
Python
my_list = [10, 20, 30, 40]
print(f"原始列表: {my_list}")# 修改索引為 1 的元素
my_list[1] = 25
print(f"修改后列表: {my_list}") # 輸出: [10, 25, 30, 40]
5. 添加元素
append(item)
: 在列表末尾添加一個元素。insert(index, item)
: 在指定的索引位置插入一個元素,原來的元素及后面的元素會向后移動。
Python
hobbies = ["reading", "swimming"]
print(f"初始愛好: {hobbies}")# 在末尾添加
hobbies.append("coding")
print(f"添加后: {hobbies}") # ['reading', 'swimming', 'coding']# 在索引 1 的位置插入
hobbies.insert(1, "hiking")
print(f"插入后: {hobbies}") # ['reading', 'hiking', 'swimming', 'coding']
6. 刪除元素
pop(index)
: 刪除并返回指定索引位置的元素。如果省略index
,默認刪除并返回最后一個元素。remove(value)
: 刪除列表中第一個出現的指定值。如果值不存在,會報錯ValueError
。del
語句:通過索引刪除元素或切片。
Python
items = ["pen", "pencil", "eraser", "ruler", "pencil"]
print(f"原始物品: {items}")# 刪除并獲取最后一個元素
last_item = items.pop()
print(f"被 pop 的元素: {last_item}") # 'pencil'
print(f"pop 后列表: {items}") # ['pen', 'pencil', 'eraser', 'ruler']# 刪除索引為 1 的元素
removed_item = items.pop(1)
print(f"被 pop(1) 的元素: {removed_item}") # 'pencil'
print(f"pop(1) 后列表: {items}") # ['pen', 'eraser', 'ruler']# 刪除第一個值為 "eraser" 的元素
items.remove("eraser")
print(f"remove 'eraser' 后列表: {items}") # ['pen', 'ruler']
# 如果 items.remove("notebook") 會報錯 ValueError,因為 "notebook" 不存在# 使用 del 刪除索引為 0 的元素
del items[0]
print(f"del items[0] 后列表: {items}") # ['ruler']# del 也可以刪除切片
numbers = [1, 2, 3, 4, 5, 6]
del numbers[1:4] # 刪除索引 1, 2, 3 的元素
print(f"del 切片后列表: {numbers}") # [1, 5, 6]
7. 常用列表方法
sort()
: 對列表進行原地排序(直接修改原列表)。默認升序。如果列表元素不能互相比較(如數字和字符串混合),會報錯TypeError
。reverse()
: 將列表中的元素原地反轉。len(list)
: (這是一個內置函數,不是方法) 返回列表中的元素個數。count(value)
: 返回列表中某個值出現的次數。index(value)
: 返回列表中某個值首次出現的索引。如果值不存在,會報錯ValueError
。
Python
nums = [5, 1, 4, 2, 3, 1]
chars = ['c', 'a', 'b']
print(f"原始 nums: {nums}")
print(f"原始 chars: {chars}")# 獲取長度
print(f"nums 的長度: {len(nums)}") # 6# 計數
print(f"nums 中 1 出現的次數: {nums.count(1)}") # 2# 查找索引
print(f"nums 中 4 的索引: {nums.index(4)}") # 2# 排序 (原地修改)
nums.sort()
chars.sort()
print(f"排序后 nums: {nums}") # [1, 1, 2, 3, 4, 5]
print(f"排序后 chars: {chars}") # ['a', 'b', 'c']# 降序排序
nums.sort(reverse=True)
print(f"降序排序后 nums: {nums}") # [5, 4, 3, 2, 1, 1]# 反轉 (原地修改)
chars.reverse()
print(f"反轉后 chars: {chars}") # ['c', 'b', 'a']
8. 列表推導式 (List Comprehensions) - 初步認識
列表推導式提供了一種更簡潔、更高效的方式來創建列表,特別是基于現有列表或范圍創建新列表時。
基本語法: [expression for item in iterable if condition]
expression
: 對item
進行處理的表達式,結果將是新列表的元素。for item in iterable
: 循環遍歷一個可迭代對象(如列表、range()
等)。if condition
: (可選) 只有當condition
為True
時,item
才會被處理并添加到新列表中。
示例:
Python
# 1. 創建一個 0 到 9 的平方數的列表
squares = []
for x in range(10):squares.append(x**2)
print(f"傳統方法創建平方數列表: {squares}")# 使用列表推導式
squares_comp = [x**2 for x in range(10)]
print(f"列表推導式創建平方數列表: {squares_comp}")# 2. 創建一個 0 到 9 中偶數的列表
evens = []
for x in range(10):if x % 2 == 0:evens.append(x)
print(f"傳統方法創建偶數列表: {evens}")# 使用列表推導式
evens_comp = [x for x in range(10) if x % 2 == 0]
print(f"列表推導式創建偶數列表: {evens_comp}")# 3. 將一個字符串列表中的所有單詞轉為大寫
words = ["hello", "world", "python"]
upper_words = [word.upper() for word in words]
print(f"單詞轉大寫: {upper_words}") # ['HELLO', 'WORLD', 'PYTHON']
列表推導式非常強大且常用,剛開始可能覺得有點抽象,但多練習幾次就會發現它的便利。
4.2 元組 (Tuple): 不可變的“數據清單”
元組和列表非常相似,也是有序的序列。但它們之間有一個關鍵區別:元組是不可變的 (Immutable)。一旦創建,你就不能修改元組中的元素(不能添加、刪除或更改)。
1. 創建元組
- 使用圓括號
()
,元素之間用逗號,
分隔。 - 注意: 創建只包含一個元素的元組時,必須在該元素后面加上逗號
,
,否則 Python 會把它當作普通的值。 - 創建空元組:
()
。 - 在某些情況下,括號可以省略(比如賦值時),Python 也能識別它是元組。
Python
# 一個包含數字的元組
numbers_tuple = (1, 2, 3)
print(numbers_tuple)# 一個混合類型的元組
mixed_tuple = (10, "hello", 3.14)
print(mixed_tuple)# 創建單元素元組 - 注意逗號!
single_tuple = (99,)
not_a_tuple = (99)
print(f"這是一個元組: {single_tuple}, 類型: {type(single_tuple)}")
print(f"這不是元組: {not_a_tuple}, 類型: {type(not_a_tuple)}") # 這是 int 類型# 空元組
empty_tuple = ()
print(empty_tuple)# 省略括號創建元組
point = 10, 20 # 這也是一個元組 (10, 20)
print(point)
print(type(point))
2. 訪問元組元素 (Indexing & Slicing)
- 和列表、字符串完全一樣,使用索引
[]
和切片[:]
。
Python
my_tuple = ('a', 'b', 'c', 'd', 'e')
print(my_tuple[0]) # 'a'
print(my_tuple[-1]) # 'e'
print(my_tuple[1:3]) # ('b', 'c')
3. 不可變性 (Immutability)
這是元組的核心特性。嘗試修改元組會引發 TypeError
。
Python
immutable_tuple = (1, 2, 3)
# 下面的代碼會報錯 TypeError: 'tuple' object does not support item assignment
# immutable_tuple[0] = 100# 下面的代碼也會報錯 AttributeError: 'tuple' object has no attribute 'append'
# immutable_tuple.append(4)# 下面的代碼也會報錯 AttributeError: 'tuple' object has no attribute 'remove'
# immutable_tuple.remove(1)
4. 為什么使用元組?
既然列表那么靈活,為什么還需要不可變的元組呢?
-
性能: 元組通常比列表占用更少的內存,并且在某些操作上(如迭代訪問)可能稍微快一點(盡管差異通常很小)。
-
安全性/數據保護: 不可變性確保了數據在創建后不會被意外修改,適用于存儲不應改變的數據,如坐標點
(x, y)
、RGB顏色值(r, g, b)
等。 -
可以作為字典的鍵: 因為元組是不可變的,所以它可以作為字典的鍵(我們馬上會學到字典),而列表不行。
-
元組解包 (Tuple Unpacking): 可以方便地將元組中的元素賦值給多個變量。
Pythoncoordinates = (10, 20, 30) x, y, z = coordinates # 解包 print(f"x={x}, y={y}, z={z}") # x=10, y=20, z=30
4.3 字典 (Dictionary / dict
): 鍵值對應的“查找表”
字典是另一種非常有用的數據結構,它存儲的是鍵值對 (Key-Value Pairs)。你可以把它想象成一本真實的字典或電話簿:
- 鍵 (Key): 就像字典里的單詞或電話簿里的名字,用來查找信息。鍵必須是唯一的、不可變的 (通常使用字符串、數字或元組)。
- 值 (Value): 就像單詞的釋義或人對應的電話號碼,是與鍵相關聯的數據。值可以是任何數據類型,也可以重復。
字典在 Python 3.7+ 版本中是按插入順序存儲的,但在更早的版本中是無序的。字典查找速度非常快,特別適合需要通過某個唯一標識來快速獲取對應信息的場景。
1. 創建字典
- 使用花括號
{}
,鍵值對之間用逗號,
分隔,鍵和值之間用冒號:
分隔。 - 創建空字典:
{}
。
Python
# 一個存儲學生信息的字典
student = {"name": "Alice","age": 20,"major": "Computer Science","is_graduated": False,"courses": ["Math", "Physics", "Programming"] # 值可以是列表
}
print(student)# 一個存儲商品價格的字典
prices = {"apple": 5.5,"banana": 3.0,"orange": 4.5
}
print(prices)# 一個空字典
empty_dict = {}
print(empty_dict)
2. 訪問字典的值
- 使用鍵放在方括號
[]
中來訪問對應的值。如果鍵不存在,會引發KeyError
。 - 使用
get(key, default=None)
方法訪問值。如果鍵不存在,它會返回None
(或者你指定的default
值),而不會報錯。這通常是更安全的方式。
Python
student = {"name": "Bob", "age": 22, "major": "Physics"}# 使用方括號訪問
print(f"姓名: {student['name']}") # Bob
print(f"年齡: {student['age']}") # 22# 嘗試訪問不存在的鍵會報錯 KeyError
# print(student['city'])# 使用 get() 方法訪問
print(f"專業: {student.get('major')}") # Physics
print(f"城市: {student.get('city')}") # None (鍵不存在,返回 None)
print(f"城市 (帶默認值): {student.get('city', 'Unknown')}") # Unknown (鍵不存在,返回指定的默認值 'Unknown')
3. 添加和修改鍵值對
- 直接給一個新鍵賦值,就可以添加新的鍵值對。
- 給一個已存在的鍵賦值,就會修改該鍵對應的值。
Python
student = {"name": "Charlie", "age": 19}
print(f"原始字典: {student}")# 修改 age
student['age'] = 20
print(f"修改年齡后: {student}")# 添加新的鍵值對 city
student['city'] = "London"
print(f"添加城市后: {student}") # {'name': 'Charlie', 'age': 20, 'city': 'London'}
4. 刪除鍵值對
pop(key)
: 刪除指定鍵的鍵值對,并返回對應的值。如果鍵不存在,會報錯KeyError
。del
語句:通過鍵刪除鍵值對。如果鍵不存在,也會報錯KeyError
。
Python
contact = {"name": "David", "phone": "123-456", "email": "david@example.com"}
print(f"原始聯系人: {contact}")# 刪除 email 并獲取其值
removed_email = contact.pop("email")
print(f"被 pop 的 email: {removed_email}") # david@example.com
print(f"pop email 后: {contact}") # {'name': 'David', 'phone': '123-456'}# 使用 del 刪除 phone
del contact["phone"]
print(f"del phone 后: {contact}") # {'name': 'David'}# 嘗試刪除不存在的鍵會報錯
# del contact["address"] # KeyError
# contact.pop("city") # KeyError
5. 常用字典方法
keys()
: 返回一個包含所有鍵的“視圖對象”(可以像列表一樣遍歷)。values()
: 返回一個包含所有值的“視圖對象”。items()
: 返回一個包含所有**(鍵, 值)**元組對的“視圖對象”。
Python
student = {"name": "Eve", "age": 21, "major": "Biology"}# 獲取所有鍵
keys = student.keys()
print(f"所有鍵: {keys}") # dict_keys(['name', 'age', 'major'])
print(list(keys)) # 可以轉換為列表 ['name', 'age', 'major']# 獲取所有值
values = student.values()
print(f"所有值: {values}") # dict_values(['Eve', 21, 'Biology'])
print(list(values)) # 可以轉換為列表 ['Eve', 21, 'Biology']# 獲取所有鍵值對
items = student.items()
print(f"所有項: {items}") # dict_items([('name', 'Eve'), ('age', 21), ('major', 'Biology')])
print(list(items)) # 可以轉換為列表 [('name', 'Eve'), ('age', 21), ('major', 'Biology')]
6. 遍歷字典
有幾種常用的方式來遍歷字典:
Python
student = {"name": "Frank", "age": 23, "major": "Chemistry"}# 遍歷鍵 (默認方式)
print("\n遍歷鍵:")
for key in student:print(f"鍵: {key}, 值: {student[key]}") # 通過鍵再次訪問值# 遍歷值
print("\n遍歷值:")
for value in student.values():print(value)# 遍歷鍵值對 (推薦方式)
print("\n遍歷鍵值對 (使用 .items()):")
for key, value in student.items(): # 使用元組解包print(f"{key}: {value}")
4.4 集合 (Set): 無序且唯一的“元素包”
集合是一個無序的、包含不重復元素的集合。你可以把它想象成一個袋子,里面裝的東西沒有順序,而且同樣的東西只能裝一個。
集合的主要用途:
- 去重 (Removing Duplicates): 快速去除列表或其他序列中的重復元素。
- 成員檢測 (Membership Testing): 快速判斷一個元素是否存在于集合中(比在列表中查找快得多)。
- 集合運算 (Set Operations): 進行數學上的集合運算,如交集、并集、差集等。
1. 創建集合
- 使用花括號
{}
,元素之間用逗號,
分隔。 - 注意: 創建空集合必須使用
set()
函數,因為{}
創建的是空字典! - 可以直接從列表或其他可迭代對象創建集合,它會自動去重。
Python
# 創建一個包含數字的集合 (重復的 3 會被忽略)
numbers_set = {1, 2, 3, 4, 3, 2}
print(numbers_set) # 輸出可能是 {1, 2, 3, 4} (順序不固定)# 創建一個包含字符串的集合
fruits_set = {"apple", "banana", "cherry"}
print(fruits_set)# 創建空集合 - 必須用 set()
empty_set = set()
print(empty_set)
print(type(empty_set)) # <class 'set'># 從列表創建集合 (自動去重)
my_list = [1, 1, 2, 3, 4, 4, 4, 5]
my_set_from_list = set(my_list)
print(my_set_from_list) # {1, 2, 3, 4, 5}
2. 無序性
集合不保證元素的順序,所以你不能像列表或元組那樣使用索引來訪問元素。
Python
my_set = {10, 20, 30}
# 下面的代碼會報錯 TypeError: 'set' object is not subscriptable
# print(my_set[0])
3. 唯一性
集合中不允許有重復的元素。如果你嘗試添加一個已存在的元素,集合不會發生任何變化。
4. 添加和刪除元素
add(element)
: 添加一個元素到集合中。如果元素已存在,則什么也不做。remove(element)
: 從集合中刪除一個元素。如果元素不存在,會引發KeyError
。discard(element)
: 從集合中刪除一個元素。如果元素不存在,它不會報錯,而是什么也不做。通常discard
更安全。
Python
colors = {"red", "green"}
print(f"原始集合: {colors}")# 添加元素
colors.add("blue")
print(f"添加 blue 后: {colors}")colors.add("red") # 嘗試添加已存在的元素
print(f"再次添加 red 后: {colors}") # 集合不變# 刪除元素
colors.remove("green")
print(f"刪除 green 后: {colors}")# 嘗試 remove 不存在的元素會報錯
# colors.remove("yellow") # KeyError# 使用 discard 刪除存在的元素
colors.discard("blue")
print(f"discard blue 后: {colors}")# 使用 discard 刪除不存在的元素 (不會報錯)
colors.discard("yellow")
print(f"discard yellow 后: {colors}") # 集合不變
5. 集合運算
集合支持標準的數學集合運算:
- 成員檢測 (
in
): 判斷元素是否在集合中(非常高效)。 - 并集 (
|
或union()
): 返回包含兩個集合中所有元素的新集合。 - 交集 (
&
或intersection()
): 返回兩個集合中共同存在的元素組成的新集合。 - 差集 (
-
或difference()
): 返回存在于第一個集合但不在第二個集合中的元素組成的新集合。 - 對稱差集 (
^
或symmetric_difference()
): 返回存在于兩個集合中,但不同時存在于兩個集合中的元素(即并集減去交集)。
Python
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
print(f"集合 A: {set_a}")
print(f"集合 B: {set_b}")# 成員檢測
print(f"2 在 A 中嗎? {2 in set_a}") # True
print(f"5 在 A 中嗎? {5 in set_a}") # False# 并集
union_set = set_a | set_b
# union_set = set_a.union(set_b)
print(f"并集 A | B: {union_set}") # {1, 2, 3, 4, 5, 6}# 交集
intersection_set = set_a & set_b
# intersection_set = set_a.intersection(set_b)
print(f"交集 A & B: {intersection_set}") # {3, 4}# 差集 (A 中有,B 中沒有)
difference_set = set_a - set_b
# difference_set = set_a.difference(set_b)
print(f"差集 A - B: {difference_set}") # {1, 2}# 差集 (B 中有,A 中沒有)
print(f"差集 B - A: {set_b - set_a}") # {5, 6}# 對稱差集 (只在 A 或只在 B 中的元素)
sym_diff_set = set_a ^ set_b
# sym_diff_set = set_a.symmetric_difference(set_b)
print(f"對稱差集 A ^ B: {sym_diff_set}") # {1, 2, 5, 6}
4.5 實踐時間!
練習1:簡單的學生成績管理
- 創建一個列表,名為
students
。這個列表將用來存儲多個學生的信息。 - 列表中的每個元素都是一個字典,代表一個學生。每個學生字典至少包含以下鍵:
"name"
: 學生姓名 (字符串)"grades"
: 一個包含該學生各科成績(數字)的列表。- 例如:
{"name": "Alice", "grades": [85, 90, 78]}
- 向
students
列表中添加至少3個學生的信息。 - 編寫代碼,計算并打印出每個學生的平均成績。你需要遍歷
students
列表,對于每個學生字典,再遍歷其"grades"
列表來計算總分和平均分。 - (可選)添加一個新功能:允許用戶輸入學生姓名,然后查找并打印該學生的成績列表和平均分。如果學生不存在,則打印提示信息。
練習2:詞頻統計
- 定義一個包含一段文本的字符串變量(比如一句或幾句話)。
- 預處理文本:
- 將文本全部轉換為小寫(使用字符串的
lower()
方法)。 - (可選,簡化處理)去除標點符號。你可以用
replace()
方法將常見的標點符號(如.
,,
,?
,!
)替換為空格或空字符串。
- 將文本全部轉換為小寫(使用字符串的
- 分詞: 使用字符串的
split()
方法將處理后的文本分割成一個單詞列表。默認情況下,split()
會按空格分割。 - 統計詞頻:
- 創建一個空字典,名為
word_counts
,用來存儲每個單詞出現的次數。 - 遍歷你的單詞列表。
- 對于列表中的每個單詞:
- 檢查這個單詞是否已經是
word_counts
字典的鍵。 - 如果是,將該鍵對應的值(計數)加 1。
- 如果不是,將這個單詞作為新鍵添加到字典中,并將值設為 1。
- (提示:使用
get(word, 0)
可以很方便地獲取當前計數,如果單詞不存在則返回0,然后加1即可:word_counts[word] = word_counts.get(word, 0) + 1
)
- 檢查這個單詞是否已經是
- 創建一個空字典,名為
- 遍歷
word_counts
字典,打印出每個單詞及其出現的次數。
模塊5:函數——代碼的復用
在我們之前的編程練習中,你可能已經注意到,有時我們會重復寫幾乎完全一樣的代碼塊。比如,計算平均分的邏輯、打印特定格式信息的代碼等。如果這樣的代碼很多,程序會變得冗長、難以閱讀,而且一旦需要修改這部分邏輯,你就得找到所有重復的地方一一修改,非常麻煩且容易出錯。
函數(Function)就是解決這個問題的利器!它可以讓你把一段具有特定功能的代碼打包起來,給它起一個名字,然后在需要執行這段代碼的任何地方,簡單地“調用”這個名字即可。
5.1 為什么需要函數?(DRY 原則)
使用函數的主要原因是為了遵循 DRY 原則:Don't Repeat Yourself(不要重復你自己)。
- 提高代碼復用性: 定義一次,多次調用。
- 提高代碼可讀性: 將復雜的任務分解成小的、有明確功能的函數,讓主程序邏輯更清晰。給函數起一個有意義的名字本身就是一種注釋。
- 提高代碼可維護性: 如果需要修改某項功能的邏輯,只需要修改對應的函數定義即可,所有調用該函數的地方都會自動生效。
- 方便協作: 不同的開發者可以分工編寫不同的函數模塊。
5.2 定義函數
使用 def
關鍵字來定義一個函數。
基本語法:
Python
def function_name(parameters):"""(可選) 函數文檔字符串 (Docstring) - 解釋函數的作用、參數和返回值。通常用三引號包裹。"""# 函數體 (縮進的代碼塊)# 這里是函數的具體邏輯statement1statement2# ...return value # (可選) 返回一個結果
def
: 定義函數的關鍵字。function_name
: 你給函數起的名字,遵循變量命名規則(snake_case)。()
: 函數名后面的圓括號,必須有。parameters
: (可選) 括號里的變量名,是函數接收的輸入值(參數),多個參數用逗號分隔。如果沒有參數,括號也是空的()
。:
: 圓括號后面必須有冒號。- Docstring: (可選但強烈推薦) 一個用三引號
"""Docstring goes here"""
包裹的字符串,用于解釋函數的功能。好的文檔字符串對于理解和使用函數至關重要。 - 函數體: 縮進的代碼塊,包含函數的實際邏輯。
return value
: (可選) 使用return
關鍵字將函數的結果(值)發送回調用它的地方。如果函數沒有return
語句,或者return
后面沒有值,它默認返回None
。
示例:一個簡單的問候函數
Python
def greet():"""打印一句簡單的問候語。"""print("Hello there! Welcome.")def greet_user(name):"""根據提供的名字打印個性化問候語。Args:name: 要問候的人的名字 (字符串)。"""print(f"Hello, {name}! Nice to meet you.")
5.3 調用函數
定義好函數后,你需要調用(Call 或 Invoke)它來執行函數體內的代碼。調用函數的方法是寫出函數名,后面跟上圓括號 ()
,并在括號內提供必要的參數(Arguments)。
Python
# 調用沒有參數的函數
greet() # 輸出: Hello there! Welcome.# 調用有參數的函數,需要提供參數值
greet_user("Alice") # 輸出: Hello, Alice! Nice to meet you.
greet_user("Bob") # 輸出: Hello, Bob! Nice to meet you.
當程序執行到函數調用時,它會“跳轉”到函數的定義處,執行函數體內的代碼,然后再“跳回”到函數被調用的地方繼續執行。
5.4 參數 (Parameters) 與 實參 (Arguments)
這兩個詞經常互換使用,但嚴格來說:
- 參數 (Parameters): 定義函數時,寫在圓括號里的變量名(如
greet_user
函數中的name
)。它們是函數內部使用的占位符。 - 實參 (Arguments): 調用函數時,傳遞給函數的實際值(如
greet_user("Alice")
中的"Alice"
)。
Python 支持多種傳遞參數的方式:
1. 位置參數 (Positional Arguments)
最常見的方式。實參會按照它們在調用時出現的順序,依次傳遞給函數定義中的參數。
Python
def describe_pet(animal_type, pet_name):"""顯示寵物的信息。"""print(f"I have a {animal_type}.")print(f"My {animal_type}'s name is {pet_name.title()}.")describe_pet("hamster", "harry") # "hamster" 傳給 animal_type, "harry" 傳給 pet_name
describe_pet("dog", "willie")
注意:位置參數的順序很重要,傳錯了會導致邏輯錯誤。 describe_pet("willie", "dog")
就會輸出錯誤的信息。
2. 關鍵字參數 (Keyword Arguments)
調用函數時,你可以明確指定哪個實參傳遞給哪個參數,使用 參數名=值
的形式。這時,實參的順序就不重要了。
Python
describe_pet(animal_type="cat", pet_name="whiskers")
describe_pet(pet_name="goldie", animal_type="fish") # 順序不同,但結果正確
注意:在一個函數調用中,關鍵字參數必須跟在所有位置參數之后。
3. 默認參數值 (Default Parameter Values)
在定義函數時,可以為參數指定一個默認值。如果在調用函數時沒有為該參數提供實參,那么就會使用這個默認值。
Python
def power(base, exponent=2): # exponent 參數默認值為 2"""計算 base 的 exponent 次方。"""result = base ** exponentprint(f"{base} 的 {exponent} 次方是 {result}")power(5) # 沒有提供 exponent,使用默認值 2。輸出: 5 的 2 次方是 25
power(3, 3) # 提供了 exponent,使用提供的值 3。輸出: 3 的 3 次方是 27
power(base=4) # 也可以用關鍵字參數調用
power(base=2, exponent=5)
注意:在函數定義中,所有帶默認值的參數必須放在所有不帶默認值的參數之后。
4. 可變參數 (Variable-Length Arguments) - 初步認識
有時你希望函數能接受任意數量的參數。
-
Python*args
(任意數量的位置參數): 在參數名前加一個星號*
。這會將調用時提供的多余的位置參數收集到一個元組 (tuple) 中。參數名通常約定俗成用args
,但也可以用別的名字(如*numbers
)。def make_pizza(size, *toppings):"""概述要制作的比薩。Args:size: 比薩的尺寸。*toppings: 一個包含所有配料的元組。"""print(f"\nMaking a {size}-inch pizza with the following toppings:")if toppings: # 檢查 toppings 元組是否為空for topping in toppings:print(f"- {topping}")else:print("- Plain cheese")make_pizza(12, "mushrooms") make_pizza(16, "mushrooms", "green peppers", "extra cheese") make_pizza(10) # toppings 會是一個空元組
-
Python**kwargs
(任意數量的關鍵字參數): 在參數名前加兩個星號**
。這會將調用時提供的多余的關鍵字參數收集到一個字典 (dict) 中。參數名通常約定俗成用kwargs
,但也可以用別的名字(如**user_info
)。def build_profile(first, last, **user_info):"""創建一個字典,包含我們知道的有關用戶的一切。Args:first: 名。last: 姓。**user_info: 包含其他信息的字典。"""profile = {}profile['first_name'] = firstprofile['last_name'] = lastfor key, value in user_info.items(): # 遍歷 kwargs 字典profile[key] = valuereturn profile # 返回構建好的字典user_profile = build_profile('albert', 'einstein',location='princeton',field='physics') print(user_profile) # 輸出: {'first_name': 'albert', 'last_name': 'einstein', 'location': 'princeton', 'field': 'physics'}user_profile2 = build_profile('marie', 'curie',field='physics', award='Nobel Prize') print(user_profile2)
注意:在函數定義中,參數的順序通常是:普通位置參數 -> 默認參數 -> *args
-> **kwargs
。對于初學者,理解 *args
和 **kwargs
的基本概念(收集多余參數)即可。
5.5 返回值 (Return Values)
函數不僅可以執行操作(比如打印),還可以計算一個結果并將其“返回”給調用它的代碼。這通過 return
語句實現。
- 當執行到
return
語句時,函數立即停止執行,并將return
后面的值作為結果返回。 - 調用函數的地方可以接收這個返回值,通常是賦給一個變量。
- 如果函數沒有
return
語句,或者return
后面沒有值,它默認返回特殊值None
。 - 一個函數可以有多個
return
語句(例如在不同的if
分支里),但只要執行到任何一個return
,函數就會結束。 - 可以返回任何類型的數據:數字、字符串、列表、字典,甚至另一個函數!
- 可以一次返回多個值,它們會被自動打包成一個元組。
Python
def add(x, y):"""計算兩個數的和并返回結果。"""result = x + yreturn resultsum_result = add(5, 3)
print(f"5 + 3 = {sum_result}") # 輸出: 5 + 3 = 8def get_name_parts(full_name):"""將全名分割成名和姓。"""parts = full_name.split() # 按空格分割if len(parts) >= 2:first_name = parts[0]last_name = " ".join(parts[1:]) # 處理可能有中間名的情況return first_name, last_name # 返回兩個值 (打包成元組)elif len(parts) == 1:return parts[0], None # 如果只有一個部分,姓氏返回 Noneelse:return None, None # 如果輸入為空,都返回 Nonefirst, last = get_name_parts("Albert Einstein")
print(f"First: {first}, Last: {last}") # First: Albert, Last: Einsteinfirst, last = get_name_parts("Marie Curie")
print(f"First: {first}, Last: {last}") # First: Marie, Last: Curiefirst, last = get_name_parts("Madonna")
print(f"First: {first}, Last: {last}") # First: Madonna, Last: Nonedef check_even(number):"""檢查數字是否為偶數。"""if number % 2 == 0:return True # 如果是偶數,返回 True 并結束函數# 如果上面的 if 不滿足,會執行到這里return Falseprint(f"4 是偶數嗎? {check_even(4)}") # True
print(f"7 是偶數嗎? {check_even(7)}") # Falsedef print_and_return_nothing(message):"""打印消息,但不顯式返回值。"""print(message)result_none = print_and_return_nothing("Hello") # 會打印 Hello
print(f"函數返回值: {result_none}") # 輸出: 函數返回值: None
5.6 函數的作用域 (Scope)
作用域指的是變量能夠被訪問的區域。
-
局部變量 (Local Variables): 在函數內部定義的變量(包括參數)是局部變量。它們只在函數被調用時創建,在函數執行結束時銷毀。局部變量只能在函數內部訪問,不能在函數外部訪問。
Pythondef my_function():local_var = 10 # 局部變量print(f"函數內部: {local_var}")my_function() # 輸出: 函數內部: 10 # 下面這行會報錯 NameError: name 'local_var' is not defined # print(f"函數外部: {local_var}")
-
全局變量 (Global Variables): 在所有函數外部定義的變量是全局變量。它們在程序的整個生命周期內都存在。全局變量可以在程序的任何地方(包括函數內部)被讀取。
Pythonglobal_var = 100 # 全局變量def read_global():print(f"函數內部讀取全局變量: {global_var}")def try_modify_global():# 試圖直接修改全局變量 (不推薦,且可能產生 UnboundLocalError)# global_var = global_var + 1 # 這會報錯,Python 認為你想創建一個新的局部變量print(f"函數內部嘗試修改前讀取: {global_var}") # 可以讀取read_global() # 輸出: 函數內部讀取全局變量: 100 try_modify_global() print(f"函數外部: {global_var}") # 輸出: 函數外部: 100 (全局變量未被修改)
-
修改全局變量 (
Pythonglobal
關鍵字): 如果確實需要在函數內部修改全局變量的值,需要使用global
關鍵字明確聲明。但通常不推薦這樣做,因為它會破壞函數的封裝性,使代碼更難理解和調試。更好的做法是將需要修改的值作為參數傳入,然后返回修改后的值。count = 0 # 全局變量def increment_global():global count # 聲明要修改的是全局變量 countcount += 1print(f"函數內 count: {count}")increment_global() # 輸出: 函數內 count: 1 increment_global() # 輸出: 函數內 count: 2 print(f"函數外 count: {count}") # 輸出: 函數外 count: 2
對于初學者,主要理解函數內部定義的變量是局部的,外部無法訪問即可。盡量避免使用 global
關鍵字。
5.7 匿名函數 (lambda
) - (可選/簡要了解)
有時你需要一個非常簡單的、只用一次的小函數,lambda
表達式提供了一種快速定義這種匿名函數(沒有名字的函數)的方式。
語法: lambda arguments: expression
lambda
: 關鍵字。arguments
: 和普通函數的參數一樣。:
: 分隔符。expression
: 一個表達式,它的計算結果就是函數的返回值(不需要return
關鍵字)。Lambda 函數體只能包含一個表達式。
Python
# 普通函數定義
def add_regular(x, y):return x + y# 等效的 lambda 函數
add_lambda = lambda x, y: x + yprint(add_regular(5, 3)) # 8
print(add_lambda(5, 3)) # 8# 直接使用 lambda
result = (lambda a, b: a * b)(6, 7) # 定義并立即調用
print(result) # 42# lambda 常用于需要傳入簡單函數的地方,比如排序的 key
points = [(1, 2), (3, 1), (5, -4), (0, 8)]
# 按每個元組的第二個元素排序
points.sort(key=lambda point: point[1])
print(points) # [(5, -4), (3, 1), (1, 2), (0, 8)]
Lambda 對于初學者來說不是必需掌握的,了解有這樣一種簡潔寫法即可,當你看到別人代碼里用 lambda
時能大概理解它的意思就行。
5.8 實踐時間!
練習1:將之前的練習改寫成函數形式
- 重構閏年判斷器:
- 定義一個函數
is_leap(year)
。 - 它接收一個年份
year
作為參數。 - 函數體包含之前判斷閏年的邏輯。
- 函數應該返回
True
(如果year
是閏年)或False
(如果不是)。 - 在主程序部分,獲取用戶輸入,調用
is_leap()
函數,并根據返回值打印結果。
- 定義一個函數
- 重構簡單計算器:
- 定義一個函數
calculate(num1, num2, operator)
。 - 接收兩個數字
num1
,num2
和一個代表運算符的字符串operator
('+'
,'-'
,'*'
,/
) 作為參數。 - 根據
operator
執行相應的計算。 - 返回計算結果。如果運算符無效或除數為零,可以考慮返回
None
或打印錯誤信息并返回None
。 - 在主程序部分,獲取用戶輸入的兩個數字和運算符,調用
calculate()
函數,并打印結果(如果結果不是None
)。
- 定義一個函數
練習2:編寫工具函數 - 計算平均值
- 定義一個函數
calculate_average(numbers)
。 - 它接收一個包含數字的列表
numbers
作為參數。 - 函數應該計算并返回列表中所有數字的平均值。
- 考慮邊界情況: 如果輸入的列表
numbers
是空的,直接計算平均值會報錯(除以零)。在函數開始時檢查列表是否為空 (if not numbers:
或者if len(numbers) == 0:
),如果為空,可以返回0
或者None
(并在文檔字符串中說明)。 - 測試你的函數:創建一個數字列表,調用
calculate_average()
并打印結果。也測試一下空列表的情況。