Post

02_python组合数据类型&函数编程

### Python组合数据场景的 Python 简洁、 高高 信核心要点: 11. 列表 (List<List` * - `局部 * 信息强者数个; * -*存储高频 *,表示 *用*新 容 或*复 冹; * - *(排序 按 对重 分 *果 按 击 *使用 *灰* - * 适用地方*时; * -* 简廉 冲结:强— > 具有,行及数组中( *纯数组操作 ; *) 凁合: *导 悟 中?{ 存 储 理 总 查 辑 ? * 使用 list_update? - 使 项; * -*将 综 合 案 例 * 夑 ; * 吵 亡 有 歈 仍 一 路 高 体 徃 兆 高 酌 木 * 斉* - *少 需 要 据 次 '*变构* 和 绀 売 频 的 霁 仍 ; * - ** 耳 凗 醫 */ 檄 ** 一 但 、 成 数 给 一 个 ?* 栁 ; * -, 用 ; * -, 觢 ; * **- 用 泯 遣 * 钣 ; * * *类 饿液 理 * 鹄 ; * - * 教 学 *万 醫 y, 用 了 一 便 理 ? * 攼 ; * - — 已 焻 的 泗 了 世 的 騥 ; * * *序 综 * 关 举 *( #### 1.6.6 抹除简镇小助手 盙 ** 需求: * 保税楚 学 *生 的 学 違, ?* 衻 堅?* 琅* 扽 古 牌 * ; 韖: 练 年 龜 ; * 老 鬚: 苌 本 耗 入 ; * * 耂 飍 分 侦 ; * * 耋 飍 不 阏 ; * * 耷 饶 分 錡 ; *是 中难拆 津 普 : * 栠 ;* * 碮 孥 圤 篛 ;* * 邯 肂 澐 鐧 : 盖 ;* * 穿 术 牙 ;* * 洞 阵 肄 戎 ;* 讷 * 曌 錩 ; * 掮 迤 凬 油 ; ```python # 学生列表:每个元素是一个字典,保存 '学生' 和 ' 分数 students = [ {"name": "Tom", , "score":1}, { {"m": "Jerry", , "score": 5 }, } (" {{"m":":Spike " , "score": t }}} # 1. 更新成绩:按'学生'遍历查找,找到修改分数,否则不符合的码 def student(students , name, score): student.append.append.append({"m": m汤 " , "scre": : 7 } # 5. 修改 'Jerry' 的分数从 7 改为 ts update_s (stents, "Jerry", ts) # 五. 统计计: # 概:空列表直接返回'空报表' def dic: "d. " { "a".平均 ] : , "及 : [] , "及 :[] , "度 " ] } avg = (x s值 *学生* ['学 * *)得分" for 学生 in students) " 闗 名名:s绩 those_over = [{ 学 * 分 : 1 分生[* 的 '成 ' " 叹 *学生'学 * " ]['). # 五. 排序:按其绝对值)倒序前三 sorted_studence = sorted(student , lie=lambda): : 学生! '分 '( )), 两种 衽反 :( : 五前 # 五. 分列:用典 经 �法 result = {} for 学生 in st: # 成 '算 ' ' ' * '名 '斯 : :] ( '人 ' ' 分 ' : : 敬生'学 ' * '分 * ' < " " 徜 ( ( 学 ' ' '算 ' * ' ' 学 * ' 分 ' ) , 屉 ' 无 ' ) # 输出结结: print (" ' ': " : "0 株 : 五 : ' 乘 ' : : : : : : : : : : ' 术 ' : : : : float 结 。 : 五 、 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 '' 五 : 五 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 九 五 五 : 五 : 五 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 : 五 五 : 五 五 \)

Python 阅读 1 点赞 0 评论 0

Python组合数据类型&函数编程

[TOC]

1. 组合数据类型

Python 提供了多种强大的组合数据类型(也称为容器),用于存储和操作一组数据。掌握它们是编写高效 Python 代码的基础。

1.1 列表 (List)

列表是 Python 中最常用的数据类型,它是一个有序可变的序列。它可以容纳不同类型的元素。

Java 对比:类似于 Java 中的 ArrayList,但 Python 的 List 可以存储任意不同类型的元素。

1.1.1 列表内存结构示意

列表本质上是一个存储对象引用的动态数组。

graph LR
    List[List对象] --> |索引0| E1[元素1: 'apple']
    List --> |索引1| E2[元素2: 'banana']
    List --> |索引2| E3[元素3: 100]

1.1.2 列表常用操作 (增删改查)

方法 描述 示例
append(x) 末尾追加 lst.append(1)
extend(seq) 末尾扩展序列 lst.extend([2,3])
insert(i, x) 指定位置插入 lst.insert(0, 'a')
pop(i) 弹出指定位置元素 lst.pop() (默认最后)
remove(x) 删除第一个值为x的元素 lst.remove('a')
clear() 清空列表 lst.clear()
index(x) 查找元素索引 lst.index('a')
count(x) 统计元素出现次数 lst.count('a')
sort() 排序 (原地修改) lst.sort(reverse=True)
reverse() 反转 (原地修改) lst.reverse()
copy() 浅拷贝列表 lst.copy()
# 1. 创建与增加
fruits = ['apple', 'banana']
fruits.append('orange')      # ['apple', 'banana', 'orange']
fruits.insert(1, 'grape')    # ['apple', 'grape', 'banana', 'orange']

# 2. 删除
x = fruits.pop()             # 弹出 'orange'
if 'grape' in fruits:        # 安全删除前先检查
    fruits.remove('grape')   # 删除 'grape'

# 3. 修改
fruits[0] = 'pear'           # ['pear', 'banana']

# 4. 查找与统计
nums = [1, 2, 3, 2, 4, 2]
print(f"2出现了 {nums.count(2)} 次") # 统计次数
print(f"3的索引是 {nums.index(3)}")   # 查找索引

# 5. 高级排序
students = [('Tom', 80), ('Jerry', 90), ('Spike', 85)]
# 按分数(第二个元素)倒序排序
students.sort(key=lambda x: x[1], reverse=True)
print(students) # [('Jerry', 90), ('Spike', 85), ('Tom', 80)]

1.1.3 列表切片 (Slicing) - 重点

切片是获取列表子集的强大工具。

  • 语法list[start:end:step] (左闭右开区间)
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(nums[1:4])    # [1, 2, 3] (索引1到3)
print(nums[:3])     # [0, 1, 2] (从头开始)
print(nums[7:])     # [7, 8, 9] (直到末尾)
print(nums[::2])    # [0, 2, 4, 6, 8] (步长为2)
print(nums[::-1])   # [9, 8, ..., 0] (列表反转)
print(nums[1:-1])   # [1, ..., 8] (去掉首尾)

1.1.4 列表推导式 (List Comprehension)

列表推导式提供了一种简洁优雅的方式来创建列表,比 for 循环更快。

Java 对比:类似 Java 8 的 Stream API,但语法更简洁。

  • Python: [x*2 for x in list if x > 0]
  • Java: list.stream().filter(x -> x > 0).map(x -> x*2).toList()
# 需求:生成 [1, 4, 9, 16, ..., 100]
# 推导式写法 (推荐)
squares = [x**2 for x in range(1, 11)]

# 带条件的推导式 (只计算偶数平方)
even_squares = [x**2 for x in range(1, 11) if x % 2 == 0]

# 嵌套推导式 (生成扑克牌)
colors = ['♠', '♥', '♣', '♦']
ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
deck = [c + r for c in colors for r in ranks]

1.2 元组 (Tuple)

元组与列表类似,但是元组是不可变的。一旦创建,就不能修改。

Java 对比:类似于 Java 中的 Object[] 数组(长度不可变),或者 Java 9+ 的 List.of() 创建的不可变列表。

  • 应用场景:保护数据不被篡改、作为字典的 Key、函数返回多个值。

1.2.1 关键陷阱:单元素元组

如果要创建只有一个元素的元组,必须加逗号,否则会被识别为普通括号运算。

t1 = (10)    # <class 'int'> (这是整数)
t2 = (10,)   # <class 'tuple'> (这是元组)

1.2.2 元组解包 (Unpacking)

# 1. 交换变量
a, b = 10, 20
a, b = b, a  # a=20, b=10

# 2. 接收剩余元素 (*)
data = (1, 2, 3, 4, 5)
head, *middle, tail = data
print(middle) # [2, 3, 4]

1.2.3 元组的高级用法

元组因其不可变性,可以作为字典的 Key,而列表不行。

locations = {
    (39.9, 116.4): "Beijing",
    (31.2, 121.5): "Shanghai"
}
print(locations[(39.9, 116.4)]) # Beijing

1.3 集合 (Set)

集合是一个无序不重复的元素集。

Java 对比:类似于 Java 中的 HashSet

  • 原理:类似只有 Key 没有 Value 的字典。
  • 场景:快速去重、数学集合运算。

1.3.1 创建集合

s1 = {1, 2, 3}
s2 = set([1, 2, 2, 3]) # {1, 2, 3} 自动去重
s3 = set() # 注意:空集合必须用 set(),{} 是空字典

1.3.2 集合实战:列表去重

这是面试中常见的考点。

names = ['Tom', 'Jerry', 'Tom', 'Spike']
unique_names = list(set(names)) # ['Spike', 'Tom', 'Jerry'] (顺序可能打乱)
# 如果需要保持顺序:
unique_names_ordered = sorted(list(set(names)), key=names.index)

1.3.3 集合常用操作

方法 描述 示例
add(x) 添加元素 s.add(4)
update(seq) 添加多个元素 s.update([5, 6])
remove(x) 删除元素 (不存在报错) s.remove(4)
discard(x) 删除元素 (不存在不报错) s.discard(99)
pop() 随机弹出元素 s.pop()
clear() 清空集合 s.clear()

1.3.4 集合运算 (交并差)

python_devs = {'Tom', 'Jerry', 'Spike'}
java_devs = {'Tom', 'Tyke'}

print(python_devs & java_devs)  # 交集 (都会的人): {'Tom'}
print(python_devs | java_devs)  # 并集 (所有人): {'Tom', 'Jerry', 'Spike', 'Tyke'}
print(python_devs - java_devs)  # 差集 (只会Python): {'Jerry', 'Spike'}
print(python_devs ^ java_devs)  # 对称差集 (只懂一门的): {'Jerry', 'Spike', 'Tyke'}

1.3.4 集合常用判断

方法 描述
isdisjoint() 是否无交集
issubset() 是否为子集 (<=)
issuperset() 是否为父集 (>=)

1.4 字典 (Dict)

字典是 Python 中唯一的映射类型,采用 Key-Value 存储。

Java 对比:类似于 Java 中的 HashMap (无序) 或 LinkedHashMap (有序,Python 3.7+ 字典默认有序)。

  • 底层原理:基于 哈希表 (Hash Table) 实现,查找速度为 O(1),极快。
  • Key的要求:必须是不可变类型(如字符串、数字、元组),列表不能做 Key。

1.4.1 字典操作详解

# 1. 创建
info = {"name": "Tom", "age": 18}
d2 = dict(name="Jerry", age=20) # 使用工厂函数

# 2. 访问
print(info["name"])        # Tom
print(info.get("sex"))     # None (安全访问,不存在不报错)
print(info.get("sex", "男")) # 男 (设置默认值)

# 3. 增改
info["city"] = "Beijing"   # 新增
info["age"] = 19           # 修改

# 4. 检查Key
if "age" in info:
    print("年龄已存在")

1.4.2 字典常用方法全解

方法 描述
keys() 获取所有键 (视图对象)
values() 获取所有值 (视图对象)
items() 获取所有键值对 (返回元组列表)
update(dict2) 合并字典 (覆盖已有key)
pop(key) 删除并返回指定值
popitem() 删除并返回最后插入的键值对
clear() 清空字典
setdefault(key, def) 获取值,若不存在则设为默认值
fromkeys(seq, val) 快速创建字典,统一初始化值

1.4.3 字典推导式

快速构建字典。

# 将两个列表组合成字典
keys = ['name', 'age', 'city']
values = ['Tom', 18, 'Beijing']
info = {k: v for k, v in zip(keys, values)}
# {'name': 'Tom', 'age': 18, 'city': 'Beijing'}

1.4.4 实战:setdefault 分组

这是一个非常实用的功能,常用于数据处理。

data = [("fruit", "apple"), ("fruit", "banana"), ("veg", "carrot")]
group = {}
for category, name in data:
    group.setdefault(category, []).append(name)
print(group) # {'fruit': ['apple', 'banana'], 'veg': ['carrot']}

1.4.5 遍历字典

d = {'a': 1, 'b': 2}
# 遍历 Key
for k in d:
    print(k)

# 遍历 Value
for v in d.values():
    print(v)

# 遍历 Key-Value
for k, v in d.items():
    print(f"{k} -> {v}")

1.5 核心难点:深浅拷贝

方式 描述 示意图
赋值 (=) 引用传递,指向同一对象 A -> [Obj] <- B
浅拷贝 (copy) 只拷贝父容器,子元素仍是引用 A -> [Obj], B -> [Obj] (Obj是同一个)
深拷贝 (deepcopy) 递归拷贝所有层级,完全独立 A -> [Obj1], B -> [Obj2] (完全隔离)
import copy
a = [1, [2, 3]]
b = copy.deepcopy(a) 
# 此时修改 a[1] 不会影响 b

1.6 容器对比总结

特性 列表 (List) 元组 (Tuple) 字典 (Dict) 集合 (Set)
符号 [] () {k:v} {}
有序性 有序 有序 3.7+有序 无序
可变性 可变 不可变 可变 可变
重复性 可重复 可重复 Key不可重复 不可重复
查询效率 O(n) O(n) O(1) O(1)
主要用途 存储序列数据 存储常量配置 键值映射 去重、数学运算

2. 函数

函数(function)用于封装一段可重复使用的逻辑:把一段代码“起个名字”,后续需要时直接调用即可。

Java 类比:可以把 Python 函数先类比成“写在类外面的静态方法”,调用时更像直接写 Util.printHello(),而不是必须 new 一个对象。

2.1 概念

函数(function)是 Python 中用于封装可重复执行代码块的基本单元,它能够接收输入参数、执行特定逻辑,并可选择性地返回结果。

简单的说:函数代表一个独立的可复用的功能。

通过使用函数,程序编写、阅读、测试和修复起来都更加容易。Python中的函数必须先定义后使用,Python提供了许多内建函数,比如print()。也可以自己创建函数,这被叫做用户自定义函数。

2.2 定义与调用

2.2.1 定义格式

Python 定义函数使用 def 关键字,一般格式如下:

Java 类比:def add(a, b): 对应 Java 的方法签名;Python 不要求写参数类型,但可以用类型提示让代码更像 Java(后面会看到)。

def 函数名 (参数列表) :
        函数体
        return [表达式]
  • 格式:
    • 函数代码块以 def 关键字开头,后接函数名与圆括号 (),括号后以冒号 : 结束
    • 函数体使用缩进表示代码块
  • 函数名:
    • 函数名需要遵循标识符命名规则。Python 工程中通常使用蛇形命名法(snake_case),例如 print_helloget_user
  • 参数列表:
    • 函数在完成某个功能时,可能需要一些数据,在定义函数时指定函数参数来接收这些数据。例如在屏幕上打印信息,需要把要打印的信息传递给print()函数。如果有多个参数,参数之间使用逗号分隔。函数也可以没有参数,但是小括弧不能省略。
  • 函数体:
    • 函数体一般包含3个部分:文档说明,功能代码,return [表达式]语句。
    • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。用三个引号括起来,单引号和双引号都可以。
    • return [表达式] 结束函数,返回一个值给调用方。不带表达式的return或没有return语句相当于返回 None。

2.2.2 定义与调用示例

  • 函数必须先定义再调用;
  • 函数定义后不调用是不会执行的;
  • 调用一次执行一次;
# -----------------------函数的演示----------------------------
# 定义函数
def print_hello():
    """
    打印hello world
    """
    print("hello world")

# 调用函数。先定义,再调用。
print_hello()
print("--------")
print_hello()
print()

# -----------------------
# 定义函数,并接收参数
def print_star(line,column):
    """
    打印指定行数和列数的星号
    """
    for i in range(line):
        for j in range(column):
            print("*", end=" ")
        print()
# 调用函数
print_star(5,10)
print()

# -----------------------
# 定义函数,并返回结果
def add(a, b):
    """
    返回两个数的和
    """
    return a + b

# 调用函数
print("add(1, 2) = ", add(1, 2))
print("add(2, 3) = ", add(2, 3))

2.3 说明文档(DocString)

Java 类比:DocString ≈ JavaDoc。区别是 DocString 写在函数体第一行的字符串里,运行时可以通过 help()/__doc__ 读取。

函数说明文档的好处:

  • 提高可读性:清晰描述函数用途,别人不用翻实现也能用
  • 便于维护:参数与返回值写清楚,后续修改不容易“改坏”
  • 支持工具:help() / IDE 能直接读取 DocString 作为提示
  • 团队协作:减少沟通成本,统一代码风格
# =================函数说明文档的演示========================
# 定义一个函数,可以判断一个数是否是素数
def is_prime(num):
    """
    判断num是否是素数
    :param num: 待判断的整数
    :return: 如果是素数返回True,否则返回False
    """
    if num < 2:
        return False
    import math
    for i in range(2, int(math.sqrt(num)+1)):
        if num % i == 0:
            return False
    return True

# 调用函数
print("7是素数吗?", is_prime(7))
print(help(is_prime))

PyCharm中将鼠标悬停在函数名上方也可以看到函数说明文档。

image-20251218105359231

2.3.1 函数注释(了解)

Java 类比:这里更像“类型提示 + 一点点描述信息”,不是 Java 的 @Annotation。它主要给人和工具看(IDE/静态检查),不影响 Python 运行。

Python 3.x 引入了函数注释,以增强函数的注释功能。

可以使用:对参数逐个进行注释,注释内容可以是任何形式,比如参数的类型、作用、取值范围等等,返回值使用->标注,所有的注释都会保存到函数的__annotations__属性中。另外,使用函数注释并不影响默认参数的使用。

# 普通的自定义函数:
def dog(name, species, age):
    print(f"{name} 的品种是:{species},年龄是:{age}")
# 调用函数
dog("旺财", "泰迪", 5)

# 添加了注释的自定义函数:
def dog(name: str, species: "狗狗的品种", age: (1, 99) = 1) -> "返回None":
    print(f"{name} 的品种是:{ species},年龄是:{age}")
# 获取函数的注释信息
print(dog.__annotations__)
# 调用函数
dog("旺财", "泰迪")

2.4 返回值

Java 类比:Java 的 void 表示“没有返回值”。Python 没写 return 时会自动返回 None(你打印出来就能看到)。

2.4.1 没有 return

# =================函数返回值的演示========================
# 定义函数,函数体没有return语句,表示没有返回值
def print_hello():
    """
    打印hello
    """
    print("hello")

# 调用函数
print_hello() # print_hello函数没有返回值,所以不用接收返回值
# 在Python中函数没有返回值,也可以打印它的结果,但结果是None
print("结果:", print_hello()) # print_hello函数没有返回值,所以结果是None
result = print_hello() # 在Python中函数没有返回值,也可以用变量接收返回值,但结果是None
print("result=",result)

2.4.2 只有 return

# 定义函数,函数体有return语句,但return后面没有值,表示没有返回值
def print_star(line,column):
    """
    打印指定行数和列数的星号
    """
    if line <= 0 or column <= 0:
        return # 作用是提前结束函数的执行
    for i in range(line):
        for j in range(column):
            print("*", end=" ")
        print()

# 调用函数
print_star(5,10) # print_star函数没有返回值,所以不用接收返回值
print("结果是", print_star(3,5)) # 结果是None
result = print_star(5,10)  # 结果是None
print("result=",result)

2.4.3 return 值

# 定义函数
def add(a, b):
    """
    返回两个数的和
    """
    return a + b # 返回结果

# 调用函数
add(1, 2) # add函数有返回值,不接收返回值也是可以的,返回值就丢失了。因为add函数中没有打印语句,看起来像没有执行一样
print("add(1, 2) = ", add(1, 2)) # 直接输出返回值
result = add(1, 2) # 可以用变量接收返回值
print("result=",result)

2.5 参数

Java 类比:

  • 位置参数:就是普通方法调用传参
  • 关键字参数:更像“命名参数”,Java 语法本身不支持(通常靠 Builder/Map/重载实现)
  • *args:≈ Java 的 T... args
  • **kwargs:≈ 传一个 Map<String, Object>

2.5.1 形参与实参

  • 在定义函数时,指定的参数称为形式参数,简称为形参(函数的提供者);在调用函数时,给函数传递的参数称为实际参数,简称为实参(函数的调用者)

  • 在定义函数时,形参没有分配存储空间,也没有值,相当于一个占位符;在调用函数时, 会在栈区中给函数分配存储空间, 然后给形参/局部变量分配存储空间,传递的是实际的数据。当函数执行结束,函数所占的栈空间会被释放,函数的形参/局部变量也会被释放

2.5.2 常见参数形式

2.5.2.1 位置参数

实参的顺序、个数必须与形参完全一致

# 1. 位置参数
# 定义函数
def print_info(name, age):
    print(f"{name} 的年龄是:{age}")

# 调用函数    
print_info("张三", 18)
2.5.2.2 默认值参数

定义默认值参数时要求:含默认值的形参必须在所有不含默认值的形参后面

含默认值的形参,可以不用指定实参

# 2. 默认参数,默认参数必须放在非默认参数后面
# 定义函数
def print_info(name, age=18):
    print(f"{name} 的年龄是:{age}")

# 调用函数
print_info("张三", 20) # 可以正常给age形参赋值
print_info("张三") # 也可以不给age形参赋值,此时age就是默认值
2.5.2.3 关键字参数

调用方法时,实参可以使用形参名作为关键字来指定给哪个形参赋值,此时实参的顺序就不用与形参完全一致了

# 3. 关键字参数
# 定义函数
def print_info(name, age, gender='男'):
    print(f"{name} 的年龄是:{age},性别是:{gender}")

# 调用函数
# 可以通过关键字参数给形参赋值, 此时name和age,gender的顺序可以任意
print_info(age=18, name="张三")
print_info(gender='女',name="张三", age=18)
2.5.2.4 可变参数
  • *args: 接收任意数量的位置参数,存储为tuple类型
  • **kwargs: 接收任意数量的关键字参数,存储为dict类型
  • 如果一个函数包含可变参数和普通参数,那么建议普通参数在前,可变参数在后
    • 一个函数中最多只能有一个*args**kwargs 的可变参数
    • *args形式的可变参数后面,可以有**kwargs形式的可变参数。虽然*args后面也可以有普通参数,但是此时普通参数必须通过关键字参数的形参赋值,所以一般不推荐在*args后面定义普通参数。
    • **kwargs形式的可变参数后面不允许再有任何参数。
# 定义函数,,*hobby表示可变参数,*hobby会变成一个元组。
def print_info( *hobby):
    print(f"爱好是:{hobby}")
# 调用函数
print_info("跑步", "看电影", "看小说")

# 定义函数,习惯上普通参数在前,可变参数一般放在形参列表的最后
def print_info(name, *hobby):
    print(f"{name} 的爱好是:{hobby}")
# 调用函数
print_info("张三", "跑步", "看电影", "看小说")

# 定义函数,如果可变参数后面还有普通参数,那么普通参数必须使用关键字参数给普通参数赋值
def print_info(name, *hobby, age):
    print(f"{name} 的爱好是:{hobby},年龄:{age}")
# 调用函数
print_info( "张三","跑步", "看电影", "看小说", age = 18)
# 定义函数,**contact_person表示可变参数,**contact_person会变成一个字典。
def print_info(name, **contact_person):
    print(f"{name} 的联系人有:{contact_person}")
# 调用函数
print_info("张三", mon = "12345678901", dad = "12345678902")

# 定义函数,**contact_person可变参数必须在最后
def print_info(name, *hobby, **contact_person):
    print(f"{name} 的爱好是:{hobby},联系人有:{contact_person}")
# 调用函数
print_info("张三","跑步", "看电影", "看小说", mon = "12345678901", dad = "12345678902")

2.5.2.5 解包传参
  • 若函数的形参是定长参数,可以通过 * 和 ** 对列表、元组、字典等解包传参。
# 5. 解包传参
# 定义函数
def print_info(name, age):
    print(f"{name} 的年龄是:{age}")
# 调用函数
print_info(*["张三", 18]) # 列表解包,要求列表元素个数与参数个数一致
print_info(*("张三", 18)) # 元组解包,要求元组元素个数与参数个数一致
print_info(*{"张三", 18}) # 不推荐用集合,因为集合是无序的
print_info(**{"name": "张三", "age": 18}) # 字典解包,要求字典的key与参数名一致
2.5.2.6 强制位置/关键字
  • 强制使用位置参数或关键字参数:/ 前的参数必须使用位置传参,* 后的参数必须用关键字传参。
# 6. 强制使用位置参数或关键字参数:/ 前的参数必须使用位置传参,* 后的参数必须用关键字传参。
# 定义函数
def print_info(name, age, /, gender, hobby, *,  city):
    print(f"{name} 的年龄是:{age},性别是:{gender},爱好是:{hobby},城市是:{city}")
# 调用函数
print_info("张三", 18, "男", "看电影", city="上海")
print_info("张三", 18, gender="男", hobby="看电影", city="上海")
2.5.2.7 deepcopy 防修改
  • 根据Python的参数传递机制的原理,如果参数是可变类型(例如:列表、集合、字典等),那么在函数中对可变参数的内容做修改,会影响实参。如果此时希望对形参的修改不影响实参,那么可以通过copy.deepcopy()来实现。本质上相当于是复制了一个实参对象给形参。
# 7、防止列表等参数被修改
# 定义函数
def change_list(la):
    print(f"{la} 的id是:{id(la)}")
    la[0] = 100
    print(f"{la} 的id是:{id(la)}")
    la.append(6)
    print(f"{la} 的id是:{id(la)}")
# 调用函数
import copy
listDemo = [1,2]
print(f"函数调用前:{listDemo},listDemo.id = {id(listDemo)}")
change_list(copy.deepcopy(listDemo))
print(f"函数调用后:{listDemo},listDemo.id = {id(listDemo)}")

2.5.3 参数传递机制

Java 核心对比
Python 的参数传递机制与 Java 的 引用传递 (Call by Object Reference) 完全一致(针对对象)。

  • 基础原则:变量保存的是对象的引用(地址),函数传参传的也是这个引用
  • 效果区别
    • 不可变对象(int, str, tuple):类似 Java 的 String。函数内修改(如 a = a + 1)是让变量指向新对象,不影响外面的变量。
    • 可变对象(list, dict):类似 Java 的 ArrayList。函数内修改内容(如 lst.append(1)),外面受影响。但如果让变量指向新列表(lst = []),则不影响外面。
2.5.3.1 案例演示

1. 不可变对象 (类似 Java String)

def change_str(s):
    print(f"函数内修改前: {s}, id={id(s)}")
    s += "b"  # 创建新对象 "aab",s 指向新地址
    print(f"函数内修改后: {s}, id={id(s)}")

x = "aa"
print(f"调用前: {x}, id={id(x)}")
change_str(x)
print(f"调用后: {x}, id={id(x)}") # x 仍然是 "aa"

2. 可变对象 (类似 Java ArrayList)

def change_list(lst):
    # 修改内容 -> 影响外部
    lst.append(100) 
    
    # 重新赋值 -> 不影响外部
    lst = [9, 9, 9] 

my_list = [1, 2]
change_list(my_list)
print(my_list)  # 输出 [1, 2, 100]
2.5.3.7 两种运算符区别

对于可变类型来说,还有一个小细节,la *= 2 和 la = la * 2 有差别:

  • la *= 2 使用原地址
  • la = la * 2 返回新地址

类似的还有:+=。

# ==============2种列表乘法的区别=================
def multiply_list_One(la) :
    print(f"{la} 的id是:{id(la)}")
   # la *= 2 # 使用原地址
    la += [1,2]
    print(f"{la} 的id是:{id(la)}")

list1 = [1,2]
multiply_list_One(list1)
print(list1) # [1, 2, 1, 2]

def multiply_list_two(la) :
    print(f"{la} 的id是:{id(la)}")
    # la = la * 2 # 使用新地址
    la = la + [1,2]
    print(f"{la} 的id是:{id(la)}")

list2 = [1,2]
multiply_list_two(list2)
print(list2) # [1, 2]

2.6 嵌套调用

  • 在一个函数中调用另一个函数,当内层函数执行完之后才会继续执行外层函数的其他语句。
# ================函数嵌套定义======================
def function_a():
    print("\t函数 A 开始执行")
    print("\t函数 A 执行中...")
    print("\t函数 A 结束执行")

def function_b():
    print("函数 B 开始执行")
    print("函数 B 执行中...")
    function_a()
    print("函数 B 执行中...")
    print("函数 B 结束执行")

function_b()

执行结果:

函数 B 开始执行
函数 B 执行中...
	函数 A 开始执行
	函数 A 执行中...
	函数 A 结束执行
函数 B 执行中...
函数 B 结束执行

2.7 作用域

Java 类比:Java 的局部变量作用域主要由 {} 控制;Python 的作用域主要由“函数/类/模块”决定,if/for/while 这类代码块并不会新开作用域。

2.7.1 LEGB 四种作用域

Python中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了哪一部分程序可以访问哪个变量,Python的作用域一共有4种,分别是:

  • L (Local) 局部作用域
    • 函数内部定义的变量
    • 只在当前函数内有效
  • E (Enclosing)嵌套作用域
    • 闭包中外层函数的作用域
    • 在嵌套函数中可以访问外层函数的变量
  • G (Global) 全局作用域
    • 模块级别的变量
    • 在整个文件中都可以访问
  • B (Built-in) 内建作用域
    • Python内置的变量和函数
    • 如 len, print, int 等内置函数

以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。

Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,其外部也可以访问。

# =================变量作用域====================
# if-else语句块中定义变量,不会开启新的变量作用域
import random
a= random.randint(1,10) # 全局变量
if a>5:
    result = "big" # 全局变量,因为当前if不在函数,闭包中
    print("a的值是:",a)
else:
    result = "small"
print("result的值是:",result) # if-else语句块外,仍然可以访问result
# =================变量作用域====================
# 在函数中定义变量,会开启新的变量作用域
# G (Global): 模块级别变量
x = "global" # 全局变量
def outer():
    # E (Enclosing): 闭包
    y = "enclosing" # 外层函数变量,在整个outer函数中都可以访问

    # 在外部函数体中定义内部函数
    def inner():
        # L (Local): 函数内部变量
        z = "local" # 局部变量,仅限于inner函数内部访问
        print(f"\t在inner中访问局部变量z的值是:{z}")  # 访问局部变量
        print(f"\t在inner中访问嵌套变量y的值是:{y}")  # 访问外层函数变量
        print(f"\t在inner中访问全局变量x的值是:{x}")  # 访问全局变量
        print(f"\t在inner中访问内建函数的值是:{len}")  # 访问内建函数

    # 在外部函数体中调用内部函数
    print("在outer中调用inner函数")
    inner()
    # print(f"在outer中访问内层函数变量z的值是:{z}") # 报错
    print(f"在outer中访问外层函数变量y的值是:{y}")
    print(f"在outer中访问全局变量x的值是:{x}")
    print(f"在outer中访问内建函数的值是:{len}")

# 调用外部函数函数
print("调用outer函数")
outer()
# print(f"在main中访问内层函数变量z的值是:{z}") # 报错
# print(f"在main中访问外层函数变量y的值是:{y}") # 报错
print(f"在main中访问全局变量x的值是:{x}")
print(f"在main中访问内建函数的值是:{len}")

2.7.2 global

  • 在函数中不使用 global 声明全局变量时不能修改全局变量的指向,即不能让全局变量指向新地址。
    • 可变类型:在函数中修改可变类型的全局变量的内容,可以不加global关键字。因为此时全局变量引用的地址并未改变。
    • 不可变类型:在函数中修改不可变类型的全局变量值,必须加global关键字。因为不可类型的变量,一旦修改就意味着指向新地址。
2.7.2.1 可变类型
# =========可变类型在局部修改可以不使用global关键字===========
listDemo = [1,2,3] #  可变类型,全局变量
# 定义函数
def change_list():
    listDemo.append(4)
    print("change_list()函数中,listDemo的值是:",listDemo) # listDemo= [1,2,3,4]
    # listDemo = [4,5,6] # 错误,因为Python 默认会把这个变量当作局部变量

# 调用函数
print("调用change_list()函数之前,listDemo的值是:",listDemo)  # listDemo= [1,2,3]
change_list()
print("调用change_list()函数之后,listDemo的值是:",listDemo)  # listDemo= [1,2,3,4]

image-20251218155328427

2.7.2.2 不可变类型
# =============未使用global关键字无法修改不可变类型全局变量的值=============
x = "global"
# 定义函数
def change_x_one():
    # x += "local" # 错误,因为没有global关键字,无法修改全局变量
    x = "local" # 没有global关键字,给x赋值,Python 默认会把这个变量当作局部变量
    print("change_x_one()函数中,x的值是:",x) # x= local

# 调用函数
print("调用change_x_one()函数之前,x的值是:",x) # x= global
change_x_one()
print("调用change_x_one()函数之后,x的值是:",x) # x= global
print()

image-20251218160158610

# ===============使用global关键字才可以修改不可变类型全局变量的值=================
# global关键字,可以修改全局变量的值
x = "global"
# 定义函数
def change_x_two():
    global x # 添加global关键字,修改全局变量的值
    x = "local"
    print("change_x_two()函数中,x的值是:",x) # x= local

# 调用函数
print("调用change_x_two()函数之前,x的值是:",x) # x= global
change_x_two()
print("调用change_x_two()函数之后,x的值是:",x) # x= local
print()

image-20251218160049634

2.7.3 nonlocal

nonlocal 也用作内部作用域修改外部作用域的变量的场景,不过此时外部作用域不是全局作用域而是嵌套作用域。

# ========使用nonlocal关键字在内部作用域修改外部作用域不可变类型变量的值==============
def function_outer():
    str_demo = "outer"
    print("function_outer()函数中,str_demo的值是:",str_demo)
    def function_inner():
        nonlocal str_demo
        str_demo = "inner"
    # 调用内部函数
    print("调用内部函数function_inner()后")
    function_inner()
    print("function_outer()函数中,str_demo的值是:",str_demo)

# 调用外部函数
function_outer() 

2.8 函数是一等公民

Java 类比:在 Java 里你通常用“函数式接口 + Lambda”来传递行为;在 Python 里函数本身就是对象,直接传就行。

在Python中,函数是一等公民,函数也是一种数据类型。

# 函数是一种数据类型
# 定义函数
def my_function():
    print("我是函数体")

# 打印函数的类型
print(type(my_function))

所以,函数也是一个对象,它可以:

  • 函数可以被赋值给变量
  • 函数可以作为参数传递给其他函数
  • 函数可以作为函数的返回值
  • 函数可以存储在数据结构中

这种特性使得Python支持高阶函数、闭包、装饰器等高级编程技术。

2.8.1 赋值给变量

# 可以使用函数可以给变量赋值
def greet():
    print("Hello!")
say_hello = greet # 注意greet后面没有()
say_hello()

注意:

  • say_hello = greet # 赋值给say_hello变量的是一个函数
  • say_hello = greet() # 赋值给say_hello变量的是函数的返回结果,这里是None,因为greet()函数没有返回值

2.8.2 作为参数传递

# 函数可以作为参数传递
def say_hi():
    print("Hi!")
    
def call_function(func): 
    func() # 这里要求传给func的实参是一个函数。

call_function(say_hi) # say_hi是一个函数,它作为参数被传给了call_function函数

2.8.3 作为返回值

# 函数可以作为返回值
def get_function():
    def inner_function():
        print("Inner function")
    return inner_function

#调用get_function函数
func = get_function() # 得到的是一个函数
func() # 调用func()函数

2.8.4 存进数据结构

# 函数可以被存储到数据结构中
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def divide(a, b):
    return a / b

# 将函数存储在列表中,同样也可以被存储在元组、字典等容器中
operations = [add, subtract, multiply, divide]
for operation in operations:
    result = operation(5, 3)
    print(operation.__name__ , ": ", result)

2.9 匿名函数(lambda)

Java 类比:lambda 的核心用途很像 Java 的 x -> x + 1,通常用在“短小的一次性逻辑”里;复杂逻辑更建议写成具名函数。

2.9.1 Lambda 表达式

Python使用 lambda 来定义匿名函数,所谓匿名,指其不用 def 的标准形式定义函数。

lambda 参数列表: 表达式
  • 特点
    • 简洁:适合写“很短的小函数”。
    • 限制:主体只能是一条表达式(不能写多行、赋值、if/for块)。
    • 闭包:可以访问上下文变量。

Java 对比

  • Java: Lambda 必须依赖“函数式接口”(Functional Interface),如 Function<T, R>, Predicate<T>
  • Python: Lambda 本质是创建一个函数对象,可以直接赋值给变量,或作为参数传递。

2.9.2 实战:结合内置函数 (Map/Filter/Sorted)

这是 Lambda 最常见的应用场景,相当于 Java 的 Stream API 操作。

Python 操作 Java Stream 对比 说明
map(func, list) stream().map(func) 映射:对每个元素做处理
filter(func, list) stream().filter(func) 过滤:保留符合条件的元素
sorted(list, key=func) stream().sorted(Comparator) 排序:自定义排序规则
reduce(func, list) stream().reduce(func) 归约:聚合结果 (需导入 functools)
data = [1, 2, 3, 4, 5]

# 1. map: 每个元素平方
# Java: list.stream().map(x -> x*x).toList()
res_map = list(map(lambda x: x*x, data))
print(res_map)  # [1, 4, 9, 16, 25]

# 2. filter: 保留偶数
# Java: list.stream().filter(x -> x%2==0).toList()
res_filter = list(filter(lambda x: x % 2 == 0, data))
print(res_filter)  # [2, 4]

# 3. sorted: 按绝对值排序
# Java: list.stream().sorted(Comparator.comparingInt(Math::abs)).toList()
nums = [-5, 2, -10, 1]
res_sorted = sorted(nums, key=lambda x: abs(x))
print(res_sorted)  # [1, 2, -5, -10]

3. 综合案例

本章节用到前面已经讲过的内容(list / dict / set / tuple、函数、lambda 排序、setdefault 分组等),尽量做到短小、清晰、可直接运行。

graph TD
    A["准备数据结构 list/dict/set/tuple"] --> B["封装函数:add/update/report"]
    B --> C["用推导式/循环统计"]
    C --> D["用 sorted + lambda 排序"]
    C --> E["用 setdefault 分组"]
    D --> F["打印/返回结果"]
    E --> F

3.1 成绩小助手(列表+字典+排序+分组)

需求:

  • 保存学生成绩
  • 修改成绩
  • 统计:平均分、及格名单、Top3、分组(及格/不及格)
# 学生列表:每个元素是一个字典,保存 name / score
students = [
    {"name": "Tom", "score": 80},
    {"name": "Jerry", "score": 59},
    {"name": "Spike", "score": 100},
]

def add_student(students, name, score):
    # 新增一条学生数据(列表是可变对象,函数内修改会影响外部)
    students.append({"name": name, "score": score})

def update_score(students, name, new_score):
    # 按姓名遍历查找,找到后修改分数并返回 True;找不到返回 False
    for s in students:
        if s["name"] == name:
            s["score"] = new_score
            return True
    return False

def report(students):
    # 约定:空列表时直接返回“空报表”
    if not students:
        return {"avg": 0, "pass_names": [], "top3": [], "group": {}}

    # 平均分:sum + 生成器表达式
    avg = sum(s["score"] for s in students) / len(students)

    # 及格名单:列表推导式
    pass_names = [s["name"] for s in students if s["score"] >= 60]

    # Top3:sorted + lambda,按 score 倒序取前三
    top3 = sorted(students, key=lambda s: s["score"], reverse=True)[:3]

    # 分组:setdefault 经典用法
    group = {}
    for s in students:
        key = "pass" if s["score"] >= 60 else "fail"
        group.setdefault(key, []).append(s["name"])

    return {"avg": round(avg, 2), "pass_names": pass_names, "top3": top3, "group": group}

# 测试:新增一个学生
add_student(students, "Tyke", 70)
# 测试:把 Jerry 的分数从 59 改成 61(刚好及格)
update_score(students, "Jerry", 61)

result = report(students)
print("avg:", result["avg"])
print("pass:", result["pass_names"])
print("top3:", [(s["name"], s["score"]) for s in result["top3"]])
print("group:", result["group"])

3.2 购物清单结算(列表+元组+字典+集合)

需求:

  • 价格表用字典保存
  • 购物清单用列表保存(元素用元组表示)
  • 结算总价,顺便找出“清单里有没有价格表里不存在的商品”
# price:商品 -> 单价
price = {"apple": 3.0, "banana": 2.5, "milk": 6.0}
# cart:每一项是 (商品名, 数量)
cart = [("apple", 2), ("banana", 3), ("milk", 1), ("cola", 1)]

def calc_total(price, cart):
    total = 0.0
    for name, qty in cart:
        if name in price:
            # 命中价格表:参与结算
            total += price[name] * qty
        # 未命中价格表:跳过(也可以选择抛异常/记录错误,这里保持简单)
    return round(total, 2)

def missing_items(price, cart):
    # 用集合差集找出“购物车里有、价格表里没有”的商品
    names_in_cart = {name for name, _ in cart}
    names_in_price = set(price.keys())
    return sorted(list(names_in_cart - names_in_price))

print("total:", calc_total(price, cart))
print("missing:", missing_items(price, cart))

Java 对比

  • list[dict] 的结构在 Java 里通常对应 List<Map<String, Object>> 或者更推荐用 List<Student>(用类建模);
  • sorted(..., key=...) 的思想类似 Java 的 Comparator.comparing(...)
  • setdefault 的分组写法,对应 Java 常见的 Map.computeIfAbsent(key, k -> new ArrayList<>())
  • 集合差集 A - B 类似 Java 里对 SetremoveAll(注意会修改原集合),或用新集合计算差集。

继续阅读

全部归档
03_python模块文件&面向对象
03_python模块文件&面向对象

基于上述提供的代码,将 �大致可以可以看出此部分内容主要包括了模块化编程的定义的关键词与具体案例的下了是: 11# �模块定义了模块和包,级该`定义了`个便于分复序,并) `contact.pyikel文件`条件.py `_和部分调了通用从`0联系人列表.e.e也支持拆配合了.e不同基本接口定义了增.e除查询功能和余/. *定义了基.e `contact.py文件.e.e实现了基于.e关键词对应方法(e.e·例如各个功能.e便于.复.e和。 `主入口.e中定义了一个.e主功能e通过.e子联系信息进行概实现了基本的操作e这里.e.d进.e联系e添加删查响应查询联系资源查更了联系信息e供与.e联系丁.e文件.e进行例如的示具体实现了数据阅读.e文件保存与加载基本逻辑。 ` **验证了模块的的单一职责原则(e各.e实现了.e基本功能e便于.代.e进一步复)e** 通过.简便而了逻辑结构e相互复.e实现.** **演示了交互入口(e..文件.e.e中通过.e全局接口实现了用户基.e功能与具体应用e即实现了具体化实现了某.e具体具体一致性;客户e.。 实现了模块化编程与交互入口解键分别表达了.e完全为独立e便于各复e,遵循单一功能原则(e.< .整个逻辑结构清晰e便于逻辑实现与维护;。** 代码结构结构为结构化包程即简明清楚。提高了易于维护与扩展。。同时充分展示了e模块化编程的优势数组功能实现与界面的交互实现.

评论