Python冷知识
1.在一些比较简单的程序里,多线程要比单线程快一些。这是因为多线程能提高CPU的利用率
2.@staticmethod是静态方法修饰器。作用:正常来说在class中,一个方法必须要传递一个self参数,但是可以使用该修饰器定义一个静态方法,不依靠self也可以正常运行
3.在Python中,函数定义后的 -> int 是类型注解(Type Hint),仅用于提示函数的返回值预期是 int 类型,但不会强制转换返回值类型。如果实际返回的类型不符合注解,Python不会报错,也不会自动转换。
4.Python 行内 for 循环详解
什么是行内 for 循环
Python 中的「行内 for 循环」通常指的是推导式(Comprehension),它允许用一行简洁的代码来创建新的数据结构。主要包括四种类型:
1. 列表推导式 (List Comprehension) - 最常用
基本语法
[expression for item in iterable]
示例
传统写法:
squares = []
for x in range(5):
squares.append(x ** 2)
列表推导式写法:
squares = [x ** 2 for x in range(5)]
# 结果: [0, 1, 4, 9, 16]
2. 带条件的列表推导式
语法
[expression for item in iterable if condition]
示例
传统写法:
even_squares = []
for x in range(10):
if x % 2 == 0:
even_squares.append(x ** 2)
列表推导式写法:
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]
# 结果: [0, 4, 16, 36, 64]
3. 嵌套循环的列表推导式
语法
[expression for item1 in iterable1 for item2 in iterable2]
示例
传统写法:
pairs = []
for i in range(2):
for j in range(3):
pairs.append((i, j))
列表推导式写法:
pairs = [(i, j) for i in range(2) for j in range(3)]
# 结果: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
4. 其他类型的推导式
集合推导式 (Set Comprehension)
names = ["Alice", "Bob", "Alice", "Charlie"]
unique_names = {name for name in names}
# 结果: {'Charlie', 'Bob', 'Alice'} (顺序可能不同)
字典推导式 (Dictionary Comprehension)
numbers = [1, 2, 3]
squares_dict = {x: x ** 2 for x in numbers}
# 结果: {1: 1, 2: 4, 3: 9}
生成器表达式 (Generator Expression)
# 列表推导式 - 立即创建整个列表
list_comp = [x ** 2 for x in range(1000000)]
# 生成器表达式 - 惰性求值,节省内存
gen_exp = (x ** 2 for x in range(1000000))
# 结果: <generator object <genexpr> at 0x...>
使用建议和最佳实践
1. 优先考虑可读性
清晰的情况:
result = [x * 2 for x in numbers if x > 5]
可能过于复杂的情况(建议用传统循环):
# 不推荐 - 难以阅读
complex_result = [x * y for x in list_a if x > 5 for y in list_b if y < x]
2. 不要用于副作用
不推荐(创建无用列表):
[print(name) for name in names] # 错误用法
推荐写法:
for name in names:
print(name) # 正确用法
3. 生成器表达式更省内存
当只需要迭代结果一次时,优先使用生成器表达式:
# 处理大文件时特别有用
large_data = (process_line(line) for line in open('large_file.txt'))
for item in large_data:
# 逐行处理,不占用大量内存
pass
总结表格
| 类型 | 语法 | 输出结果 | 特点 |
|---|---|---|---|
| 列表推导式 | [x for x in ...] |
list |
最常用 |
| 集合推导式 | {x for x in ...} |
set |
自动去重 |
| 字典推导式 | {k:v for k,v in ...} |
dict |
键值对 |
| 生成器表达式 | (x for x in ...) |
generator |
惰性求值,省内存 |
核心原则: 在保持代码可读性的前提下使用推导式,避免为了简洁而牺牲代码的清晰度。
5. 迭代器只能遍历一次,就会耗尽
6. 线程池的原理
新建线程系统需要分配资源、终止线程系统需要回收资源
如果可以重用线程,则可以减去新建/终止的开销
使用线程池的好处:
- 提升性能:因为减去了大量新建、终止线程的开销,重用了线程资源
- 适用场景:适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短
- 防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大相应变慢等问题
- 代码优势:使用线程池的语法比自己新建线程执行线程更加简洁
7.线程的五种生命状态:新建、就绪、运行、阻塞、终止
8.如果遇到了CPU密集型计算,多线程反而会降低执行速度
9.函数装饰器(注解)使用方法
一、什么是“注解”
在 Python 里,其实没有真正的“注解”语法。我们常说的 “@login_required” 等,其实是 函数装饰器(Function Decorator)。
语法糖形式:
@decorator
def func():
...
等价于:
def func():
...
func = decorator(func)
也就是说:
@decorator只是语法糖,在定义函数时自动调用decorator(func),并用返回值替换原函数。
二、简单示例:自定义装饰器
def my_decorator(func):
def wrapper():
print("调用前")
func()
print("调用后")
return wrapper
@my_decorator
def hello():
print("你好,Python!")
hello()
输出结果:
调用前
你好,Python!
调用后
解释:
- 定义
hello()时,Python 自动执行hello = my_decorator(hello) my_decorator()返回一个新的函数wrapper- 调用
hello()实际上执行的是wrapper()。
三、@符号语法的执行时机
在 Python 解释器加载函数定义时,@ 语法会立即执行。
print("开始定义")
@my_decorator
def test():
print("运行 test")
print("定义结束")
执行顺序:
开始定义
定义结束
此时已经执行了 my_decorator(test),定义的 test 已被替换为装饰后的函数。
四、带参数的装饰器(装饰器工厂)
简单示例1:
def decorator_with_args(arg):
print("外层被执行:收到参数", arg)
def decorator(func):
print(arg) #这里可以使用外层参数
print("中层被执行:收到函数", func.__name__)
def wrapper():
print("内层被执行:真正运行函数")
func()
return wrapper
return decorator
@decorator_with_args("ABC")
def test():
print("执行 test()")
test()
输出:
外层被执行:收到参数 ABC
ABC
中层被执行:收到函数 test
内层被执行:真正运行函数
执行 test()
简单示例2:
如果想写成这种形式:
@login_required(login_url='/login/')
那就是一个装饰器工厂。示例如下:
def login_required(login_url='/accounts/login/'):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"需要登录,跳转到:{login_url}")
return func(*args, **kwargs)
return wrapper
return decorator
执行过程:
login_required('/login/')返回一个具体的装饰器函数;- 该装饰器函数再包裹目标函数;
- 最终形成嵌套结构。
简单示例3:
def decorator_with_args(arg):
print("外层被执行:收到参数", arg)
def decorator(func):
print(arg) # 可以访问外层参数
print("中层被执行:收到函数", func.__name__)
def wrapper(*args, **kwargs): # ← 关键修改:接收任意参数
print("内层被执行:真正运行函数")
print("test函数收到的位置参数:", args)
print("test函数收到的关键字参数:", kwargs)
return func(*args, **kwargs) # ← 记得传递参数给原函数
return wrapper
return decorator
@decorator_with_args("ABC")
def test(x, y):
print("执行 test()", x, y)
# 调用时传入两个参数
test(10, 20)
简单示例4:
# main.py
def decorator_with_args(arg):
print("外层被执行:收到参数", arg)
def decorator(func):
print(arg) # 可以访问外层参数
print("中层被执行:收到函数", func.__name__)
def wrapper(*args, **kwargs): # ← 关键修改:接收任意参数
print("内层被执行:真正运行函数")
print("test函数收到的位置参数:", args)
print(args[0])
print("test函数收到的关键字参数:", kwargs)
return func(*args, **kwargs) # ← 记得传递参数给原函数
return wrapper
return decorator
# 6.py
from main import decorator_with_args
@decorator_with_args("ABC") # 此时(定义的时候)就已经执行了外层
def test(x, y):
print("执行 test()", x, y)
print("-----分隔线-----")
print("调用 test(10, 20)")
test(10, 20)
输出:
外层被执行:收到参数 ABC
ABC
中层被执行:收到函数 test
-----分隔线-----
调用 test(10, 20)
内层被执行:真正运行函数
test函数收到的位置参数: (10, 20)
10
test函数收到的关键字参数: {}
执行 test() 10 20
10. *args 和 **kwargs 是什么?
-
*args:代表 任意数量的位置参数,会被打包成一个元组。 -
**kwargs:代表 任意数量的关键字参数,会被打包成一个字典。
示例:
def show(*args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
show(1, 2, 3, name="Tom", age=18)
输出:
args: (1, 2, 3)
kwargs: {'name': 'Tom', 'age': 18}
11.魔术方法
__name__ == "__main__": 在该程序作为主程序(直接运行的程序)时执行以下代码__file__:获取当前脚本所在路径
12.多继承中的MRO调用规则:
- Method Resolution Order(MRO) 决定了多继承中方法或属性的查找顺序
- super() 会调用 MRO 中当前类之后的第一个类的方法。
- 避免直接调用父类构造函数,多继承时用 super() 可确保每个父类方法只调用一次。
示例代码:
class A:
def __init__(self):
print("A init")
class B(A):
def __init__(self):
print("B init")
super().__init__()
class C:
def __init__(self):
print("C init")
class D(B, C):
def __init__(self):
print("D init")
super().__init__()
print("MRO:", D.__mro__)
d = D()
输出:
MRO: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>)
D init
B init
A init
C init
说明:
-
super().__init__()按 MRO 顺序调用父类构造函数。 -
每个类的
__init__都只调用一次,不会重复。
13.raise关键字:主动抛出异常
在 Python 中,raise 是一个 关键字,用于 主动触发异常(即抛出异常),通常用于错误处理或控制程序流程。它可以让程序在遇到不符合预期的情况时停止执行或交给上层捕获处理。
14.在尝试清空字典操作时,应该使用dict.clear(),而非dict={}
dict.clear()的作用原理是改变原本的dict对象dict={}的作用原理是,将一个新的字典对象赋值给dict变量
什么情况下会有不同?
data = {"abc":"abc"}
def add(data:list):
data = {}
add(data)
print(data)
外部 data → 指向 { "abc": "abc" }
进入函数 add:
局部 data → 也指向 { "abc": "abc" }
执行 data = {}:
局部 data → 改为指向新对象 {}
外部 data → 仍然指向 { "abc": "abc" }
注:在此示例中add()函数内的data是对全局变量data 的一个引用。所以让局部变量指向一个新的字典
{}(改变“变量名绑定”,不会影响外部对象)不会修改其原变量的值
data = {"abc":"abc"}
def add(data):
data.clear() # ← 清空对象内容
add(data)
print(data) # {}
外部 data → 指向 { "abc": "abc" }
传入函数 data → 也指向 { "abc": "abc" }
执行 data.clear()
字典对象本身被清空,但引用没变!
外部 data → 指向 {}
局部 data → 指向 {}
注:
data.clear()修改“对象本身内容”,会影响所有引用它的变量
15.在使用打包库对Python程序进行打包时,一定要留意“打包风险区”,有可能会对程序造成意外影响
┌─────────────────────────────────────────────┐
│ 操作系统启动 │
│ (自启动 / 双击 EXE / 登录) │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 🟥 阶段 1:EXE 解压 & Python 运行时初始化 │
│ │
│ • PyInstaller 解包 │
│ • sys.path 初始化 │
│ • 系统时间 / 路径可能未稳定 │
│ │
│ ❌ 高风险: │
│ - 读取时间 │
│ - 读写文件 │
│ - 修改全局状态 │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 🟥 阶段 2:模块 import & 顶层代码执行 │
│ │
│ 你现在踩雷的位置就在这里 👇 │
│ │
│ current_date = time.strftime(...) ❌ │
│ │
│ ❌ 高风险: │
│ - 顶层变量 = 时间 / IO / 状态 │
│ - 启动线程 │
│ - 跨天判断 │
│ │
│ ✅ 只允许: │
│ - 常量 │
│ - 类 / 函数定义 │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 🟢 阶段 3:进入 __main__ │
│ │
│ if __name__ == "__main__": │
│ │
│ ✅ 安全区: │
│ - 设置工作目录 │
│ - 初始化全局状态 │
│ - 读取配置 / JSON │
│ - 同步 current_date │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 🟢 阶段 4:QApplication 创建 │
│ │
│ app = QApplication(sys.argv) │
│ │
│ ✔ GUI 环境就绪 │
│ ✔ 事件系统准备完成 │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 🟢 阶段 5:窗口初始化(init_data) │
│ │
│ window = MyMainWindow() │
│ ├─ init_data() │
│ ├─ 读取当天 JSON │
│ ├─ 恢复表格 │
│ │
│ ✅ 这是你应该放“当天逻辑”的地方 │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 🟡 阶段 6:线程 / 定时器启动 │
│ │
│ window_monitor_thread.start() │
│ auto_save_thread.start() │
│ │
│ ⚠️ 中风险: │
│ - 必须保证状态已初始化完成 │
│ - 不能再清空 dict │
│ - 不能覆盖历史数据 │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 🟢 阶段 7:进入事件循环 │
│ │
│ app.exec_() │
│ │
│ ✔ 程序进入稳定运行态 │
│ ✔ 时间 / 文件 / UI 都可靠 │
└─────────────────────────────────────────────┘
16.打包后 pathlib.Path(__file__) 无法正确获取路径的原因与解决方案
当你使用 PyInstaller 的 --onefile(-F) 模式打包程序时,
PyInstaller 会在运行时自动将所有文件 临时解压 到一个随机生成的目录,例如:
C:\Users\<用户名>\AppData\Local\Temp\_MEI151002\
然后从这个临时目录中执行你的程序。
因此,执行以下代码时:
pathlib.Path(__file__)
返回的路径自然会是:
C:\Users\<用户名>\AppData\Local\Temp\_MEI151002\main.py
而不是你的项目原始目录。
✅ 正确的解决方案
要兼容 源码运行 与 打包运行 两种情况,应使用以下写法:
import sys
import pathlib
if getattr(sys, 'frozen', False):
# 如果是被打包的 exe 运行
app_path = pathlib.Path(sys.executable).parent
else:
# 如果是源码运行
app_path = pathlib.Path(__file__).parent
print("程序运行目录:", app_path)
说明:
sys.frozen是 PyInstaller 在运行时自动注入的标志。sys.executable指向当前可执行文件的完整路径。__file__仅在源码状态下可用。
🧱 示例修正版
import sys
import pathlib
class Functions:
def __init__(self):
self.key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
self.app_name = "DeviceUsageTime"
if getattr(sys, 'frozen', False):
# 打包后运行
self.app_path = pathlib.Path(sys.executable).parent
else:
# 源码运行
self.app_path = pathlib.Path(__file__).parent
print("当前程序路径:", self.app_path)
🧠 为什么 PyInstaller 这么做?
在 --onefile 模式下,PyInstaller 会:
- 将所有依赖文件压缩进一个 EXE。
- 启动时解压到
_MEIxxxxx临时文件夹。 - 从该文件夹运行虚拟环境。
- 程序退出后自动删除该文件夹。
因此,__file__ 永远会指向这个临时路径。
若要获取 真正的 EXE 所在目录(例如存放配置文件、日志、资源等),
请使用 sys.executable。
🧩 推荐封装:资源路径函数
可以定义一个通用函数,在源码和打包模式下都能正确找到文件路径:
def resource_path(relative_path):
"""获取资源文件的绝对路径,兼容打包和源码运行"""
import sys, pathlib
if getattr(sys, 'frozen', False):
base_path = pathlib.Path(sys.executable).parent
else:
base_path = pathlib.Path(__file__).parent
return base_path / relative_path
使用方式:
config_file = resource_path("config.json")
这样即使打包成 EXE,也能正确读取配置文件或资源文件。
17.Python中的平替Switch的方法
一、Python 3.10+:match-case(官方“switch”)
这是 Python 官方给出的 switch 等价物
基本用法
def handle(code):
match code:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Server Error"
case _:
return "Unknown"
特点
-
✅ 官方语法
-
✅ 自动 break(不会贯穿)
-
✅ 支持复杂匹配(不是只能匹配值)
多值匹配
match status:
case 200 | 201:
print("Success")
二、Python < 3.10:字典模拟 switch(最常用)
基本写法(强烈推荐)
def handle(code):
return {
200: "OK",
404: "Not Found",
500: "Server Error"
}.get(code, "Unknown")
带函数调用
def ok(): print("OK")
def not_found(): print("404")
actions = {
200: ok,
404: not_found
}
actions.get(code, lambda: print("default"))()
优点
-
✔ 版本兼容
-
✔ 性能好
-
✔ 可读性高
18.函数定义中* 的作用
在函数定义中,* 表示 之后的参数必须使用关键字传递(keyword-only arguments),不能用位置参数传递。
例子 1:list.sort
numbers = [3, 1, 2]
# 正确写法(关键字传参)
numbers.sort(key=len, reverse=True)
# 错误写法(位置参数)
numbers.sort(len, True) # ❌ 会报错
key和reverse在函数定义中位于*之后 → 必须写成key=...、reverse=...- 这是为了提高代码可读性,避免位置混乱
例子 2:自定义函数
def func(a, *, b, c):
print(a, b, c)
# 正确
func(1, b=2, c=3)
# 错误
func(1, 2, 3) # ❌ b 和 c 必须关键字传参
19.用%r来替换指定字符串
def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)
实际效果与str.format相同
20.or运算符
or 运算符从左到右判断,返回第一个为真的值;一旦左侧为真就停止计算右侧,这就是逻辑短路。
A or B
如果 A 为真 → 直接返回 A
如果 A 为假 → 返回 B
经典用途:设置默认值
name = user_input or "匿名用户"

浙公网安备 33010602011771号