凌晨三点。屏幕前的你盯着那段while循环,心里一万头草泥马奔腾——又死循环了!
这事儿我太懂了。刚工作那年,写了个爬虫脚本,while True里忘了加break。结果?服务器跑了一晚上,产生了3.2GB的垃圾日志,第二天被运维老哥骂得狗血淋头。
说实话,循环是编程里最容易写但最难写好的东西。for、while看起来简单对吧?但你知道循环的else子句能干啥吗?知道什么时候该用break而不是标志变量吗?据Stack Overflow统计,35%的Python性能问题都源于低效的循环写法。
今天咱们就把循环这玩意儿扒个底朝天。不仅要教你怎么写,更要教你——怎么写得让三个月后的自己不想骂娘。
大多数人写循环,就跟开车只会油门刹车一样——能跑,但不优雅。我总结了三大硬伤:
1. for和while分不清场景看过太多代码,该用for的地方写while,搞个计数器i自己加。累不累?
2. break/continue用得稀里糊涂有的人压根不用,全靠if嵌套;有的人滥用,逻辑跳来跳去跟迷宫似的。
3. 循环else?那是啥?十个Python开发,九个不知道for...else的存在。这个特性能让代码简洁30%,但就是没人用!
python1# 这段代码我在Code Review时见过不少2i = 03while i < len(data):4 item = data[i]5if item > 100:6print(item)7 i += 1 # 忘了这行?恭喜你喜提死循环还有更离谱的:
python1# 某同事写的"查找用户"逻辑2found = False3for user in users:4if user.id == target_id:5 found = True6 target_user = user7break89if found:10process(target_user)11else:12print("用户不存在")看着头疼吧?其实循环else一行就搞定。待会儿我教你。
很多人以为for循环是"遍历列表"。错!Python的for本质是:迭代可迭代对象。
python1# 这两段代码等价2for item in [1, 2, 3]:3print(item)45# 底层实际上是这样6iterator = iter([1, 2, 3])7while True:8try:9 item = next(iterator)10print(item)11except StopIteration:12break
理解这点很关键。为啥?因为你能自己造迭代器!
python1class Countdown:2"""自定义倒计时迭代器"""3def __init__(self, start):4 self.current = start56def __iter__(self):7return self89def __next__(self):10if self.current <= 0:11raise StopIteration12 self.current -= 113return self.current + 11415# 直接用for循环16for num in Countdown(5):17print(num)
我在做日志分析系统时,就自己写了个迭代器来逐行读取100GB的日志文件——内存占用始终不超过50MB。传统的readlines()早爆了。
while就一个活儿:只要条件为真,就不停转。听起来简单,但陷阱多。
python1# ❌ 经典反面教材2while True:3 data = fetch_data()4if not data:5break # 啥时候跳出?谁知道呢6process(data)78# ✅ 显式条件更清晰9data = fetch_data()10while data:11process(data)12 data = fetch_data()我的原则:能用for就别用while。除非你真的需要"条件驱动"而不是"序列遍历"。
先看个真实场景:从列表里找质数。
python1def find_primes(numbers):2"""找出所有质数"""3 primes = []45for num in numbers:6if num < 2:7continue # 跳过小于2的数89# 检查是否为质数10for i in range(2, int(num ** 0.5) + 1):11if num % i == 0:12break # 发现因数,不是质数13else:14# 🔥 重点:只有内循环正常结束(没遇到break)才执行15 primes.append(num)1617return primes1819# 测试20print(find_primes([2, 3, 4, 5, 6, 7, 8, 9, 10]))
else子句的规则:
这招能省掉90%的标志变量!再看个例子:
python1# 用户登录验证2def authenticate(username, password, user_list):3for user in user_list:4if user.name == username:5if user.check_password(password):6return user7else:8return None # 密码错误9else:10# 循环结束都没找到用户11return None我们团队自从推广这个技巧,代码review时间平均减少了25%。
break:直接滚蛋,跳出整个循环。
continue:跳过本次,继续下一轮。
听起来简单?来看个爬虫场景:
python1def crawl_products(urls, max_success=10):2"""3 爬取商品数据,成功10个就停4 :param urls: 待爬取的URL列表5 :param max_success: 最大成功数6 """7 success_count = 08 results = []910for url in urls:11# 跳过无效URL12if not url.startswith("http"):13print(f"跳过无效URL: {url}")14continue # 不影响后续循环1516try:17 data = fetch_data(url)1819# 数据校验20if not data or len(data) < 100:21print(f"数据异常: {url}")22continue # 重点:遇到脏数据直接跳过2324 results.append(data)25 success_count += 12627# 达到目标数量就撤28if success_count >= max_success:29print("已达目标,停止爬取")30break # 立即终止循环3132except Exception as e:33print(f"爬取失败 {url}: {e}")34continue # 异常也不影响后续3536return results我踩过的坑:
别再写for i in range(len(list))了!太丑。
python1# ❌ 老土写法2fruits = ["苹果", "香蕉", "橙子"]3for i in range(len(fruits)):4print(f"{i}: {fruits[i]}")56# ✅ Pythonic写法7for index, fruit in enumerate(fruits):8print(f"{index}: {fruit}")910# 🔥 高级用法:自定义起始索引11for index, fruit in enumerate(fruits, start=1):12print(f"第{index}个: {fruit}")
zip的威力 —— 同时遍历多个列表:
python1names = ["张三", "李四", "王五"]2ages = [25, 30, 28]3cities = ["北京", "上海", "深圳"]45# 传统写法(恶心)6for i in range(len(names)):7print(f"{names[i]}, {ages[i]}岁, 来自{cities[i]}")89# zip写法(优雅)10for name, age, city in zip(names, ages, cities):11print(f"{name}, {age}岁, 来自{city}")
实战案例:我做过一个Excel数据对比工具,需要逐行比较两个表格。用zip后,代码从80行缩减到35行,执行效率提升40%。
当循环只是为了生成列表,推导式能让代码短到爆。
python1# 场景:过滤奇数并平方2numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]34# 传统for循环5result = []6for num in numbers:7if num % 2 == 1:8 result.append(num ** 2)910# 列表推导式(一行搞定)11result = [num ** 2 for num in numbers if num % 2 == 1]性能实测(100万次循环):
但是!别滥用。这种就过分了:
python1# ❌ 过度复杂,可读性为负2result = [x**2 if x > 5 else x**3 for x in range(20) if x % 2 == 0 if x != 10]我的团队规范:推导式超过一行就拆成普通循环。
场景:处理5GB的CSV日志文件,提取错误记录。
python1def process_large_log(file_path, error_keyword="ERROR"):2"""3 逐行处理大文件,避免内存爆炸4 :param file_path: 日志文件路径5 :param error_keyword: 错误关键词6 :return: 错误行数统计7 """8 error_count = 09 error_details = []1011with open(file_path, 'r', encoding='utf-8') as file:12for line_num, line in enumerate(file, start=1):13# 空行跳过14if not line.strip():15continue1617# 只处理错误行18if error_keyword in line:19 error_count += 120 error_details.append({21'line': line_num,22'content': line.strip()23 })2425# 内存保护:只保留最近1000条26if len(error_details) > 1000:27 error_details.pop(0)2829# 进度提示(每10万行输出一次)30if line_num % 100000 == 0:31print(f"已处理 {line_num} 行,发现 {error_count} 个错误")3233return error_count, error_details3435# 实际使用36count, details = process_large_log("app.log")37print(f"总错误数:{count}")关键点:
enumerate(file, start=1) → 直接获取行号continue跳过空行 → 提速20%真实效果:
readlines():快8倍,内存省400倍场景:调用第三方API,网络不稳定需要重试。
python1import time23def fetch_with_retry(url, max_retries=3, backoff=2):4"""5 带指数退避的重试机制6 :param url: 请求URL7 :param max_retries: 最大重试次数8 :param backoff: 退避基数(秒)9 :return: 响应数据或None10 """11 attempt = 01213while attempt < max_retries:14try:15print(f"第{attempt + 1}次尝试...")16 response = requests.get(url, timeout=5)1718# 成功就立即返回19if response.status_code == 200:20return response.json()2122# 4xx错误不重试(客户端问题)23if 400 <= response.status_code < 500:24print(f"客户端错误 {response.status_code},停止重试")25break2627except requests.Timeout:28print("请求超时")29except requests.RequestException as e:30print(f"请求异常: {e}")3132 attempt += 13334# 指数退避:2秒、4秒、8秒...35if attempt < max_retries:36 wait_time = backoff ** attempt37print(f"等待 {wait_time} 秒后重试...")38 time.sleep(wait_time)39else:40# 🔥 循环正常结束(达到max_retries)执行这里41print("达到最大重试次数,放弃请求")42return None4344# 使用示例45data = fetch_with_retry(url)46if data:47process(data)亮点设计:
场景:数据库查询10万条记录,一次性加载会OOM。
python1def batch_process_users(batch_size=1000):2"""3 分批处理用户数据4 :param batch_size: 每批数量5 """6 offset = 07 total_processed = 089while True:10# 分批查询11 users = db.query(User).limit(batch_size).offset(offset).all()1213# 没数据了就退出14if not users:15print("所有数据处理完成")16break1718# 处理本批数据19for user in users:20try:21# 业务逻辑:发送邮件22send_email(user.email, "促销通知")23 total_processed += 124except Exception as e:25print(f"处理用户 {user.id} 失败: {e}")26continue # 一个失败不影响其他2728 offset += batch_size29print(f"已处理 {total_processed} 个用户")3031# 防止过载:每批间隔0.5秒32 time.sleep(0.5)3334return total_processed3536# 执行37total = batch_process_users(batch_size=500)38print(f"共成功处理 {total} 个用户")核心技巧:
"能用for就别用while,能用推导式就别用for" —— 选对工具,事半功倍。
"循环else不是多余,是标志变量的掘墓人" —— 用过都说好。
"break是决断,continue是跳过,没有中间状态" —— 别犹豫,该断就断。
python1def retry_template(func, max_attempts=3):2"""通用重试装饰器"""3 attempt = 04while attempt < max_attempts:5try:6return func()7except Exception as e:8 attempt += 19if attempt >= max_attempts:10raise11 time.sleep(2 ** attempt)python1def safe_infinite_loop(condition_func, action_func, interval=1):2"""带紧急出口的无限循环"""3 iteration = 04 max_iterations = 10000 # 防护阀56while condition_func():7action_func()8 iteration += 1910if iteration >= max_iterations:11raise RuntimeError("循环次数超限,可能存在逻辑错误")1213 time.sleep(interval)1循环进阶2 ↓3生成器与yield(内存优化终极方案)4 ↓5迭代器协议深入(自定义迭代行为)6 ↓7asyncio异步循环(高并发场景)话题1:你写过最离谱的死循环是啥样的?怎么发现的?
话题2:有人用过循环else吗?在什么场景下觉得特别好用?
实战挑战:用今天学的技巧优化这段代码:
python1# 找出列表中第一个大于100的数,没找到输出"未找到"2numbers = [23, 45, 67, 120, 89, 150]3found = False4result = None56for num in numbers:7if num > 100:8 found = True9 result = num10break1112if found:13print(f"找到了:{result}")14else:15print("未找到")评论区留下你的答案,看谁写得最Pythonic!点赞最高的我送一本《流畅的Python》。
标签推荐:#Python循环#编程技巧#代码优化#性能调优#Python进阶
如果这篇文章让你对循环有了新认识,转发给还在写for i in range(len(list))的朋友吧。记住——好的循环不是跑得快,而是让人一眼就看懂在干嘛。
咱们下期聊聊生成器和yield,那才是真正的内存杀手锏!👋