python八股文汇总(持续更新版)
python装饰器
一、装饰器是什么?
装饰器是Python中一种"化妆师",它能在不修改原函数代码的前提下,给函数动态添加新功能。
- 本质:一个接收函数作为参数,并返回新函数的工具。
- 作用:像给手机贴膜,既保护屏幕(原函数),又新增防摔功能(装饰逻辑)。
二、核心原理
- 函数是"对象":Python中函数可以像变量一样传递,这是装饰器的基础。
- 闭包机制:装饰器通过嵌套函数(闭包)保留原函数,并包裹新功能。
工作流程:
- 你调用被装饰的函数(如
hello()
)。 - Python实际执行的是装饰器加工后的新函数。
- 新函数先执行装饰器添加的逻辑(如权限检查),再执行原函数。
三、常见用途
场景 | 作用 | 生活类比 |
权限验证 | 检查用户是否登录再执行函数 | 进小区前刷卡(装饰器是门禁系统) |
日志记录 | 自动记录函数调用时间和参数 | 飞机黑匣子(自动记录飞行数据) |
性能统计 | 计算函数运行耗时 | 跑步时用手表计时 |
缓存结果 | 避免重复计算(如 ) | 备忘录(记下答案直接复用) |
四、两种实现方式
- 函数式装饰器
-
- 最常用,通过嵌套函数实现。
- 示例:给函数添加"呼叫提醒"功能
def remind_call(func):def wrapper():print("【提醒】开始打电话...")func() # 执行原打电话函数print("【提醒】通话结束")return wrapper@remind_call
def call_friend():print("正在和好友通话中...")call_friend()
输出:
【提醒】开始打电话...
正在和好友通话中...
【提醒】通话结束
- 类装饰器
-
- 通过实现
__call__
方法让类能像函数一样调用。 - 适合需要维护状态的场景(如重试计数器)。
- 通过实现
五、需要注意的坑
- 原函数信息丢失
-
- 直接使用装饰器会导致
help(func)
显示的是wrapper函数的信息。 - 解决:用
functools.wraps
装饰wrapper函数。
- 直接使用装饰器会导致
- 装饰顺序影响
-
- 多个装饰器从下往上执行:
@decorator1 # 最后执行
@decorator2 # 先执行
def func(): pass
- 带参数的装饰器
-
- 需要三层嵌套函数,最外层接收装饰器参数。
六、现实世界类比
- 快递打包
-
- 原函数 = 商品
- 装饰器 = 打包服务(先加泡沫纸,再贴快递单)
@打包
后的商品功能不变,但多了运输保护。
- 游戏装备
-
- 原函数 = 游戏角色
@穿装备
后角色攻击力提升,但角色代码未修改。
七、为什么用装饰器?
- 避免重复代码(如所有函数都要加日志时)
- 保持纯净(不修改原函数,降低耦合)
- 灵活组合(像乐高积木一样叠加功能)
一句话总结:装饰器是Python的"功能外挂",用@
符号轻松实现代码增强!
Python的深拷贝和浅拷贝
1. 基本概念
类型 | 特点 | 适用场景 |
浅拷贝 | 只复制对象本身,不复制内部的子对象 | 简单对象(如列表嵌套不深) |
深拷贝 | 递归复制对象及其所有子对象 | 复杂嵌套对象(如多层字典/列表) |
2. 核心区别
- 浅拷贝:像给房子拍照片,只复制了外观(顶层结构),房间里的家具(子对象)还是同一套。
- 深拷贝:连房子带家具全部克隆一份,新旧对象完全独立。
3. 技术实现
(1) 浅拷贝方法
import copya = [1, [2, 3]]
b = copy.copy(a) # 或 a.copy() / list(a) / slice[:]
- 效果:
-
- 修改
a[0]
(不可变类型)不影响b
- 修改
a[1][0]
(可变子对象)会影响b
- 修改
(2) 深拷贝方法
c = copy.deepcopy(a)
- 效果:
-
- 无论修改
a
的哪一层,c
都完全不受影响
- 无论修改
4. 通俗例子
场景1:合租公寓(浅拷贝)
- 你(
a
)和室友(b
)共用客厅的冰箱(子对象)。 - 你往冰箱里放啤酒 → 室友也能看到这些啤酒。
- 但你换了自己的床单(顶层不可变项) → 室友的床单不变。
场景2:买新房(深拷贝)
- 开发商按样板房(
a
)给你建了完全一样的房子(c
)。 - 你在新房砸墙 → 样板房毫无影响。
- 样板房换家具 → 你的新房也不变。
5. 验证实验
import copy# 原始对象(含可变子对象)
original = [1, {'name': 'Alice'}, [3, 4]]# 浅拷贝
shallow = copy.copy(original)
# 深拷贝
deep = copy.deepcopy(original)# 修改原始对象的子对象
original[1]['name'] = 'Bob'
original[2].append(5)print("原始对象:", original) # [1, {'name': 'Bob'}, [3, 4, 5]]
print("浅拷贝:", shallow) # [1, {'name': 'Bob'}, [3, 4, 5]] (受影响)
print("深拷贝:", deep) # [1, {'name': 'Alice'}, [3, 4]] (不受影响)
6. 特殊注意事项
- 不可变类型(数字/字符串/元组):
-
- 深浅拷贝无区别(因为本身不能修改)
- 示例:
a = (1, [2]); b = copy.copy(a)
→ 修改a[1]
仍会影响b
- 自定义对象:
-
- 需实现
__copy__()
和__deepcopy__()
方法控制拷贝行为
- 需实现
- 性能权衡:
-
- 深拷贝比浅拷贝慢,对复杂结构可能差10倍以上
7. 什么时候用哪种?
- 用浅拷贝:
-
- 数据没有嵌套或子对象不可变
- 需要节省内存/时间
- 明确希望共享子对象时
- 用深拷贝:
-
- 复杂嵌套结构且需要完全独立副本
- 防止意外修改影响原数据
- 需要序列化/反序列化时
总结:
- 浅拷贝是"省力但不彻底",深拷贝是"一劳永逸"。
- 就像备份手机:浅拷贝像云同步(部分依赖原数据),深拷贝像整机克隆(完全独立)。
- 遇到嵌套结构,当心浅拷贝的"连带影响"!
Python有哪些数据结构?list和set有什么区别?数组与链表的区别?
一、Python内置数据结构
类型 | 特点 | 示例 |
列表(list) | 有序、可变、允许重复 |
|
元组(tuple) | 有序、不可变、允许重复 |
|
集合(set) | 无序、可变、不允许重复 |
|
字典(dict) | 键值对、键不可重复 |
|
字符串(str) | 不可变字符序列 |
|
二、List(列表) vs Set(集合)
对比项 | List | Set |
顺序 | 保持插入顺序 | 无序(存储顺序不确定) |
重复元素 | 允许重复 | 自动去重 |
查找速度 | 慢(遍历查找,O(n)) | 极快(哈希表实现,O(1)) |
适用场景 | 需要保留顺序或重复数据时 | 去重、快速成员检测 |
示例:
names = ["Alice", "Bob", "Alice"] # List允许重复
unique_names = {"Alice", "Bob"} # Set自动去重
三、数组(Array) vs 链表(LinkedList)
对比项 | 数组 | 链表 |
内存分配 | 连续内存,大小固定 | 非连续内存,动态扩展 |
访问速度 | 极快(O(1),直接索引) | 慢(O(n),需遍历节点) |
插入/删除 | 慢(需移动元素,O(n)) | 快(O(1),修改指针即可) |
适用场景 | 频繁随机访问,数据量固定 | 频繁增删,数据量变化大 |
通俗比喻:
- 数组:像书架上的书,直接按编号拿(快),但插入新书要挪动其他书(慢)。
- 链表:像藏宝图,每步告诉你下一个地点在哪,添加新线索只需改箭头(快),但找宝藏要一步步走(慢)。
四、Python中的实现
- 数组:
-
- 使用
list
(实际是动态数组,非严格数组) - 或
array
模块(类型受限但更省内存)
- 使用
import array
arr = array.array('i', [1, 2, 3]) # 整型数组
- 链表:
-
- 需手动实现或使用
collections.deque
(双端队列,近似链表特性)
- 需手动实现或使用
class Node:def __init__(self, val):self.val = valself.next = None
五、如何选择?
- 需要快速访问/修改? → 用列表(动态数组)
- 需要频繁去重/检测存在? → 用集合
- 需要高效插入/删除? → 考虑链表(但Python中优先用
list
,除非极端性能需求)
总结:Python的list
和set
分别解决顺序存储和快速查询的问题,而数组与链表的区别是内存结构的根本差异。
python怎么为list去重,list,vector,map区别
✅ Python中如何对list
去重
最常见的几种方式如下:
1. 使用set()
去重(简单快捷,但会打乱顺序):
my_list = [1, 2, 2, 3, 1]
unique_list = list(set(my_list))
2. 保持顺序去重(常用于有序数据)
my_list = [1, 2, 2, 3, 1]
unique_list = []
seen = set()
for item in my_list:if item not in seen:seen.add(item)unique_list.append(item)
3. 使用dict.fromkeys()
(保持顺序,适合Python 3.7+)
my_list = [1, 2, 2, 3, 1]
unique_list = list(dict.fromkeys(my_list))
✅ list、vector、map 区别(以编程语言通用概念为基础)
类型 | 描述 | 是否有序 | 是否可重复 | 常用语言中的名称 |
list | 有序元素集合,支持增删改查 | ✅ | ✅ | Python list, Java List |
vector | 动态数组,自动扩容,支持随机访问 | ✅ | ✅ | C++ vector, Java ArrayList |
map | 键值对集合,key唯一,用于快速查找 | ❌(通常无序) | key❌ value✅ | Python dict, C++ map, Java HashMap |
简单说明:
- list 适合顺序存储,如游戏关卡列表、操作记录。
- vector 类似list,但在C++等语言中是支持自动扩容的动态数组,适合频繁插入、访问。
- map 适合按键快速查找数据,比如通过玩家ID查找昵称或背包。
🎮 举个游戏开发中的例子:
- 玩家背包中的物品列表:用
list
或vector
存储。 - 用来映射玩家ID到玩家数据:用
map
(如Python中的dict
)。 - 排行榜查找前100名玩家分数:可以用
list
去重、排序后显示。
总结:
去重方法要结合“是否保留顺序”来选,三种数据结构各有用途,理解它们的特性可以帮助你在实际开发或测试中选择合适的数据结构。
为什么使用递归?
使用递归的主要原因是为了让代码更简洁、结构更清晰,尤其适合处理具有“自相似”特征的问题,比如树结构、分形结构、回溯搜索、数学归纳等场景。
递归是指一个函数调用自身来解决问题,适用于下面这些情况:
- 问题可以分解成规模更小的子问题;
- 子问题的结构与原问题相同(即自相似);
- 最终有一个明确的终止条件(递归出口)。
📌 与 for 循环的对比:
对比点 | for 循环 | 递归 |
代码结构 | 适合线性问题,步骤明确 | 适合分治、树形或图形问题 |
可读性 | 简单任务更直观 | 复杂结构更直观(如树的遍历) |
性能开销 | 更高效,无额外栈空间 | 每次调用会消耗栈空间 |
可维护性 | 复杂问题不易扩展 | 思路清晰,符合数学表达逻辑 |
错误易发点 | 循环条件控制 | 容易造成栈溢出、忘记递归出口 |
✅ 举个典型例子:树的遍历
假设你在游戏里写一个技能树系统,每个技能点可能有多个子技能。
用递归写:
def traverse(skill_node):print(skill_node.name)for child in skill_node.children:traverse(child)
用 for + 栈写:
stack = [root]
while stack:node = stack.pop()print(node.name)stack.extend(node.children)
递归的写法更短,更符合“每个技能点都做同样的事”的逻辑,可读性强,代码更贴近问题本身的结构。
✅ 什么时候选择递归?
- 操作树结构、图结构(如遍历、搜索);
- 问题天然有递归定义(如斐波那契、阶乘、汉诺塔);
- 需要进行回溯、分治、深度优先搜索等;
- 写算法时为了清晰表达思路。
⚠️ 注意:
递归虽然优雅,但也容易出错:
- 如果没有出口条件,容易栈溢出;
- 对性能敏感的程序中,可能需要用循环或栈代替递归(叫做尾递归优化或递归转迭代)。
总结:
递归的好处是:代码更简洁、逻辑更自然,特别适合解决自结构问题;但它在性能和稳定性上不如循环,需要谨慎使用。
在实际工作中,如果功能简单、可控,推荐优先用 for;如果是多层结构、递归定义的问题,就大胆用递归。
python2和python3的区别
Python2 和 Python3 的主要区别集中在语法、标准库、字符编码和底层实现等方面。Python3 是 Python 官方推荐使用的版本,Python2 已在 2020 年正式停止支持。
下面从几个核心角度来对比这两个版本:
1. 打印语法的不同
- Python2:
print
是一个语句
print "Hello, world"
- Python3:
print
是一个函数
print("Hello, world")
2. 字符串的处理(编码)
- Python2 默认字符串是 ASCII 编码,
str
是字节串,unicode
是独立类型
s = "你好" # 报错,默认 ASCII
- Python3 默认字符串是 Unicode 编码,
str
就是 Unicode,bytes
用于字节
s = "你好" # 正常,默认 unicode
3. 整数除法行为
- Python2:整数除法默认是向下取整(地板除)
print(5 / 2) # 输出 2
- Python3:整数除法默认是真实除法(保留小数)
print(5 / 2) # 输出 2.5
若要地板除:使用 //
4. xrange
与 range
- Python2:
-
range
返回的是列表xrange
返回的是生成器(节省内存)
- Python3:
-
range
就是生成器,xrange
被取消了
5. input()
的行为
- Python2:
-
input()
会执行输入的表达式,容易有安全风险raw_input()
才是读取字符串
- Python3:
-
input()
始终返回字符串,语义更清晰
6. 库兼容性
- Python3 的标准库进行了重构和改名,例如:
-
urllib
→ 拆分成urllib.request
,urllib.parse
等Queue
→queue
- 更统一的异常语法:
except Exception as e:
7. 其他特性差异(Python3 的优势)
- 支持更多高级语法,如:
-
f""
格式化字符串- 类型注解(type hints)
async/await
原生支持异步编程- 更清晰的迭代器、生成器写法
- 更好的 Unicode 支持
- 更一致的语言设计理念(移除了旧的不一致用法)
总结对比表:
功能/特性 | Python2 | Python3 |
打印语法 |
|
|
字符编码 | 默认 ASCII,需手动处理 Unicode | 默认 Unicode,字符串更统一 |
除法行为 | 5 / 2 = 2 | 5 / 2 = 2.5 |
/ |
是列表, 是生成器 |
是生成器,没有 |
输入函数 |
|
|
官方支持 | 已停止支持 | 持续更新和优化 |
建议:
如果你正在做任何新项目或测试框架搭建,一定要用 Python3。
Python2 已经是历史版本,虽然一些老项目可能还在维护,但新系统已经不推荐再使用。