我最开始的时候是Java程序员,后来当了5年全职宝妈,现在all in AI赛道。专注AI基础知识和AI前沿科技分享。最近在系统学Python进阶内容,发现有3个特性特别有意思——学会了之后真的能让代码上一个档次。它们就是装饰器、生成器、上下文管理器 ,今天就和大家聊聊这Python三件套到底怎么用。你有没有遇到过这种情况:写了十几个爬虫函数,每个都要加计时、重试、代理轮换的逻辑?代码重复度真的太高了。说白了,装饰器就是一个 "函数包装机" 。给你一个函数,它帮你包一层,加点额外功能,再还给你。原函数该干嘛还干嘛,但多了点"超能力"。就像给手机套个壳。手机还是那个手机,但多了防摔功能。def my_decorator(func):def wrapper():print("执行前")func() # 调用原函数print("执行后")return wrapper@my_decoratordef say_hello():print("你好")say_hello()
`@mydecorator` 这行代码,就是给你的 `sayhello` 函数"套了个壳"。等价于 `sayhello = mydecorator(say_hello)` 。import timefrom functools import wrapsdef timer(func):@wraps(func) # 这行很重要!保留原函数信息def wrapper(args, *kwargs):start = time.time()result = func(args, *kwargs)print(f"{func.name} 耗时:{time.time() - start:.2f}秒")return resultreturn wrapper@timerdef slow_function():time.sleep(1)print("执行完毕")slow_function()
执行完毕slow_function 耗时:1.00秒
有时候你想让装饰器"可配置"。比如重试次数,有的函数网络不稳定想重试3次,有的只需要重试1次。def retry(max_attempts=3, delay=1):def decorator(func):@wraps(func)def wrapper(args, *kwargs):for attempt in range(1, max_attempts + 1):try:return func(args, *kwargs)except Exception as e:if attempt == max_attempts:raiseprint(f"第{attempt}次失败,{delay}秒后重试...")time.sleep(delay)return wrapperreturn decorator@retry(max_attempts=3, delay=2)def fetch_data():pass
- 第一层 `retry()` 接收装饰器自己的参数( `max_attempts` 、 `delay` )
- 第二层 `decorator()` 接收被装饰的函数
- 第三层 `wrapper()` 执行实际的包裹逻辑
我猜你注意到代码里的 `@wraps(func)` 了。这玩意儿不是摆设,它能解决一个"身份危机"。def bad_decorator(func):def wrapper():func()return wrapper@bad_decoratordef test():"""这是测试函数的文档"""passprint(test.name) # 输出:wrapper ❌print(test.doc) # 输出:None ❌from functools import wrapsdef good_decorator(func):@wraps(func)def wrapper():func()return wrapper@good_decoratordef test2():"""这是测试函数的文档"""passprint(test2.name) # 输出:test2 ✅print(test2.doc) # 输出:这是测试函数的文档 ✅
写装饰器一定带上 `@wraps`,这是专业和业余的分水岭。你有没有遇到过这种情况:要处理100GB的日志文件,Python直接内存爆炸?lst = [x*2 for x in range(1000000)] # 占用大量内存gen = (x*2 for x in range(1000000)) # 仅创建生成器对象
def countupto(max_num):count = 1while count <= max_num:yield countcount += 1for num in countupto(5):print(num) # 1, 2, 3, 4, 5
- 调用 `countupto(5)` 不会执行函数体,而是返回一个生成器对象
- `next()` 触发函数执行,遇到 `yield` 暂停
- 函数状态被保存,下次 `next()` 从暂停位置继续
def readlargefile(filepath):"""内存友好的大文件读取"""with open(filepath, 'r') as f:for line in f:yield line.strip()for line in readlargefile('huge_log.txt'):process(line) # 每次只处理一行
写文件操作的时候,你还在手动 `f.close()` 吗?f = open("test.txt", "r")data = f.read()f.close()with open("test.txt", "r") as f:data = f.read()
这就是上下文管理器——管理资源的生命周期,确保资源在使用前正确打开,在使用后正确关闭。class FileManager:def init(self, filename, mode):self.filename = filenameself.mode = modeself.file = Nonedef enter(self):self.file = open(self.filename, self.mode)return self.filedef exit(self, exctype, excval, exc_tb):if self.file:self.file.close()return False # 不抑制异常with FileManager("example.txt", "w") as f:f.write("Hello, World!")
3.2 用 @contextmanager 更简洁from contextlib import contextmanagerimport time@contextmanagerdef timer(name):start = time.time()yieldprint(f"{name} 耗时: {time.time() - start:.2f}秒")with timer("数据处理"):pass
`yield` 前面的代码相当于 `enter`,后面的代码相当于 `exit`。from contextlib import contextmanagerimport sqlite3@contextmanagerdef transaction(db_path):conn = sqlite3.connect(db_path)try:yield connconn.commit()except Exception:conn.rollback()raisefinally:conn.close()with transaction('app.db') as conn:cursor = conn.cursor()cursor.execute('UPDATE users SET name=? WHERE id=?', ('Alice', 1))
@timer@retry(max_attempts=3)def fetch_page(url):with open('cache.txt', 'r') as f:return f.read()
装饰器从下往上执行: `fetch_page` → `@retry` → `@timer` | | |
|---|
| | |
|---|
| | |
|---|
| with、__enter__/__exit__、资源管理 | |
|---|
记住一个原则 :好的代码不是"能跑就行",而是"优雅、可维护、易扩展"。这三个特性,就是让你从"会写代码"到"写好代码"的阶梯。如果你觉得有用, 欢迎转发给身边想学Python的朋友 。还有什么想了解的AI或编程知识? 评论区告诉我 ,咱们下期见!- 经历:大厂程序员 → 全职宝妈 → 米核AI合伙人
- 当前:all in AI赛道,专注AI前沿知识分享