解读《Effective Python 3rd Edition》:从练气到老魔(第一章 Item 7 - 9)
Cloud_Shy 陪你解读《Effective Python 3rd Edition》从练气到老魔大家好呀欢迎来到博主新开的《Effective Python 3rd Edition》学习笔记系列毕竟也读过几百篇 SCI 这次来试试阅读原版学习是一种怎样的体验。小伙伴们感兴趣的话请一定要点赞收藏加关注呀第一章 Pythonic ThinkingPython 程序员倾向于保持清晰明了选择简单胜于复杂并力求提高可读性。Item 7考虑简单内联逻辑的条件表达式Python中的 if 语句并非表达式。if 块、elif 块和 else 块均可包含若干附加语句。整个块组并不会被评估为一个单一值该值可以被存储在变量中或作为函数参数传递。Python 还支持条件表达式这种表达式使您能够在几乎任何允许表达式的位置处插入 if/elif/else 行为。例如这里使用一个条件表达式根据布尔测试结果来赋值给一个变量i 3 x even if i % 2 0 else odd print(x) odd这种表达式结构似乎颇为便捷尤其适用于特定用途的场景并让人联想起你可能从 C 及其他语言中熟悉的三元运算符例如condition true_value : false_value。对于此类简单的赋值操作甚至是在函数调用参数列表中例如my_func1 if x else 2使用条件表达式不失为一种平衡代码简洁性与灵活性的好选择。值得注意的是关于 Python 中的条件表达式与其他语言中的三元运算符之间的差异有一个关键细节需要注意在 C 语言中测试表达式位于首位而在 Python 中当测试表达式为真时需要进行评估的表达式则位于首位。例如你可能以为下面的代码会调用 fail 函数并引发异常但实际上fail 函数从未被执行因为测试条件为假def fail(): raise Exception(Oops) x fail() if False else 20 print(x) 20Python 中的 if 语句在过滤操作中具有类似的语法和行为参见 Item 40“使用 comprehension 而非 map 和 filter” 以及 Item 44“考虑使用生成式表达式处理大型列表 comprehension”。例如在此示例中我通过在列表 comprehension 中使用 if 语句仅在计算结果列表时包含 x 的偶数值result [x / 4 for x in range(10) if x % 2 0] print(result) [0.0, 0.5, 1.0, 1.5, 2.0]需要评估的表达式x / 4位于 if test 表达式x % 2 0之前这与条件表达式中的情况类似。在 Python 中条件表达式可用之前人们有时会利用布尔逻辑来实现类似的功能详情请见 Item 4“编写辅助函数而非复杂表达式”。例如以下表达式与上述条件表达式是等价的x (i % 2 0 and even) or odd这种逻辑形式颇为令人困惑因为你需要了解这一点它要么返回第一个假值要么返回最后一个真值而或则返回第一个真值要么返回最后一个假值详情请参阅 Item 23“将迭代器传递给任何和所有元素以高效实现短路逻辑”。此外若你希望将一个假值作为真值条件的结果返回例如x i % 2 0 and [] 或 [1] 始终等于 [1]那么使用布尔运算符的方法并不适用。这一切都不甚明了且容易出错这也是语言中最初引入条件表达式的原因之一。现在参照与先前单行示例相同的逻辑将其转换为四行 if 语句形式:if i % 2 0: x even else: x odd尽管这段代码较长但这样做可能更为有利原因有以下几点。首先如果日后需要在每个条件分支内部执行更多操作比如打印调试信息则无需对代码结构进行任何改动即可实现这一目标if i % 2 0: x even print(It was even!) # Added else: x odd还可以在同一语句中用 elif 块插入额外的分支:i % 2 0: x even elif i % 3 0: # Added x divisible by three else: x odd如果真的需要做到简洁明了并将这一逻辑整合到单个表达式中则可以通过将所有内容移至一个辅助函数中来实现这一目标然后就可以在代码中直接调用这个辅助函数def number_group(i): if i % 2 0: return even else: return odd x number_group(i) # Short call此外这种辅助函数还能在多个地方被重复使用而不会像条件表达式那样是一次性的。至于究竟应采用条件表达式、完整的 if 语句还是将 if 语句封装在辅助函数中这取决于具体情境。当条件表达式必须被拆分到多行时应尽量避免使用此类表达式。例如此处所编写的函数调用非常长以至于条件表达式必须被用括号包裹起来以便跨行显示x (my_long_function_call(1, 2, 3) if i % 2 0 else my_other_long_function_call(4, 5, 6))这段代码读起来相当困难。如果你为这段代码应用自动格式化工具参见 Item 2“遵循 PEP 8 风格指南” 其中的条件表达式很可能会被重写使用的代码行数会比标准的 if/else 语句更多x ( my_long_function_call(1, 2, 3) if i % 2 0 else my_other_long_function_call(4, 5, 6) )与条件表达式相比的另一项 Python 语言特性是赋值表达式参见 Item 8“使用赋值表达式防止重复”。这些表达式同样允许在表达式中表现出类似语句的行为。但关键区别在于当赋值表达式被用于歧义性语境中时必须被括号包围而条件表达式则无需被括号包围但缺少括号可能会影响可读性。例如这段包含赋值表达式的 if 语句如下x 2 y 1 if x and (z : x y): ...但这种未用圆括号包裹的 if 语句属于语法错误if x and z : x y: ... Traceback ... SyntaxError: cannot use assignment expressions with expression对于条件表达式不需要括号。 这使得破译程序员的初衷是什么变得困难因为这两种形式都是允许的if x y if z else w: # Ambiguous if x (y if z else w): # Clear当在函数调用参数列表中使用时赋值表达式也需要括号z dict( your_value (y : 1), )省略括号是一个语法错误w dict( other_value y : 1, ) Traceback ... SyntaxError: invalid syntax相比之下条件表达式在这种情况下不需要括号并且缺少括号会使代码更加混乱且难以阅读v dict( my_value 1 if x else 3, )底线运用你的判断力。 在许多情况下条件表达式很有价值并且可以提高清晰度。 有时带括号会更好有时则不然。 条件表达式很容易被过度使用来编写新读者难以理解的模糊代码。 如有疑问请选择普通的 if 语句。注意Python 中的条件表达式允许您将 if 语句放置在一般情况下的表达式所在的几乎任何位置。条件表达式中的测试表达式、真结果表达式和假结果表达式的顺序与其他语言中三元运算符的顺序不同。不要在会增加歧义或损害代码可读性的地方使用条件表达式。当不清楚条件表达式是否提供令人信服的优点时首选标准 if 语句和辅助函数。Item 8使用赋值表达式防止重复赋值表达式也称为海象运算符是 Python 3.8 中引入的一项新语法功能旨在解决该语言长期存在的可能导致代码重复的问题。正常的赋值语句写成 a b发音为 “a equals b”而这些赋值语句写为 a : b发音为 “a walrus b”因为 : 看起来像一对眼球和獠牙。赋值表达式非常有用因为它们使您能够在不允许使用赋值语句的地方例如 if 语句的测试表达式中对变量进行赋值。 赋值表达式的值计算为分配给海象运算符左侧标识符的任何值。例如假设我有一篮子新鲜水果我正在尝试为果汁吧管理。 这里我定义了篮子的内容fresh_fruit { apple: 10, banana: 8, lemon: 5, }当顾客来到柜台点柠檬水时我需要确保篮子里至少有一个柠檬可以挤。 在这里我通过检索柠檬的数量然后使用 if 语句检查非零值来实现此目的def make_lemonade(count): ... def out_of_stock(): ... count fresh_fruit.get(lemon, 0) if count: make_lemonade(count) else: out_of_stock() Making 5 lemons into lemonade这个看似简单的代码的问题在于它的噪音比它需要的要大。 count 变量仅在 if 语句的第一个块内使用。 在 if 语句上方定义 count 会导致它看起来比实际情况更重要就好像后面的所有代码包括 else 块都需要访问 count 变量但事实并非如此。这种先获取值、检查它是否真实、然后使用它的模式在 Python 中非常常见。 许多程序员尝试使用各种会损害可读性的技巧来解决多重引用计数问题请参阅 Item 4“编写辅助函数而不是复杂表达式”和 Item 7“考虑简单内联逻辑的条件表达式”。 幸运的是赋值表达式被添加到语言中以简化此类代码。 这里使用 walrus 运算符重写了上面的示例if count : fresh_fruit.get(lemon, 0): make_lemonade(count) else: out_of_stock()虽然这只是缩短了一行但它的可读性要高得多因为现在很清楚 count 只与 if 语句的第一个块相关。 赋值表达式首先为 count 变量赋值然后在 if 语句的上下文中计算该值以确定如何继续进行流程控制。 这种两步行为赋值然后评估是海象运算符的基本性质。柠檬的功效非常强大所以我的柠檬水配方只需要一个这意味着非零、真实的检查就足够了。 不过如果顾客点了苹果酒我需要确保我至少有四个苹果。 在这里我通过从 fresh_fruit 字典中获取计数值然后在 if statement 测试表达式中使用比较来实现此目的def make_cider(count): ... count fresh_fruit.get(apple, 0) if count 4: make_cider(count) else: out_of_stock() Making cider with 10 apples这与柠檬水示例具有相同的问题其中计数的分配分散了对该变量的注意力。 在这里使用 walrus 运算符来提高此代码的清晰度if (count : fresh_fruit.get(apple, 0)) 4: make_cider(count) else: out_of_stock()这样编写代码符合预期并使代码缩短一行。 需要注意的是我需要用括号将赋值表达式括起来以便将其与 if 语句中的 4 进行比较。在柠檬水的例子中不需要括号因为赋值表达式本身就是一个非零的真值检查它不是一个更大表达式的子表达式。 与其他表达式一样您应该尽可能避免用括号包围赋值表达式以减少视觉干扰。当我需要根据某些条件在封闭范围内赋值给一个变量然后不久后在函数调用中引用该变量时就会出现这种重复模式的另一个常见变体。 例如假设一位顾客订购了一些香蕉冰沙。 为了制作它们我需要至少有两片香蕉的切片否则会引发 “Out Of Bananas” 异常。 这里以典型的方式实现这个逻辑def slice_bananas(count): ... class OutOfBananas(Exception): pass def make_smoothies(count): ... pieces 0 count fresh_fruit.get(banana, 0) if count 2: pieces slice_bananas(count) try: smoothies make_smoothies(pieces) except OutOfBananas: out_of_stock() Slicing 8 bananas Making smoothies with 32 banana slices另一种常见的方法是将 pieces 0 赋值放在 else 块中count fresh_fruit.get(banana, 0) if count 2: pieces slice_bananas(count) else: pieces 0 # Moved try: smoothies make_smoothies(pieces) except OutOfBananas: out_of_stock()第二种方法可能会感觉很奇怪因为它意味着 pieces 变量有两个不同的位置在 if 语句的每个块中可以在其中最初定义它。 这种分割定义在技术上是有效的因为 Python 的作用域规则参见 Item 33“了解闭包如何与变量作用域和非本地交互”但它不容易阅读或发现这就是为什么许多人更喜欢上面的构造其中 pieces 0 赋值是第一个。海象运算符可用于将此示例缩短一行代码。这个小变化消除了对计数变量的任何强调。现在更清楚的是除了 if 语句之外各个片段也很重要pieces 0 if (count : fresh_fruit.get(banana, 0)) 2: # Changed pieces slice_bananas(count) try: smoothies make_smoothies(pieces) except OutOfBananas: out_of_stock()使用 walrus 运算符还可以提高将片段定义拆分到 if 语句的两个部分的可读性。 当 count 定义不再位于 if 语句之前时跟踪 pieces 变量会更容易if (count : fresh_fruit.get(banana, 0)) 2: pieces slice_bananas(count) else: pieces 0 # Moved try: smoothies make_smoothies(pieces) except OutOfBananas: out_of_stock()刚接触 Python 的程序员经常遇到的一个挫折是缺乏灵活的 switch/case 语句。 近似此类功能的一般风格是深度嵌套多个 if、elif 和 else 块。例如假设想实现一个优先系统以便每个客户自动获得最好的果汁而不必订购。 在这里我定义了逻辑以便首先提供香蕉冰沙然后提供苹果酒最后提供柠檬水count fresh_fruit.get(banana, 0) if count 2: pieces slice_bananas(count) to_enjoy make_smoothies(pieces) else: count fresh_fruit.get(apple, 0) if count 4: to_enjoy make_cider(count) else: count fresh_fruit.get(lemon, 0) if count: to_enjoy make_lemonade(count) else: to_enjoy Nothing像这样丑陋的结构在 Python 代码中非常常见。 幸运的是海象运算符提供了一个优雅的解决方案它几乎与 switch/case 语句的专用语法一样通用if (count : fresh_fruit.get(banana, 0)) 2: pieces slice_bananas(count) to_enjoy make_smoothies(pieces) elif (count :fresh_fruit.get(apple, 0)) 4: to_enjoy make_cider(count) elif count : fresh_fruit.get(lemon, 0): to_enjoy make_lemonade(count) else: to_enjoy Nothing使用赋值表达式的版本仅比原始版本短了五行但由于嵌套和缩进的减少可读性的提高是巨大的。Python 新手程序员的另一个常见问题是缺乏 do/while 循环结构。 例如假设我想在交付新水果时将果汁装瓶直到没有水果剩余为止。这里用 while 循环实现这个逻辑def pick_fruit(): ... def make_juice(fruit, count): ... bottles [] fresh_fruit pick_fruit() while fresh_fruit: for fruit, count in fresh_fruit.items(): batch make_juice(fruit, count) bottles.extend(batch) fresh_fruit pick_fruit()这是重复的因为它需要两个单独的 fresh_fruit pick_fruit() 调用一个在循环之前设置初始条件另一个在循环结束时补充交付的水果列表。在这种情况下提高代码复用的策略是使用半循环语句。这消除了多余的行但也削弱了 while 循环的贡献使其成为一个愚蠢的无限循环。 现在循环的所有流程控制都取决于条件 break 语句bottles [] while True: # Loop fresh_fruit pick_fruit() if not fresh_fruit: # And a half break for fruit, count in fresh_fruit.items(): batch make_juice(fruit, count) bottles.extend(batch)海象运算符通过允许重新分配 fresh_fruit 变量然后每次通过 while 循环进行条件评估从而消除了对半循环语句用法的需要。 该解决方案简短且易于阅读它应该是代码中的首选方法bottles [] while fresh_fruit : pick_fruit(): # Changed for fruit, count in fresh_fruit.items(): batch make_juice(fruit, count) bottles.extend(batch)还有许多其他情况可以使用赋值表达式来消除冗余示例请参见 Item 42“使用赋值表达式减少推导式中的重复”。 一般来说当您发现自己在一组行中多次重复相同的表达式或赋值时是时候考虑使用赋值表达式来提高可读性了。注意赋值表达式使用海象运算符 (:) 在单个表达式中对变量名称进行赋值和求值从而减少重复。当赋值表达式是较大表达式的子表达式时必须用括号将其括起来。尽管 switch/case 语句和 do/while 循环在 Python 中不可用但可以通过使用赋值表达式更清楚地模拟它们的功能。Item 9考虑流程控制中解构的匹配避免使用 When if 语句就足够了match 语句是一个相对较新的 Python 功能在 3.10 版本中引入。 由于有很多不同功能导致匹配的学习曲线非常陡峭使人感觉像是在 Python 中嵌入的另一种迷你语言类似于推导式的独特人体工程学请参阅 Item 40“使用推导式而不是映射和过滤器”和 Item 44“考虑使用生成器表达式进行大型列表推导式”。乍一看match 语句似乎为 Python 提供了长期以来备受追捧的行为类似于其他编程语言中的 switch 语句请参阅 Item 8“使用赋值表达式防止重复” 进行了解。例如假设我正在编写一个对交通信号灯颜色做出反应的车辆辅助程序。 这里我使用一个简单的 Python if 语句来实现此目的def take_action(light): if light red: print(Stop) elif light yellow: print(Slow down) elif light green: print(Go!) else: raise RuntimeError可以确认这个函数能够按预期工作take_action(red) take_action(yellow) take_action(green) Stop Slow down Go!要使用 match 语句我可以创建与每个 if、elif 和 else 条件相对应的 case clausedef take_match_action(light): match light: case red: print(Stop) case yellow: print(Slow down) case green: print(Go!) case _: raiseRuntimeError使用 match 语句似乎比使用 if 语句更好因为我可以删除对 light 变量的重复引用并且可以省略每个条件分支的 运算符。 然而这段代码仍然不理想因为它对所有内容都使用字符串文字。为了解决这个问题通常要做的是在模块级别为每种光颜色创建一个常量并修改代码以使用它们如下所示# Added these constants RED red YELLOW yellow GREEN green def take_constant_action(light): match light: case RED: # Changed print(Stop) case YELLOW: # Changed print(Slow down) case GREEN: # Changed print(Go!) case _: raise RuntimeError Traceback ... SyntaxError: name capture RED makes remaining patterns unreachable不幸的是这段代码有一个错误而且是一个神秘的错误。 问题在于 match 语句假定 case 关键字后面的简单变量名称是捕获模式。为了演示这意味着什么在这里缩短 match 语句使其只有一个应该匹配 RED 的分支def take_truncated_action(light): match light: case RED: print(Stop)现在我通过传递 GREEN 来调用该函数。我希望首先评估匹配 light clause并将当前作用域中的 light 变量查找解析为 “绿色”。 接下来我期望对 case RED clause 进行评估并将 RED 变量查找解析为 “red”。这两个值不匹配即“绿色”与“红色”因此我预计不会有输出take_truncated_action(GREEN) Stop令人惊讶的是match 语句执行了 RED 分支。 这里我使用 print 来弄清楚发生了什么def take_debug_action(light): match light: case RED: print(f{RED}, {light}) take_debug_action(GREEN) REDgreen, lightgreencase clause 没有查找 RED 的值。而是将 RED 分配给了 light 变量的值 match 语句所做的与解包行为类似请参阅 Item 5“优先选择多重赋值解包而不是索引”。 Python 不是将 case RED 转换为 light RED而是确定多重赋值 ( RED,) (light,) 是否会在没有错误的情况下执行类似于def take_unpacking_action(light): try: (RED,) (light,) except TypeError: # Did not match ... else: # Matched print(f{RED}, {light})发生上述原始语法错误是因为 Python 在编译时确定赋值 ( RED,) (light,) 将适用于任何 light 值因此后续带有 case YELLOW 和 case GREEN 的子句无法访问。解决此问题的一种方法是确保字符位于 case clause 的变量引用中。点运算符的存在会导致 Python 查找属性并进行相等测试而不是将变量名称视为捕获模式。例如在这里通过使用 enum 内置模块和点运算符来访问每个常量名称来实现最初的预期行为import enum # Added class ColorEnum(enum.Enum): # Added RED red YELLOW yellow GREEN green def take_enum_action(light): match light: case ColorEnum.RED: # Changed print(Stop) case ColorEnum.YELLOW: # Changed print(Slow down) case ColorEnum.GREEN: # Changed print(Go!) case _: raise RuntimeError尽管这段代码现在可以按预期工作但很难看出 match 版本相对于上面 take_action 函数中更简单的 if 版本的优势。if 版本是 9 行与 10 行匹配。 if 版本为每个分支重复 light 前缀但 match 版本为常量重复 Color Enum.prefix 。从表面上看这似乎是一种洗礼。 如果 match 语句不是一个引人注目的功能为什么 Python 还要向该语言中添加 match 语句呢match 用于解构解构是一种编程语言技术用于使用最少的语法从复杂的嵌套数据结构中提取组件。 Python 程序员一直在不假思索地使用解构。 例如在下面这个 for 循环中将索引、值多次赋值给 enumerate 的返回值就是一种解构形式参见 Item 17“优先选择枚举而不是范围”for index, value in enumerate(abc): print(findex {index} is {value}) index 0 is a index 1 is b index 2 is cPython 长期以来一直支持深度嵌套元组和列表的解构赋值请参阅 Item 16“更喜欢 Catch-All 解包而不是切片”。 match 语句扩展了语言还支持字典、集合和用户定义类的这种类似于解包的行为仅用于控制流的目的。当您的代码需要处理异构对象图和半结构化数据时match 的结构模式匹配技术尤其有价值。函数式编程中类似的习惯用法是代数数据类型、求和类型和标记联合。例如假设要搜索二叉树并确定它是否包含给定值。 我们可以将二叉树表示为三元组其中第一个索引是值第二个索引是左较低值子级第三个索引是右较高值子级。第二个位置或第三个位置中的 None 表示不存在子节点。 对于叶节点可以将值内联而不是使用另一个嵌套元组。 这里我们定义一个包含五个值7、9、10、11、13的嵌套树my_tree (10, (7, None, 9), (13, 11, None))我们使用简单的 if 语句实现一个递归函数来测试树是否包含值。树的参数可能是 None对于不存在的子节点或非元组对于叶节点因此该代码需要确保在解包三元组节点表示之前处理这些条件def contains(tree, value): if not isinstance(tree, tuple): return tree value pivot, left, right tree if value pivot: return contains(left, value) elif value pivot: return contains(right, value) else: return value pivot当节点的值具有可比性时此函数将按预期生效assert contains(my_tree, 9) assert not contains(my_tree, 14)现在我可以使用 match 语句重写这个函数def contains_match(tree, value): match tree: case pivot, left, _ if value pivot: return contains_match(left, value) case pivot, _, right if value pivot: return contains_match(right, value) case (pivot, _, _) | pivot: return pivot value使用 match消除了对i sinstance 的调用可以避免拆包赋值代码结构使用 case clauses更加规则逻辑更加简单易懂并且函数只有七行代码而不是 if 版本所需的九行代码。 这使得 match 语句显得非常引人注目参见 Item 76“了解如何将线程 I/O 移植到 asyncio” 以获得另一个示例。在此函数中匹配的方式是每个 case clause 尝试使用给定的解构模式提取树的参数的内容。 Python 确定结构匹配后它会评估任何后续的 if 子句其工作方式与推导式中的 if 子句类似。 当 if clause有时称为保护表达式的计算结果为 True 时将执行该 case 块的缩进语句并跳过其余语句。 如果没有 case clause 与输入值匹配则 match 语句不执行任何操作并失败。此代码还使用 |管道运算符将 or 模式添加到最终的 case 分支。 这允许 case clause 匹配给定模式之一(pivot, _, _) 或 hub。 您可能还记得上面的交通信号灯示例该示例尝试引用 RED constant第二个模式枢轴是与任何值匹配的捕获模式。 因此当树不是具有正确结构的元组时代码假定它是应该测试相等性的叶子值。现在想象一下我们的需求再次发生变化我们想使用类而不是元组来表示二叉树中的节点请参阅 Item 29“组合类而不是深度嵌套字典、列表和元组” 以了解如何做出该选择。 这里为节点定义了一个新类class Node: def __init__(self, value, leftNone, rightNone): self.value value self.left left self.right right我可以使用此类创建树的另一个实例。 同样我仅通过提供叶节点的值来指定叶节点而不是将它们包装在附加的 Node object 中obj_tree Node( value 10, left Node(value 7, right 9), right Node(value 13, left 11), )修改 contains 函数的 if 语句版本来处理 Node class 非常简单def contains_class(tree, value): if not isinstance(tree, Node): return tree value elif value tree.value: return contains_class(tree.left, value) elif value tree.value: return contains_class(tree.right, value) else: return tree.value value生成的代码与使用三元组的早期版本类似地复杂。在某些方面类使函数变得更好例如访问对象属性而不是解包而在其他方面它使函数变得更糟例如重复的 tree. 前缀。还可以调整 contains 函数的 match版本以使用 Node classdef contains_match_class(tree, value): match tree: case Node(value pivot, left left) if value pivot: return contains_match_class(left, value) case Node(value pivot, right right) if value pivot: return contains_match_class(right, value) case Node(value pivot) | pivot: return pivot value其工作方式是每个 case clause 隐式执行 isinstance check 来测试 tree 的值是否是 Node object。 然后它使用捕获模式旋转、左、右提取对象的属性类似于元组解构的工作原理。 捕获变量可以在保护表达式和 case block 中使用以避免更详细的属性访问例如tree.left。 match 所提供的功能和清晰度与对象和嵌套的内置数据结构一样好。半结构化数据与封装数据当数据结构及其解释解耦时match 也会表现出色。 例如反序列化的 JSON 对象仅仅是字典、列表、字符串和数字的嵌套有关示例请参见 Item 54“考虑使用混合类来组合功能”。 它缺乏由显式类层次结构提供的清晰的职责封装参见 Item 53“用 super 初始化父类”。 但这些基本 JSON 类型的嵌套方式每个级别上存在的键、值和元素赋予了程序可以解释的数据语义。例如假设我正在构建计费软件我需要反序列化存储为 JSON 的客户记录。一些记录适用于个人客户其他记录适用于企业客户record1 {customer: {last: Ross, first: Bob}} record2 {customer: {entity: Steves Painting Co.}}我想获取这些记录并将它们转换为定义良好的 Python 对象我可以将其与程序的数据处理功能、UI 小部件等一起使用有关背景信息请参阅 Item 51“首选数据类来定义轻量级类”from dataclasses import dataclass dataclass class PersonCustomer: first_name: str last_name: str dataclass class BusinessCustomer: company_name: str我可以使用 match 语句来解释 JSON 数据中的结构和值并将其映射到具体的 Person Customer 和 Business Customer 类。 这使用 match 语句的独特语法来通过捕获模式解构字典文字import json def deserialize(data): record json.loads(data) match record: case {customer: {last: last_name, first: first_name}}: return PersonCustomer(first_name, last_name) case {customer: {entity: company_name}}: return BusinessCustomer(company_name) case _: raise ValueError(Unknown record type)该函数在上面定义的记录上按预期生效并生成所需要的对象print(Record1:, deserialize(record1)) print(Record2:, deserialize(record2)) Record1: PersonCustomer(first_nameBob, last_nameRoss) Record2: BusinessCustomer(company_nameSteves Painting Co.)这些示例只是让您初步了解 match 语句的可能性。 还支持设置模式、aspatterns、位置构造函数模式使用__match_args__定制、使用类型注释进行详尽检查请参阅 Item 124“通过输入考虑静态分析以消除错误”等等。 考虑到复杂性最好参考官方教程 (https://peps.python.org/pep-0636/) 来确定如何利用 match 来满足您的特定用例。注意尽管您可以使用 match 语句来替换简单的 if 语句但这样做很容易出错。 对于还不熟悉匹配陷阱的 Python 程序员来说case clause 中捕获模式的结构本质是不直观的。match 语句提供了一种简洁的语法用于将 isinstance 检查和解构行为与流控制相结合。 它们在处理异构对象图和解释半结构化数据的语义时特别有用。case patterns 可以有效地与内置数据结构例如列表、元组、字典和用户定义的类一起使用但每种类型都有独特的语义这些语义并不立即显而易见。