首页 / 资讯中心 / 文章详情

Python大括号{}完全指南:字典、集合、f-string与推导式

Python大括号{}完全指南:字典、集合、f-string与推导式 ★ FEATURED ARTICLE
开头先认个门Python 里的大括号其实是个“三面人”聊到 Python 里的大括号{}很多人第一反应是“这不就是字典吗”这话对了一半。真正把 Python 用熟了你会发现大括号这货在同一门语言里至少干了三份工它是字典的分隔符是集合的字面量语法还是字符串格式化里最有存在感的占位符。再加上推导式这种写法一行{}能同时把“数据结构、生成逻辑、输出格式”三件事全包了。这篇文章我打算用自己平时写代码的真实场景来拆一遍什么时候该用它、什么时候别用它、什么时候表面上用了它但其实踩了坑。适合刚开始学 Python 的初学者快速建立认知也适合写了一段时间但总在{}和set()之间犯迷糊的同学查漏补缺。我会把每个用法都配上能直接跑的代码示例再解释一下“为什么要这么写”尽量让你看完就能迁移到自己的项目里去。1. 字典才是大括号的“主战场”基础语法与容易忽略的细节1.1 花括号构造字典本质是键值对的集合先明确一件事{}在 Python 里最基础的身份就是字典的字面量literal语法。所谓字面量就是你在代码里直接写出来就能生成一个对象而不需要调用任何构造函数。你写{name: 张三, age: 30}解释器就给你创建了一个里面存着两个键值对的字典对象。这里的关键点在于“键值对”这个概念。字典里的数据不是线性排列的而是通过“键”去映射“值”。你可以把字典理解成一本通讯录你要查一个人的电话不需要从头翻到尾只要知道他的名字键直接就能定位到电话号码值。这个映射关系就是哈希表hash table在底层帮我们实现的。创建字典有几种常见写法# 写法一空字典最常用 d1 {} # 写法二直接写键值对 d2 {name: 张三, age: 30} # 写法三用 dict() 构造函数 d3 dict(name李四, age25) # 写法四从列表的二元组序列转成字典 pairs [(city, 北京), (level, 3)] d4 dict(pairs)写代码时大多数人习惯用写法二可读性最好。写法三有个限制键必须是合法的标识符不能写dict(a1)这种带特殊符号的键。写法四适合你在处理数据库查询结果或接口返回的键值对列表时做转换比如dict(cursor.fetchall())。1.2 字典操作的实操增删改查与嵌套结构字典最常见的操作就是增删改查。改和增其实是同一个动作给不存在的键赋值就是新增给已存在的键赋值就是覆盖。user {name: 王五, age: 28} # 查直接索引键不存在会抛 KeyError print(user[name]) # 安全查用 get 方法键不存在时返回默认值 print(user.get(email, no email)) # 增/改 user[city] 上海 user[age] 29 # 删 del user[age] # 弹出并删除 removed user.pop(city, None) # 判断键是否存在 if name in user: print(键存在)这里我想特别强调get和in的配合。很多初学者一开始只知道user[name]这种写法后来在真实项目里经常遇到“键不存在就程序崩溃”的问题。尤其是解析外部接口返回的 JSON 数据时字段缺失是家常便饭用get方法加默认值或者先in判断能省下大量调试时间。嵌套字典是实际业务里更常见的形式。比如一个用户信息可能长这样users { u001: {name: 赵六, tags: [vip, 老用户]}, u002: {name: 钱七, tags: [new]}, } # 访问嵌套字段 print(users[u001][tags][0]) # vip这种结构在配置文件、接口返回数据、数据库记录里非常普遍。你要记住的规律是字典套字典、字典套列表、列表套字典都是你读写数据时的组合拳。每多一层嵌套访问时就需要多一个索引操作。1.3 字典的无序性一说3.7 之后的顺序保证过去很长一段时间Python 官方文档都说“字典是无序的”这坑了不少人。实际情况是在 Python 3.6 的 CPython 实现里字典的底层结构改了键值对的插入顺序被保留了下来到了 Python 3.7这个行为被正式写进语言规范。所以你现在用 Python 3.8 写代码字典遍历时输出的顺序一定和插入顺序一致。但我要提醒你别依赖这个顺序来设计逻辑。官方之所以当初强调无序是因为这不是语言层面的原始承诺。虽然现在实际表现有序但如果未来有优化改动你的代码可能会悄悄变样。排序这种需求老老实实写sorted(d.items(), keylambda x: x[1])才是正路。2. 集合与大括号一个空花括号引发的“血案”2.1 集合的两种建法{}和set()到底差在哪如果你写a {}得到的不是集合而是空字典。这是 Python 里最容易让新手踩坑的地方之一。要创建一个空集合必须用set()构造函数这是语言设计者为了避免歧义做的决定既然{}已经被字典占了空集合就不能再用它了。但是只要集合里有元素用大括号写就完全没问题# 这是集合 s1 {1, 2, 3} # 这是字典 d1 {1: one, 2: two} # 这是空字典不是空集合 a {} # 这是空集合 b set()判断一个对象到底是集合还是字典最简单的办法是看元素间有没有冒号。{1, 2, 3}是集合{1: one}是字典。这个判断规则非常直观日常写代码时扫一眼就能确认。2.2 集合的核心价值去重和集合运算集合之所以值得单独讲是因为它的两个核心特性在业务处理里太有用了元素唯一性以及对数学意义上的集合运算的原生支持。去重是使用频率最高的场景。你从数据库里查出来一批用户 ID里面可能有重复的你只要包一层set()重复项自动消失user_ids [101, 102, 103, 101, 104, 102] unique_ids set(user_ids) print(unique_ids) # {101, 102, 103, 104}注意集合里的元素必须是可哈希的hashable也就是说不能放列表、字典这种可变容器。你要是写{[1, 2]}或者{{a: 1}}解释器直接报TypeError: unhashable type。集合运算这块我在处理权限、标签、推荐逻辑时经常用到。Python 直接用运算符就行a {1, 2, 3, 4} b {3, 4, 5, 6} print(a b) # 交集 {3, 4} print(a | b) # 并集 {1, 2, 3, 4, 5, 6} print(a - b) # 差集 {1, 2} print(a ^ b) # 对称差 {1, 2, 5, 6}这比用循环加判断去实现交集要优雅太多。比如你有两组用户分别关注了不同的商品想找出共同关注的用户一个就搞定了。每次想到有人还在用 for 循环做这种操作我都替他们手疼。2.3 集合的“不可变兄弟”frozenset既然提到了可哈希这里顺便说一下frozenset。它是不可变版本的集合可以被放进另一个集合里也可以作为字典的键fs frozenset([1, 2, 3]) d {fs: frozen data}这个用法偏冷门但你在设计缓存系统、需要把“一组标签”作为整体去映射到某个结果时它会非常有用。毕竟普通集合不能作为哈希值你没法把它直接当字典的键换成frozenset就能解决。3. f-string 里的大括号格式化字符串的真正主角3.1 为什么 f-string 成了主流的格式化方式Python 格式化字符串有三种主流方式%运算符、str.format()方法、f-string格式化字符串字面量。f-string 是 Python 3.6 引入的它让“把变量塞进字符串”这件事变得非常直白——你直接在字符串里写变量名前面加个f前缀就行。name 小明 score 96.5 print(f{name}的得分是{score})f-string 之所以好用是因为它打破了之前格式化的割裂感。%方式要把变量写在字符串后面format()方式要把变量写在方法参数里你在阅读代码时大脑需要在“模板”和“实际值”之间来回切换。f-string 则把变量直接内联到字符串里一眼就能看懂输出长什么样。3.2 大括号内可以放表达式不只是变量很多人以为 f-string 的花括号里只能填变量名其实不是。它可以写任意 Python 表达式包括函数调用、运算、属性访问以及三元表达式等items [3, 7, 1] print(f列表长度: {len(items)}最大值: {max(items)}) price 89 discount 0.8 print(f折后价: {price * discount:.2f}) age 18 print(f{成年 if age 18 else 未成年})这里要把握一个使用尺度表达式可以写但别写得太复杂。你见过那种 f-string 里塞十几个方法调用的代码吗可读性基本为零。我自己的习惯是花括号里只放简单表达式复杂的先算成变量再放进字符串里。代码是给人看的不是写给解释器看的。3.3 格式说明符冒号后面才是精华f-string 的花括号里冒号后面的部分是格式说明符这才是格式化输出真正拉开差距的地方。用法大致是{变量:格式说明}salary 1234567 print(f月薪: {salary:,}) # 千分位分隔符 - 1,234,567 ratio 0.8732 print(f占比: {ratio:.2%}) # 百分数保留两位小数 - 87.32% num 42 print(f补零: {num:06d}) # 6位宽度前面补0 - 000042 pi 3.1415926 print(f圆周率: {pi:.4f}) # 保留4位小数 - 3.1416 # 对齐方式 name python print(f[{name:^10}]) # 居中对齐 print(f[{name:10}]) # 左对齐 print(f[{name:10}]) # 右对齐这些格式说明符在输出报表、打印日志、对齐终端信息时特别有用。我说一个真实场景我用 Python 生成自动化测试报告需要把多条用例的结果打印成对齐的表格用{result:20}这种左对齐格式把所有列排整齐比手工补空格省事太多了。需要单独用一个对齐宽度变量时还可以写{name:{width}}这种嵌套花括号的写法动态指定宽度值。3.4 花括号在 f-string 里怎么转义两个大坑f-string 中想输出字面意义上的花括号{或}必须连写两个。比如你想输出 JSON 片段或者模板代码print(f{{key: value}}) # {key: value} print(f大括号: {{ }})但你要是写f{{{变量}}}解释器会把它理解成“输出一个左花括号 变量值 一个右花括号”。这算是 f-string 里最绕的语法了。我的建议是需要大量输出花括号的场景比如生成 JSON、生成代码模板不要用 f-string 硬拼改用普通字符串拼接或者format()方法心智负担会小很多。还有个很容易忽略的坑f-string 里的引号冲突。如果花括号里的表达式本身要使用单引号字符串而整个 f-string 也用单引号包起来就会冲突# 错误写法 # f结果是 {func(x)} # 正确写法 f结果是 {func(x)} # 外层单引号内用双引号 f结果是 {func(x)} # 外层双引号内用单引号这种引号配对规则写多了自然就熟了但刚开始确实容易在复杂的 f-string 里撞上语法错误。记住一个原则外层选一种引号内层表达式尽量用另一种视觉上不容易乱。4. format() 方法里的花括号老江湖的落点4.1 位置参数与关键字参数的花括号占位在被 f-string 抢了风头之前str.format()是大家最常用的格式化方式。它也用花括号做占位符支持位置参数和关键字参数# 位置参数 print({} 今年 {} 岁.format(小红, 18)) print({0} 今年 {1} 岁明年就 {2} 岁了.format(小红, 18, 19)) # 关键字参数 print({name} 住在 {city}.format(name小红, city杭州)) # 混合使用 print({0} 喜欢 {lang}.format(小红, langPython))位置参数的花括号里写数字表示format()方法的第几个参数。不写数字的话按顺序依次匹配。关键是同一个编号可以重复使用比如{0} 和 {0} 又不一样这在某些场景能少传几次参数。4.2 format() 里的格式规范与 f-string 互通format()花括号里冒号后面的格式说明符语法和 f-string 是完全一样的。因为两种方式底层用的都是同一个格式规范Format Specification Mini-Language。所以你前面学的{:.2f}、{:,.0f}、{:10}这些在format()里照样能用print({:.2%}.format(0.8723)) # 87.23% print({:8} | {:8}.format(left, right))如果要动态拼接出格式说明的一部分format()比 f-string 更灵活因为它支持往格式串里传参width 12 print({:{width}}.format(内容, widthwidth))这种“格式模板”和“实际数据”完全分离的特性是format()至今没有被 f-string 完全取代的原因。当你要把同一个模板拿出来反复套用的时候format()的优势非常大。比如打日志的格式字符串从配置里读出来然后log_format.format(...)一执行数据就进去了。4.3 format() 里的大括号转义比 f-string 更有规律format()里要输出字面花括号同样是双写template {{name}}: {} print(template.format(python)) # {name}: python由于format()的方法模式比较纯粹双写花括号的规则在它这里显得更有规律——不存在表达式求值的干扰纯粹就是模板渲染。如果你要生成大量含花括号的模板文本比如 shell 脚本片段、SQL 模板我反而更推荐format()而不是 f-string因为它把“数据插入”和“模板本身”分开了不容易在编辑器里看花眼。5. 推导式里的大括号一行代码生成字典和集合5.1 字典推导式从循环到一行式的进化列表推导式大家都会写但很多人没用过字典推导式和集合推导式。它们同样用大括号包裹但写法细节不一样。字典推导式的基本形式是{键表达式: 值表达式 for 元素 in 可迭代对象}# 把列表元素变成平方映射 nums [1, 2, 3, 4] square_map {n: n ** 2 for n in nums} print(square_map) # {1: 1, 2: 4, 3: 9, 4: 16} # 从两个列表构造键值对 keys [name, age, city] values [张三, 28, 深圳] person {k: v for k, v in zip(keys, values)} print(person) # 带过滤条件的推导式 scores {语文: 88, 数学: 92, 英语: 75} passed {subject: score for subject, score in scores.items() if score 80} print(passed) # {语文: 88, 数学: 92}推导式的核心价值是“用声明的方式代替命令式循环”。你从一个可迭代对象里通过映射冒号左边的表达式和过滤if条件直接生成一个新字典。这在处理dict.items()的键值转换时极好用。比如你要把一个字典的键全部转小写或者把所有值都加一raw {Name: Tom, Age: 20} normalized {k.lower(): v for k, v in raw.items()}5.2 集合推导式去重和变换一步到位集合推导式和列表推导式几乎一模一样只是把方括号换成花括号nums [1, 2, 2, 3, 4, 4, 5] unique_even {n for n in nums if n % 2 0} print(unique_even) # {2, 4} # 对字符串做字符去重 word hello chars {c for c in word} print(chars) # {h, e, l, o}集合推导式很适合“筛选 去重”同时完成的场景。我之前处理一批文件名称时就是从一个大列表里提取所有后缀名并去重一行代码就把需要统计的类型集合拿出来了。5.3 推导式的使用边界不要为了炫技而滥用我的经验是推导式能用但别嵌套太深。一个字典推导式里如果又嵌套一个列表推导式再加上两个if条件读起来基本就是灾难了。譬如{k: [x for x in v if x 0] for k, v in data.items() if v}这种东西写完三天后你自己都看不懂。实用的判断标准推导式的长度如果超过一行 80 字符左右或者括号里面出现了第二个推导式关键字for就拆成普通循环。性能上没有多大差别但代码的可读性会天差地别。6. 代码块的大括号为什么 Python 偏偏不用它6.1 其他语言的花括号职责在 Python 里交给了缩进如果你是从 Java、C、JavaScript 转过来学 Python 的一定会有一个强烈的困惑代码块的花括号去哪了在那些语言里函数体、循环体、条件分支全部用{}包起来。而 Python 选择用冒号加缩进来表示代码块def greeting(name): if name: print(f你好{name}) # 缩进表示这段属于 if 内部 else: print(你好匿名者) # 缩进表示这段属于 else 内部这个设计在刚接触时容易不习惯但它其实是 Python 的一大优点。花括号嵌套越多漏写一个}或;就会导致整段代码解析错误而缩进是强制的写出来的代码天然就是对齐的团队协作时很难把代码写得乱七八糟。Python 开发者之间提代码风格第一句基本就是“注意缩进”。6.2 lambda 表达式和函数体花括号缺席的地方要小心lambda 表达式在 Python 里只能写单行表达式不能像其他语言那样用花括号多行写函数体。这个限制经常被从其他语言转来的人吐槽但它的设计是有意为之——Python 的哲学是“简单明确”lambda 就是给最简单的函数用的复杂逻辑请回头写defadd lambda x, y: x y print(add(3, 5)) # 8我见过有人在 lambda 里拼命塞表达式一个函数里用了五个 and/or结果又一次调它的同事根本没法维护。我的习惯是需要两句以上逻辑就老老实实写def。6.3 花括号与字典语法在 return 时的经典问题这里有个 Python 新手几乎都会踩的坑函数想返回一个字典结果缩进或语法写错导致和代码块混淆。其实 Python 的处理很简单——return 后面跟着{...}就会被识别为字典但这要求你的缩进正确def make_user(): return { name: 小红, age: 18, } print(make_user())注意到这里字典的花括号没有和return在同一行结尾而是换行缩进。这种写法在格式化长字典时非常流行可读性远胜于把全部键值对挤在一行。7. 常见问题与排查技巧实录这些坑我基本都踩过7.1 问题一空花括号到底是字典还是集合问得最多的问题“我写a {}为什么type(a)是dict”答案就是前面说的空花括号只会被解释成空字典。要创建空集合必须用set()。我在写一些去重逻辑时曾经因为初始化集合用了{}然后往里add元素结果报错“AttributeError: dict object has no attribute add”。排查了半天才发现自己初始化错了。这个坑很隐蔽因为代码在创建瞬间不报错一直要等到调用add才炸。所以最好在代码里通过注释或命名来区分集合变量建议叫xxx_set字典变量叫xxx_dict从命名上避免误判。7.2 问题二f-string 里想输出花括号结果输出了一堆变量你想输出{status: ok}这种 JSON 文本如果直接写f{status: ok}解释器会把里面的内容当成表达式求值然后大概率报语法错误或 KeyError。正确的是双写花括号text f{{status: ok, code: {code}}}这类问题在拼接 SQL、生成配置文件、构造测试数据时经常会遇到。我的建议是花括号密度超过 3 对的字符串就别用 f-string 了用format()模板或者直接字符串拼接省得数花括号数到崩溃。7.3 问题三format() 参数个数和花括号数量不匹配format()方法最常见报错是IndexError: Replacement index 0 out of range或者KeyError: xxx。前者是位置参数不够后者是关键字参数缺失。排查思路很简单数一数花括号里的编号是不是连续从 0 开始关键字名字是否和实际传参一一对应。还有一个隐蔽情况你在format()字符串里写了花括号但没有内容比如{}却传了两个参数多出来的参数不会报错直接忽略。反过来参数少一个就立刻炸。开发时写单元测试多跑跑字符串格式化的边界情况能省不少线上排查的时间。7.4 问题四字典推导式里花括号和冒号位置写颠倒字典推导式和集合推导式外观相近但字典推导式必须有冒号把键和值分开。我见过不少同学把{k: v for ...}写成{for ... : k, v}这种顺序完全乱的代码。正确结构永远是左侧写键: 值的表达式中间写for循环最后跟可选的if过滤。多写几次肌肉记忆就会形成。7.5 一个小工具用大括号快速判断数据结构的类型我自己在调试时常用一个土办法直接在交互式环境里把对象类型打出来。print(type({})) # class dict print(type({1, 2})) # class set print(type({1: 2})) # class dict如果你接到一段别人传过来的数据不确定最外层是字典还是集合直接type()一下永远是最稳妥的。8. 大括号在 JSON 和配置文件里的特殊一席之地8.1 dict 与 JSON 的互相转换花括号的“跨界演出”JSON 的语法和 Python 字典几乎一脉相承所以大括号在 Python 里还会频繁出现在 JSON 处理场景。用json模块你可以在字典和大括号风格文本之间自由切换import json data {name: 小红, age: 18, tags: [student, python]} json_text json.dumps(data, ensure_asciiFalse, indent2) print(json_text) restored json.loads(json_text) print(restored[name])这里要注意ensure_asciiFalse这个参数。默认情况下json.dumps会把所有非 ASCII 字符转成\uXXXX的转义形式导致中文全部变成乱码。处理中文字段时务必加上这个参数输出的 JSON 才是人类可读的。json.loads的逆向操作也要注意如果输入文本不是合法 JSON比如字符串里有单引号、结尾多逗号、键没有用双引号包裹直接抛JSONDecodeError。尤其是“键没有双引号”这个问题Python 字典字面量允许键不加引号写成标识符但 JSON 规范不允许。这种差异在调试接口返回数据时特别容易踩。8.2 用大括号做模板占位符从字典渲染到字符串既然字典和大括号的关联这么紧密很多人会把配置模板直接设计成{key}的形式再用format()或者自定义替换逻辑来渲染。比如一个邮件模板template 尊敬的 {name} 恭喜您在 {date} 的抽奖活动中中奖 奖品是{prize} content template.format(name小红, date2024-06-01, prizePython 入门书)这种模板渲染的经典用法本质上还是花括号占位符。要注意的是如果模板里还有其他字面花括号比如 CSS、代码片段必须先双写转义否则format()会尝试当成占位符解析引发报错。8.3 多级嵌套字典的读写别再用一长串 [][] 了处理业务数据时三层以上的嵌套字典访问容易写出很长的索引链config { server: { host: 127.0.0.1, ports: {http: 80, https: 443}, }, logging: {level: INFO}, } print(config[server][ports][http]) # 80长索引链的问题在于任何一层键缺失整行代码直接KeyError。我建议对深层结构封装一个安全取值的小工具函数或者用.get()逐层取这样代码健壮性会高很多。当然你也可以用collections.defaultdict或者自己写一个递归的 get。核心原则是不管嵌套多深访问逻辑必须让人一眼能看明白在取哪个字段。8.4 用**解包把多个字典合并花括号语法的延伸技巧字典合并是日常开发里的高频需求。Python 3.5 提供了**解包语法两个花括号字典可以优雅地合并成一个新字典default_config {timeout: 10, retries: 3} user_config {timeout: 30} merged {**default_config, **user_config} print(merged) # {timeout: 30, retries: 3}timeout 被覆盖这种写法的语义很直白后面的字典覆盖前面的同名键。相比update()方法它不需要先复制再修改而是直接生成新对象更适合函数式风格。如果你在乎字典顺序3.9 之后的|运算符也能做同样的事情不过我还是更常用**解包因为它在旧版本里也可用。9. 顺着花括号这条线串一串 Python 的核心数据思维聊到这里你会发现大括号在 Python 里从来不是一个孤立的语法记号它背后串着一整套数据结构的思维脉络。看到{}你要立刻在脑子里过一遍可能是字典、可能是集合、可能是 f-string 占位符、可能是 format 模板、可能是推导式外层、也可能是 JSON 文本的一部分。每一种身份背后都对应着一类场景、一组方法、一堆容易踩的坑。我在实际项目里体会最深的一点是写代码前先想清楚“我要处理的数据结构是什么”远比记语法本身重要。你要做映射、按名字取值就选字典要做去重、做集合运算就选集合要往字符串里嵌入动态内容就考虑 f-string 或 format。大括号只是个入口真正影响代码质量的是你对数据结构特性的理解和选择。最后再分享一个小技巧当你看到一行代码里花括号特别多阅读压力很大的时候不要硬着头皮读——直接复制到脚本里跑一下打印出中间过程花不了半分钟。调试器配上前几节提到的type()判断法几乎所有大括号相关的坑都能快速定位。踩过几次坑之后我对大括号的态度从“容易写错”变成了“其实很有意思”因为它让我更清楚地看到 Python 这门语言在“容器”和“文本处理”上的设计哲学是高度统一的。
阅读完成 · 觉得有帮助?
咨询建站