
学Python到第四天是一个很微妙的节点。前三天你大概已经知道了变量、数据类型、列表、元组这些积木长什么样但写出来的代码多半是直来直去的顺序结构——从上往下执行没有任何分支遇到重复的事情只能复制粘贴。再往下学如果不引入条件判断和字典你会发现自己被困在一个很尴尬的境地能看懂代码但写不出能解决实际问题的程序。条件判断if/elif/else让代码有了大脑可以根据不同情况走不同逻辑字典dict则是最符合人类思维习惯的数据容器存名字、属性、配置项都靠它。这两个知识点单独拎出来都不难但把它们组合起来才是Python日常开发的真正起点。这篇就按我自己的学习节奏把第四天的重点、容易踩的坑和实战用法完整过一遍。1. 条件判断不只是if-else从执行流程说起1.1 为什么前三天写的代码总感觉没头脑先回忆一下前三天写的代码长什么样大概率是这样的name 张三 age 17 print(f姓名{name}年龄{age})这种代码最大的问题就是一根筋——无论输入什么结果都一样。但现实世界里的业务逻辑几乎全是条件性的用户年龄大于18岁才允许注册库存大于0才显示购买按钮成绩大于等于60分才算及格。没有if这些规则全部写不出来。条件判断的本质是让程序在某个节点上看一眼当前的数据状态然后决定接下来走哪条路。这个看一眼的动作在Python里就是布尔表达式求值——True走一条路False走另一条路。1.2 从if-elif-else看代码块与缩进的关联Python的缩进不是风格问题而是语法的一部分。别的语言用花括号{}圈定代码块Python用缩进层级表示哪些代码属于同一个块。score 85 if score 90: print(优秀) elif score 80: print(良好) elif score 60: print(及格) else: print(不及格)这段代码的执行逻辑是这样的程序从上往下依次判断每个条件一旦某个条件为True就执行它下面的缩进代码块执行完直接跳过整个if-elif-else结构哪怕后面的条件也成立也不再检查。这里有个新手最容易懵的点为什么写成elif score 80而不是独立写三个if区别在于独立if会被依次检查可能出现多个分支都被执行而elif是否则如果的缩写本质上是前一个不成立的前提下才检查这一个。如果你把上面改成三个独立ifscore85时会同时进入score 80的良好分支但score 90为False就不会进——看起来结果一样但如果age95三个独立if会打印优秀良好及格三行而elif链只会打印优秀一行。1.3 布尔求值与短路行为容易被忽略的细节条件判断的底层是布尔运算。Python里的布尔值只有True和False但任何对象都可以参与真假判断——隐式转换规则是数字0、空字符串、空列表[]、空字典{}、None都被视为False其他一律为True。这个规则在做数据校验时极其好用data {} if not data: print(字典是空的需要初始化)这一行比if len(data) 0更符合Python的惯用风格。再说说and和or的短路求值这是从C语言时代就传下来的特性但很多教程讲得太浅。A and B在A为False时直接返回A不再计算BA or B在A为True时直接返回A不再计算B。这意味着你可以在and里安全地调用可能不存在的东西user {name: 李四, age: 0} if user.get(age) and user[age] 18: print(已成年) else: print(未成年或年龄未知)当user.get(age)返回0时0被当作False所以不会进入and后面的user[age] 18判断。在很多实际场景里短路求值能避免NoneType报错这类问题先把业务逻辑写对再去考虑健壮性。2. 字典的底层逻辑为什么它查找飞快2.1 从哈希表理解键的唯一性字典是Python里应用最广的数据结构没有之一。它的底层是哈希表——你可以把哈希表理解成一个带有无数编号格子的储物柜每个格子有一个编号你存东西的时候用哈希函数把钥匙换算成编号东西就放进那个格子里找东西的时候同样用钥匙换算成编号直接去那个格子取。这就是为什么字典查找是O(1)级别的——不管字典里有10个键还是10万个键查找速度都差不多。而列表的查找是O(n)的数据量大了差距极其明显。这个底层机制直接推导出两个重要结论键必须是可哈希的也就是不可变类型。字符串、整数、元组都可以当键列表、字典本身不能当键。键不能重复因为同一个格子只能放一个东西。后赋值的键会覆盖前面的值。d {} d[name] 张三 d[name] 王五 print(d) # {name: 王五}很多初学者不理解为什么Python的字典会覆盖而不是报错理解了哈希表就明白了——同一个键经过哈希函数换算永远得到同一个格子的编号往同一个格子里写两次后面的自然覆盖前面的。2.2 字典的增删改查从最常用的写法说起字典最基本的操作是增删改查但Python提供了几种不同的查法用错场景会带来完全不同的代码体验。直接取键d[key]如果键不存在直接抛出KeyError程序崩溃。user {name: 张三} # print(user[age]) # KeyError: ageget()方法键不存在时返回默认值默认为None不报错是实际项目里最常用的安全取值方式。age user.get(age, 18)setdefault()方法这个方法的用法比较特别——如果键存在返回它的值如果键不存在先设置默认值再返回这个默认值。有则取之无则设之的语义在计数器场景里很好用word_count {} word python word_count[word] word_count.get(word, 0) 1但更优雅的是setdefault配合列表的场景courses {} course_name 数学 courses.setdefault(course_name, []).append(张三) courses.setdefault(course_name, []).append(李四) print(courses) # {数学: [张三, 李四]}这比先判断键在不在再决定是新建列表还是追加列表要简洁得多。2.3 字典推导式与迭代让代码更Pythonic列表推导式很多人知道了字典推导式其实同样好用。假设你有一个包含多个学生的列表想快速建立一个名字 - 成绩的映射students [(张三, 88), (李四, 92), (王五, 70)] scores {name: score for name, score in students} print(scores) # {张三: 88, 李四: 92, 王五: 70}如果你还想在里面加条件判断可以直接在后面接ifpassed {name: score for name, score in students if score 80} # {张三: 88, 李四: 92}迭代字典时默认只迭代键。如果你想要键值对用items()只想要值用values()只想遍历键并同时修改键值就在items()上解包。这三者之间的区别一开始就要从语义上分清楚否则后面很容易晕。3. 条件判断字典的经典组合从成绩等级系统开始3.1 用字典存数据用条件判断处理逻辑前面的知识点都是散的现在把它们串起来。一个非常典型的场景就是学生成绩等级判定——用字典存学生数据用条件判断映射成绩等级。先用原始的条件判断链实现students { 张三: 88, 李四: 92, 王五: 70, 赵六: 55, } def get_grade(score): if score 90: return A elif score 80: return B elif score 70: return C elif score 60: return D else: return E for name, score in students.items(): grade get_grade(score) print(f{name}{score}分等级{grade})这段代码功能没问题但当等级规则一多、阈值一变修改起来就很痛苦——每加一个等级你就得在函数里加一个elif分支。3.2 查表法用字典映射替代长elif链这里要分享一个很快就能用上的优化思路当你的条件判断是根据一个数值落在哪个区间返回对应的值这种结构时完全可以换一种写法——把区间边界和结果放进一个字典然后用循环遍历。grade_table [ (90, A), (80, B), (70, C), (60, D), ] def get_grade(score): for threshold, grade in grade_table: if score threshold: return grade return E这样调整等级阈值时只需要改grade_table里的数字函数代码一行都不用动。如果哪天规则变成85分以上是A改一个元组就够了不需要在代码里翻找elif。再进一步如果只是精确匹配而不是区间匹配连循环都不用直接查字典role_permissions { admin: [create, delete, edit], editor: [create, edit], viewer: [view], } def can(user_role, action): return action in user_permissions.get(user_role, [])这种查表法在处理枚举类映射、权限配置、状态机转换时极其常见。判等条件越多字典映射的优势越明显。3.3 边界情况空字典、None、缺失键组合使用条件判断和字典时最危险的往往不是正常逻辑而是边界情况。比如一个学生没成绩存在字典里的值是None你直接用score 60判断就会报TypeError一个学生不在字典里你用students[新同学]拿成绩会报KeyError。稳妥的写法是先用in或者get做存在性检查def judge_student(name): score students.get(name) if score is None: return 该学生没有成绩记录 return get_grade(score)这里先处理None再进入条件判断顺序很重要。Python的None is None判断是安全的但如果你写成if not score当分数恰好是0分时就会被误判成没有成绩。这个坑非常隐蔽但非常常见——布尔假值和值为None是两个不同的概念。4. 新手最容易栽的坑条件判断与字典的隐蔽错误4.1 缩进错位导致的逻辑偏差Python的缩进决定代码归属。哪怕条件判断本身写对了缩进错一位逻辑就完全变了。最经典的坑是这样的income 5000 if income 8000: gate 高收入 elif income 3000: gate 中等收入 print(f当前收入层级判断完成) # 注意这一句的缩进这段代码的print与if平级所以无论如何都会执行。但如果你不小心把print缩进到elif里面那就只有进入中等收入分支时才会打印——这种错误编译器不会报错结果和预期完全不符。我的建议是在编辑器里打开显示空格/制表符功能全程只用空格缩进并且统一用4个空格。不要混用Tab和空格这是无数新手的血泪教训——同一个文件中Tab和空格混用Python解释器会直接报IndentationError而且报错位置往往和真实错误位置偏差几行很误导人。4.2 键存在性检查in、get()、try/except三选一判断字典里有没有某个键有三种常见写法各自适用场景不同写法适用场景注意点if key in d:只需要判断键存不存在语义最清晰但会查两次表先in再取值value d.get(key, default)不存在时需要一个兜底值一次查询搞定最常用try: value d[key] except KeyError:绝大多数情况键要存在异常是意外符合异常用于异常情况的原则性能也不错很多教程喜欢推崇try/except但在日常业务逻辑里get通常就够用了。只有在键如果不存在说明数据有问题需要特殊处理或记录日志的情况下才建议用try/except。不要盲目追求防一切错误代码的可读性和意图清晰度更重要。另外提醒一个细节dict.get(key)在键不存在时返回None但如果某个键的值本身就是None你就没法区分键不存在和键存在但值是None。如果你确实需要区分这两种状态用in判断更保险。4.3 循环遍历时修改字典一个必踩的运行时错误假如你想从字典里删掉所有值为待删除的项新手通常会这样写d {a: 1, b: 待删除, c: 3} for key in d: if d[key] 待删除: del d[key]运行到一半Python直接抛RuntimeError: dictionary changed size during iteration。原因很简单——字典在迭代过程中大小发生了变化迭代器的内部状态乱了。正确的做法是先把要删除的键收集到一个列表里遍历完之后再统一删除to_delete [] for key, value in d.items(): if value 待删除: to_delete.append(key) for key in to_delete: del d[key]或者用更加Pythonic的字典推导式重建一个新字典d {key: value for key, value in d.items() if value ! 待删除}第二种方式不修改原字典而是创建新字典在数据量不大的时候完全够用代码也干净。数据量特别大时再用第一种先收集再删除的方式避免复制整个字典的内存开销。4.4 可变对象做默认值和字典关联的经典陷阱这里虽然讲的是函数默认参数但和字典的联系非常紧密。很多人在写函数时为了省事这样写def add_student(name, course_scores{}): course_scores[name] 90 return course_scores第一次调用add_student(张三)返回{张三: 90}第二次调用add_student(李四)你以为会返回{李四: 90}结果返回的是{张三: 90, 李四: 90}——因为默认参数{}在函数定义时创建了一次之后所有调用共用同一个字典对象。正确的写法是默认参数设为None在函数内部再初始化def add_student(name, course_scoresNone): if course_scores is None: course_scores {} course_scores[name] 90 return course_scores这个坑看似和条件判断无关但它是Python里和可变对象有关的最高频坑之一。第四天学字典时正好会碰到这种场景提前记下来能省掉后面不少排查时间。5. 综合小实战写一个简易通讯录5.1 需求拆解把模糊想法变成明确功能学编程最有成就感的一刻就是用学到的东西做一个能用起来的小工具。我现在就带你一步一步做个最简单的通讯录它要做的事情用字典保存联系人键是姓名值是电话号码支持添加联系人支持按姓名查找电话显示所有联系人用条件判断处理联系人不存在的场景先想清楚数据模型contacts {张三: 13800138000, 李四: 13900139000}非常简单明了。5.2 完整代码与逐段解读contacts {} def add_contact(name, phone): if name in contacts: print(f联系人 {name} 已存在如需修改请联系人移除后再添加) return False contacts[name] phone print(f已添加联系人{name}) return True def find_contact(name): phone contacts.get(name) if phone is None: print(f未找到联系人{name}) return None print(f{name} 的电话是{phone}) return phone def list_contacts(): if not contacts: print(通讯录还是空的) return for name, phone in contacts.items(): print(f{name}: {phone}) add_contact(张三, 13800138000) add_contact(李四, 13900139000) add_contact(张三, 13700137000) # 重复添加会被拦截 find_contact(张三) find_contact(王五) # 不存在的联系人不会崩溃 list_contacts()这段代码把第四天学到的几乎所有知识点都用上了字典存数据contacts[name] phone条件判断控制流程if name in contacts检查重复get()方法安全取值phone contacts.get(name)配合if phone is None判断if not contacts判断空字典利用空字典隐式转换为False的特性items()遍历键值对最值得体会的是find_contact函数的写法——它用了三层防护get()代替直接索引避免KeyError、is None判断去除误判、返回None让调用方可以继续做后续处理而不是让程序崩溃。这种尽量不报错给出明确反馈的容错设计是工程代码的重要思维习惯和第2天学的让程序走通已经不是同一个层次了。5.3 再进一步用while循环让通讯录真正可以交互如果想让程序像一个真正的工具可以加上一个循环菜单while True: print(\n1. 添加联系人) print(2. 查找联系人) print(3. 查看所有联系人) print(4. 退出) choice input(请输入操作编号) if choice 1: name input(请输入姓名) phone input(请输入电话) add_contact(name, phone) elif choice 2: name input(请输入要查找的姓名) find_contact(name) elif choice 3: list_contacts() elif choice 4: print(已退出通讯录) break else: print(无效的操作编号请重新输入)注意这里的input()返回的是字符串所以判断条件必须写成choice 1而不是choice 1。这是一个很低级但非常高频的bug——从input()键盘输入的数字永远是字符串。如果没有这一层意识你会发现明明输入了1程序却永远进不去第一个分支。这个小小的循环加上之前的函数和字典一个能跑起来、能处理异常输入、能反复使用的完整小程序就诞生了。第四天能把这种程度的代码自己写出来说明前面三天的知识真正开始融会贯通了。5.4 这个代码还能怎么扩展如果你学有余力可以自己动手加这几个功能检验一下今天的内容是否真的吃透了支持修改联系人电话先判断联系人存在再用新值覆盖旧值支持删除联系人del contacts[name]但要先判断存在性避免KeyError手机号格式简单校验用len(phone) 11这类条件判断做基础验证按姓名的首字母分类如果联系人多可以做一个首字母 - [联系人列表]的嵌套字典结构第5个扩展其实很有意思——{Z: [张三], L: [李四]}这样的嵌套结构同时用到了字典的值是列表、条件判断处理首字母、以及setdefault方法。做出来之后你对复合数据结构的理解会上一个台阶。学Python第四天我的体会是条件判断和字典是代码从玩具走向工具的分水岭。前者让程序有了逻辑后者让程序有了组织数据的能力。把这两者练熟接下来学函数、文件操作、面向对象都会顺滑很多。尤其是字典等你以后接触真实项目从配置管理、接口返回的JSON解析到爬虫数据存储几乎每一处都能见到它的身影。今天把字典的常用操作和条件判断的组合方式练扎实后面就是复利效应。