Python 装饰器
一、装饰器是什么?
装饰器是 Python 中一种**'化妆师',它能在不修改原函数代码的前提下,给函数动态添加新功能**。
- 本质:一个接收函数作为参数,并返回新函数的工具。
- 作用:像给手机贴膜,既保护屏幕(原函数),又新增防摔功能(装饰逻辑)。
二、核心原理
- 函数是'对象':Python 中函数可以像变量一样传递,这是装饰器的基础。
- 闭包机制:装饰器通过嵌套函数(闭包)保留原函数,并包裹新功能。
汇总了 Python 面试核心知识点。涵盖装饰器的原理、实现及注意事项;深浅拷贝的区别与应用场景;常见数据结构(List、Set、Dict)特性对比及数组链表差异;List 去重方法;递归的使用场景与循环对比;以及 Python2 与 Python3 在语法、编码、库等方面的主要区别。旨在帮助开发者巩固基础,应对技术面试。
装饰器是 Python 中一种**'化妆师',它能在不修改原函数代码的前提下,给函数动态添加新功能**。
工作流程:
hello())。| 场景 | 作用 | 生活类比 |
|---|---|---|
| 权限验证 | 检查用户是否登录再执行函数 | 进小区前刷卡(装饰器是门禁系统) |
| 日志记录 | 自动记录函数调用时间和参数 | 飞机黑匣子(自动记录飞行数据) |
| 性能统计 | 计算函数运行耗时 | 跑步时用手表计时 |
| 缓存结果 | 避免重复计算(如 @lru_cache) | 备忘录(记下答案直接复用) |
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 的'功能外挂',用 @ 符号轻松实现代码增强!
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 浅拷贝 | 只复制对象本身,不复制内部的子对象 | 简单对象(如列表嵌套不深) |
| 深拷贝 | 递归复制对象及其所有子对象 | 复杂嵌套对象(如多层字典/列表) |
import copy
a = [1, [2, 3]]
b = copy.copy(a) # 或 a.copy() / list(a) / slice[:]
a[0](不可变类型)不影响 ba[1][0](可变子对象)会影响 bc = copy.deepcopy(a)
a 的哪一层,c 都完全不受影响a) 和室友 (b) 共用客厅的冰箱(子对象)。a) 给你建了完全一样的房子 (c)。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]] (不受影响)
不可变类型(数字/字符串/元组):
a = (1, [2]); b = copy.copy(a) → 修改 a[1] 仍会影响 b自定义对象:
__copy__() 和 __deepcopy__() 方法控制拷贝行为性能权衡:
总结:
| 类型 | 特点 | 示例 |
|---|---|---|
| 列表 (list) | 有序、可变、允许重复 | [1, 2, 2, 3] |
| 元组 (tuple) | 有序、不可变、允许重复 | (1, "a", True) |
| 集合 (set) | 无序、可变、不允许重复 | {1, 2, 3} |
| 字典 (dict) | 键值对、键不可重复 | {"name": "Alice", "age": 20} |
| 字符串 (str) | 不可变字符序列 | "hello" |
| 对比项 | List | Set |
|---|---|---|
| 顺序 | 保持插入顺序 | 无序(存储顺序不确定) |
| 重复元素 | 允许重复 | 自动去重 |
| 查找速度 | 慢(遍历查找,O(n)) | 极快(哈希表实现,O(1)) |
| 适用场景 | 需要保留顺序或重复数据时 | 去重、快速成员检测 |
示例:
names = ["Alice", "Bob", "Alice"] # List 允许重复
unique_names = {"Alice", "Bob"} # Set 自动去重
| 对比项 | 数组 | 链表 |
|---|---|---|
| 内存分配 | 连续内存,大小固定 | 非连续内存,动态扩展 |
| 访问速度 | 极快(O(1),直接索引) | 慢(O(n),需遍历节点) |
| 插入/删除 | 慢(需移动元素,O(n)) | 快(O(1),修改指针即可) |
| 适用场景 | 频繁随机访问,数据量固定 | 频繁增删,数据量变化大 |
通俗比喻:
list(实际是动态数组,非严格数组)array 模块(类型受限但更省内存)import array
arr = array.array('i', [1, 2, 3]) # 整型数组
collections.deque(双端队列,近似链表特性)class Node:
def __init__(self, val):
self.val = val
self.next = None
list,除非极端性能需求)总结:Python 的 list 和 set 分别解决顺序存储和快速查询的问题,而数组与链表的区别是内存结构的根本差异。
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 | 有序元素集合,支持增删改查 | ✅ | ✅ | Python list, Java List |
| vector | 动态数组,自动扩容,支持随机访问 | ✅ | ✅ | C++ vector, Java ArrayList |
| map | 键值对集合,key 唯一,用于快速查找 | ❌(通常无序) | ❌(key)✅(value) | Python dict, C++ map, Java HashMap |
list 或 vector 存储。map(如 Python 中的 dict)。list 去重、排序后显示。总结: 去重方法要结合'是否保留顺序'来选,三种数据结构各有用途,理解它们的特性可以帮助你在实际开发或测试中选择合适的数据结构。
使用递归的主要原因是为了让代码更简洁、结构更清晰,尤其适合处理具有'自相似'特征的问题,比如树结构、分形结构、回溯搜索、数学归纳等场景。
递归是指一个函数调用自身来解决问题,适用于下面这些情况:
| 对比点 | 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 的主要区别集中在语法、标准库、字符编码和底层实现等方面。Python3 是 Python 官方推荐使用的版本,Python2 已在 2020 年正式停止支持。
下面从几个核心角度来对比这两个版本:
print 是一个语句print "Hello, world"
print 是一个函数print("Hello, world")
str 是字节串,unicode 是独立类型s = "你好" # 报错,默认 ASCII
str 就是 Unicode,bytes 用于字节s = "你好" # 正常,默认 unicode
print(5 / 2) # 输出 2
print(5 / 2) # 输出 2.5
若要地板除:使用 //
xrange 与 rangerange 返回的是列表xrange 返回的是生成器(节省内存)range 就是生成器,xrange 被取消了input() 的行为input() 会执行输入的表达式,容易有安全风险raw_input() 才是读取字符串input() 始终返回字符串,语义更清晰urllib → 拆分成 urllib.request, urllib.parse 等Queue → queueexcept Exception as e:f"" 格式化字符串async/await 原生支持异步编程| 功能/特性 | Python2 | Python3 |
|---|---|---|
| 打印语法 | print "abc" | print("abc") |
| 字符编码 | 默认 ASCII,需手动处理 Unicode | 默认 Unicode,字符串更统一 |
| 除法行为 | 5 / 2 = 2 | 5 / 2 = 2.5 |
| range/xrange | range 是列表,xrange 是生成器 | range 是生成器,没有 xrange |
| 输入函数 | raw_input() | input() |
| 官方支持 | 已停止支持 | 持续更新和优化 |
如果你正在做任何新项目或测试框架搭建,一定要用 Python3。 Python2 已经是历史版本,虽然一些老项目可能还在维护,但新系统已经不推荐再使用。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online