使用结构模式匹配反转测试

Invert a test with structural pattern matching

我一直在将 if-elif-chains 转换为结构模式匹配,但在反向测试中遇到困难。

创建匹配任何受支持模式(文字、class、映射、序列等)的案例很容易。我如何证明否定匹配?

例如,当对象的类型匹配时,我需要强制对象:

   if isinstance(x, (list, dict)):
      x = json.dump(x)  
   elif not isinstance(x, str):    # <-- Inverted test
      x = str(x)

基本技术

从本质上讲,结构模式匹配的设计仅在存在正匹配时触发案例。但是,有两种解决方法。

最简单的方法是添加 guard 表达式。但这是最后的手段,因为它没有利用模式匹配和解构功能。

第二种方法是添加一个较早的匹配测试,以便后面的情况可以假定反向匹配为真。如果否定案例是最后一个案例,那么这很好用。如果不是,就有点尴尬了,因为需要嵌套。

守卫

进行倒排测试并将其移动到 if-expression:

   match x:
       case list() | dict():
           x = json.dump(x)
       case _ if not isinstance(x, str):    # <-- Inverted test
           x = str(x)

预测试

这里的基本思想是进行正匹配,以便后续案例可以假设为负匹配:

   match x:
       case list() | dict():
           x = json.dump(x)
       case str():                          # <-- Positive match
           pass
       case _:                              # <-- Inverted case
           x = str(x)

后续案例的预测试

预测试技术很优雅,除非你有额外的案例要匹配:

   if isinstance(x, (list, dict)):
      x = json.dump(x)  
   elif not isinstance(x, str):    # <-- Inverted test
      x = str(x)
   elif x == 'quit':               # <-- Additional test
      sys.exit(0)

此处最简单的解决方案是将附加测试移到倒置测试之前:

   match x:
       case list() | dict():
           x = json.dump(x)
       case 'quit':                    # <-- Moved the test up
           sys.exit(0)
       case str():                     # <-- Positive match
           pass
       case _:                         # <-- Inverted case
           x = str(x)

并非总是可以重新排序测试。如果是这样,那么可以引入新的嵌套匹配级别:

   case list() | dict():
       x = json.dump(x)
   case str():                     # <-- Positive match
       match x:                    # <-- Nested match
           case 'quit':            # <-- Inner case
               sys.exit(0)               
   case _:                         # <-- Inverted case
       x = str(x)