第二阶段:数据结构与函数

模块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: (可选) 只有当 conditionTrue 时,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): 可以方便地将元组中的元素赋值给多个变量。

    Python

    coordinates = (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:简单的学生成绩管理

  1. 创建一个列表,名为 students。这个列表将用来存储多个学生的信息。
  2. 列表中的每个元素都是一个字典,代表一个学生。每个学生字典至少包含以下键:
    • "name": 学生姓名 (字符串)
    • "grades": 一个包含该学生各科成绩(数字)的列表
    • 例如:{"name": "Alice", "grades": [85, 90, 78]}
  3. students 列表中添加至少3个学生的信息。
  4. 编写代码,计算并打印出每个学生的平均成绩。你需要遍历 students 列表,对于每个学生字典,再遍历其 "grades" 列表来计算总分和平均分。
  5. (可选)添加一个新功能:允许用户输入学生姓名,然后查找并打印该学生的成绩列表和平均分。如果学生不存在,则打印提示信息。

练习2:词频统计

  1. 定义一个包含一段文本的字符串变量(比如一句或几句话)。
  2. 预处理文本:
    • 将文本全部转换为小写(使用字符串的 lower() 方法)。
    • (可选,简化处理)去除标点符号。你可以用 replace() 方法将常见的标点符号(如 ., ,, ?, !)替换为空格或空字符串。
  3. 分词: 使用字符串的 split() 方法将处理后的文本分割成一个单词列表。默认情况下,split() 会按空格分割。
  4. 统计词频:
    • 创建一个空字典,名为 word_counts,用来存储每个单词出现的次数。
    • 遍历你的单词列表。
    • 对于列表中的每个单词:
      • 检查这个单词是否已经是 word_counts 字典的
      • 如果是,将该键对应的值(计数)加 1。
      • 如果不是,将这个单词作为新键添加到字典中,并将值设为 1。
      • (提示:使用 get(word, 0) 可以很方便地获取当前计数,如果单词不存在则返回0,然后加1即可:word_counts[word] = word_counts.get(word, 0) + 1
  5. 遍历 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) - 初步认识

有时你希望函数能接受任意数量的参数。

  • *args (任意数量的位置参数): 在参数名前加一个星号 *。这会将调用时提供的多余的位置参数收集到一个元组 (tuple) 中。参数名通常约定俗成用 args,但也可以用别的名字(如 *numbers)。

    Python

    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 会是一个空元组
    
  • **kwargs (任意数量的关键字参数): 在参数名前加两个星号 **。这会将调用时提供的多余的关键字参数收集到一个字典 (dict) 中。参数名通常约定俗成用 kwargs,但也可以用别的名字(如 **user_info)。

    Python

    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): 在函数内部定义的变量(包括参数)是局部变量。它们只在函数被调用时创建,在函数执行结束时销毁。局部变量只能在函数内部访问,不能在函数外部访问。

    Python

    def 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): 在所有函数外部定义的变量是全局变量。它们在程序的整个生命周期内都存在。全局变量可以在程序的任何地方(包括函数内部)被读取。

    Python

    global_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 (全局变量未被修改)
    
  • 修改全局变量 (global 关键字): 如果确实需要在函数内部修改全局变量的值,需要使用 global 关键字明确声明。但通常不推荐这样做,因为它会破坏函数的封装性,使代码更难理解和调试。更好的做法是将需要修改的值作为参数传入,然后返回修改后的值。

    Python

    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:将之前的练习改写成函数形式

  1. 重构闰年判断器:
    • 定义一个函数 is_leap(year)
    • 它接收一个年份 year 作为参数。
    • 函数体包含之前判断闰年的逻辑。
    • 函数应该返回 True(如果 year 是闰年)或 False(如果不是)。
    • 在主程序部分,获取用户输入,调用 is_leap() 函数,并根据返回值打印结果。
  2. 重构简单计算器:
    • 定义一个函数 calculate(num1, num2, operator)
    • 接收两个数字 num1, num2 和一个代表运算符的字符串 operator ('+', '-', '*', /) 作为参数。
    • 根据 operator 执行相应的计算。
    • 返回计算结果。如果运算符无效或除数为零,可以考虑返回 None 或打印错误信息并返回 None
    • 在主程序部分,获取用户输入的两个数字和运算符,调用 calculate() 函数,并打印结果(如果结果不是 None)。

练习2:编写工具函数 - 计算平均值

  1. 定义一个函数 calculate_average(numbers)
  2. 它接收一个包含数字的列表 numbers 作为参数。
  3. 函数应该计算并返回列表中所有数字的平均值。
  4. 考虑边界情况: 如果输入的列表 numbers 是空的,直接计算平均值会报错(除以零)。在函数开始时检查列表是否为空 (if not numbers: 或者 if len(numbers) == 0:),如果为空,可以返回 0 或者 None(并在文档字符串中说明)。
  5. 测试你的函数:创建一个数字列表,调用 calculate_average() 并打印结果。也测试一下空列表的情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/901638.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++算法】61.字符串_最长公共前缀

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;解释 题目链接&#xff1a; 14. 最长公共前缀 题目描述&#xff1a; 解法 解法一&#xff1a;两两比较 先算前两个字符串的最长公共前缀&#xff0c;然后拿这个最长公共前缀和后面一个来比较&…

JVM 调优不再难:AI 工具自动生成内存优化方案

在 Java 应用程序的开发与运行过程中&#xff0c;Java 虚拟机&#xff08;JVM&#xff09;的性能调优一直是一项极具挑战性的任务&#xff0c;尤其是内存优化方面。不合适的 JVM 内存配置可能会导致应用程序出现性能瓶颈&#xff0c;甚至频繁抛出内存溢出异常&#xff0c;影响业…

纷析云开源财务软件:企业财务数字化转型的灵活解决方案

纷析云是一家专注于开源财务软件研发的公司&#xff0c;自2018年成立以来&#xff0c;始终以“开源开放”为核心理念&#xff0c;致力于通过技术创新助力企业实现财务管理的数字化与智能化转型。其开源财务软件凭借高扩展性、灵活部署和全面的功能模块&#xff0c;成为众多企业…

【数字图像处理】数字图像空间域增强(3)

图像锐化 图像细节增强 图像轮廓&#xff1a;灰度值陡然变化的部分 空间变化&#xff1a;计算灰度变化程度 图像微分法&#xff1a;微分计算灰度梯度突变的速率 一阶微分&#xff1a;单向差值 二阶微分&#xff1a;双向插值 一阶微分滤波 1&#xff1a;梯度法 梯度&#xff1…

基于Linux的ffmpeg python的关键帧抽取

1.FFmpeg的环境配置 首先强调&#xff0c;ffmpeg-python包与ffmpeg包不一样。 1) 创建一个虚拟环境env conda create -n yourenv python3.x conda activate yourenv2) ffmpeg-python包的安装 pip install ffmpeg-python3) 安装系统级别的 FFmpeg 工具 虽然安装了 ffmpeg-p…

C#进阶学习(四)单向链表和双向链表,循环链表(上)单向链表

目录 前置知识&#xff1a; 一、链表中的结点类LinkedNode 1、申明字段节点类&#xff1a; 2、申明属性节点类: 二、两种方式实现单向链表 ①定框架&#xff1a; ②增加元素的方法&#xff1a;因为是单链表&#xff0c;所以增加元素一定是只能在末尾添加元素&#xff0c;…

RK3588 Buildroot 串口测试工具

RK3588 Buildroot串口测试工具(含代码) 一、引言 1.1 目的 本文档旨在指导开发人员能快速测试串口功能 1.2 适用范围 本文档适用于linux 系统串口测试。 二、开发环境准备 2.1 硬件环境 开发板:RK3588开发板,确保其串口硬件连接正常,具备电源供应、调试串口等基本硬…

HOJ PZ

https://docs.hdoi.cn/deploy 单体部署 请到~/hoj-deploy/standAlone的目录下&#xff0c;即是与docker-compose.yml的文件同个目录下&#xff0c;该目录下有个叫hoj的文件夹&#xff0c;里面的文件夹介绍如下&#xff1a; hoj ├── file # 存储了上传的图片、上传的临…

EtherCAT 的优点与缺点

EtherCAT&#xff08;以太网控制自动化技术&#xff09;是一种高性能的工业以太网协议&#xff0c;广泛应用于实时自动化控制。以下是其核心优缺点分析&#xff1a; ​一、EtherCAT 的核心优点​ 1. ​超低延迟 & 高实时性​ ​原理​&#xff1a;采用"​Processing…

高并发多级缓存架构实现思路

目录 1.整体架构 3.安装环境 1.1 使用docket安装redis 1.2 配置redis缓存链接&#xff1a; 1.3 使用redisTemplate实现 1.4 缓存注解优化 1.4.1 常用缓存注解简绍 1.4.2 EnableCaching注解的使用 1.4.3使用Cacheable 1.4.4CachePut注解的使用 1.4.5 优化 2.安装Ngin…

Qt QML实现Windows桌面颜色提取器

前言 实现一个简单的小工具&#xff0c;使用Qt QML实现Windows桌面颜色提取器&#xff0c;实时显示鼠标移动位置的颜色值&#xff0c;包括十六进制值和RGB值。该功能在实际应用中比较常见&#xff0c;比如截图的时候&#xff0c;鼠标移动就会在鼠标位置实时显示坐标和颜色值&a…

vue3+vite 多个环境配置

同一套代码 再也不用在不同的环境里来回切换请求地址了 然后踩了一个坑 就是env的文件路径是在当前项目下 不是在views内 因为公司项目需求只有dev和pro两个环境 虽然我新增了3个 但是只在这两个里面配置了 .env是可以配置一些公共配置的 目前需求来说不需要 所以我也懒得配了。…

AI赋能PLC(一):三菱FX-3U编程实战初级篇

前言 在工业自动化领域&#xff0c;三菱PLC以其高可靠性、灵活性和广泛的应用场景&#xff0c;成为众多工程师的首选控制设备。然而&#xff0c;传统的PLC编程往往需要深厚的专业知识和经验积累&#xff0c;开发周期长且调试复杂。随着人工智能技术的快速发展&#xff0c;利用…

XSS 跨站Cookie 盗取表单劫持网络钓鱼溯源分析项目平台框架

漏洞原理&#xff1a;接受输入数据&#xff0c;输出显示数据后解析执行 基础类型&#xff1a;反射 ( 非持续 ) &#xff0c;存储 ( 持续 ) &#xff0c; DOM-BASE 拓展类型&#xff1a; jquery &#xff0c; mxss &#xff0c; uxss &#xff0c; pdfxss &#xff0c; flashx…

鸿蒙应用(医院诊疗系统)开发篇2·Axios网络请求封装全流程解析

一、项目初始化与环境准备 1. 创建鸿蒙工程 src/main/ets/ ├── api/ │ ├── api.ets # 接口聚合入口 │ ├── login.ets # 登录模块接口 │ └── request.ets # 网络请求核心封装 └── pages/ └── login.ets # 登录页面逻辑…

ADAS高级驾驶辅助系统详细介绍

ADAS&#xff08;高级驾驶辅助系统&#xff09;核心模块&#xff0c;通过 “监测→预警→干预” 三层逻辑提升行车安全。用户选择车辆时&#xff0c;可关注传感器配置&#xff08;如是否标配毫米波雷达&#xff09;、功能覆盖场景&#xff08;如 AEB 是否支持夜间行人&#xff…

Prometheus+Grafana+K8s构建监控告警系统

一、技术介绍 Prometheus、Grafana及K8S服务发现详解 Prometheus简介 Prometheus是一个开源的监控系统和时间序列数据库&#xff0c;最初由SoundCloud开发&#xff0c;现已成为CNCF(云原生计算基金会)的毕业项目‌。它专注于实时监控和告警&#xff0c;特别适合云原生和分布式…

MATLAB脚本实现了一个三自由度的通用航空运载器(CAV-H)的轨迹仿真,主要用于模拟升力体在不同飞行阶段(初始滑翔段、滑翔段、下压段)的运动轨迹

%升力体:通用航空运载器CAV-H %读取数据1 升力系数 alpha = [10 15 20]; Ma = [3.5 5 8 10 15 20 23]; alpha1 = 10:0.1:20; Ma1 = 3.5:0.1:23; [Ma1, alpha1] = meshgrid(Ma1, alpha1); CL = readmatrix(simulation.xlsx, Sheet, Sheet1, Range, B2:H4); CL1 = interp2(…

Day09【基于jieba分词和RNN实现的简单中文分词】

基于jieba分词和RNN实现的中文分词 目标数据准备主程序预测效果 目标 本文基于给定的中文词表&#xff0c;将输入的文本基于jieba分词分割为若干个词&#xff0c;词的末尾对应的标签为1&#xff0c;中间部分对应的标签为0&#xff0c;同时将分词后的单词基于中文词表做初步序列…

Linux-服务器添加审计日志功能

#查看audit软件是否在运行(状态为active而且为绿色表示已经在运行) systemctl start auditd #如果没有在运行的话,查看是否被系统禁用 (audit为0表示被禁用) cat /proc/cmdline | grep -w "audit=0" #修改/etc/default/grub里面audit=0 改为audit=1 #更新GRUB…