Python区分大小写(开关)性能

rmbxnbpk  于 2022-11-26  发布在  Python
关注(0)|答案(4)|浏览(146)

我希望Python match/case对每种情况都有相同的时间访问,但似乎我错了。有什么好的解释吗?
让我们使用以下示例:

def match_case(decimal):
    match decimal:
      case '0':
        return "000"
      case '1':
        return "001"
      case '2':
        return "010"
      case '3':
        return "011"
      case '4':
        return "100"
      case '5':
        return "101"
      case '6':
        return "110"
      case '7':
        return "111"
      case _:
        return "NA"

并定义一个快速工具来测量时间:

import time
def measure_time(funcion):
    def measured_function(*args, **kwargs):
        init = time.time()
        c = funcion(*args, **kwargs)
        print(f"Input: {args[1]} Time: {time.time() - init}")
        return c
    return measured_function

@measure_time
def repeat(function, input):
    return [function(input) for i in range(10000000)]

如果我们对每个用例运行10000000次,则次数如下:

for i in range(8):
    repeat(match_case, str(i))

# Input: 0 Time: 2.458001136779785
# Input: 1 Time: 2.36093807220459
# Input: 2 Time: 2.6832823753356934
# Input: 3 Time: 2.9995620250701904
# Input: 4 Time: 3.5054492950439453
# Input: 5 Time: 3.815168857574463
# Input: 6 Time: 4.164452791213989
# Input: 7 Time: 4.857251167297363

只是想知道为什么访问时间是不同的。这是不是优化了也许一个查找表?注意,我不感兴趣的其他方式有相等的访问时间(即字典)。

bmvo0sr5

bmvo0sr51#

PEP 622开发“match\case”功能是为了替换如下代码:

def is_tuple(node):
if isinstance(node, Node) and node.children == [LParen(), RParen()]:
    return True
return (isinstance(node, Node)
        and len(node.children) == 3
        and isinstance(node.children[0], Leaf)
        and isinstance(node.children[1], Node)
        and isinstance(node.children[2], Leaf)
        and node.children[0].value == "("
        and node.children[2].value == ")")

代码如下:

def is_tuple(node: Node) -> bool:
match node:
    case Node(children=[LParen(), RParen()]):
        return True
    case Node(children=[Leaf(value="("), Node(), Leaf(value=")")]):
        return True
    case _:
        return False

虽然在最原始的情况下,它可能相当于dict查找,但通常情况下并不是这样的。Case模式被设计成看起来像普通的python代码,但实际上它们隐藏了isinsancelen调用,并且当你看到像Node()这样的代码时,它们不会执行你所期望执行的。
本质上,这等价于if ... elif ... else语句链。注意,与前面提出的switch语句不同,预先计算的调度字典语义在这里不适用。

lkaoscv7

lkaoscv72#

我尝试使用另一个函数调用match_if来重复您的实验:

def match_if(decimal):
    if decimal == '0':
        return "000"
    elif decimal == '1':
        return "001"
    elif decimal == '2':
        return "010"
    elif decimal == '3':
        return "011"
    elif decimal == '4':
        return "100"
    elif decimal == '5':
        return "101"
    elif decimal == '6':
        return "110"
    elif decimal == '7':
        return "111"
    else:
        return "NA"

看起来如果我们使用if,elif,else语句的效率不如match / case方法。下面是我的结果:

for i in range(8):
    repeat(match_if, str(i))

Input: 0 Time: 1.6081502437591553
Input: 1 Time: 1.7993037700653076
Input: 2 Time: 2.094271659851074
Input: 3 Time: 2.3727521896362305
Input: 4 Time: 2.6943907737731934
Input: 5 Time: 2.922682285308838
Input: 6 Time: 3.3238701820373535
Input: 7 Time: 3.569467782974243

结果匹配/病例:

for i in range(8):
    repeat(match_case, str(i))

 Input: 0 Time: 1.4507110118865967
 Input: 1 Time: 1.745032787322998
 Input: 2 Time: 1.988663911819458
 Input: 3 Time: 2.2570419311523438
 Input: 4 Time: 2.54061222076416
 Input: 5 Time: 2.7649216651916504
 Input: 6 Time: 3.1373682022094727
 Input: 7 Time: 3.3378067016601562

关于为什么会出现这些结果,我没有一个精确的答案,但这个实验表明,如果我们使用if语句,比match case稍长一点。

bqf10yzr

bqf10yzr3#

我来晚了,但我觉得我可以添加一些有用的东西到这个线程。
不久前,我在Medium上发表了一篇关于Python的match case性能的文章,分析了生成的字节码,并进行了基准测试和比较。如果你愿意,你可以阅读文章here。我认为这是值得你花时间的。
不过,我在这里总结一下。
许多编程语言都把switch-case语句当作if-else序列来实现,有时switch-case语句可以被优化成一个高效的查找表,但并不总是这样。
在Python中,从来不执行这种优化,因此总是导致一系列的条件检查。
从文章中可以看到if-else和match-case之间的速度比较:

Average time for match_case:  0.00424 seconds
Average time for if_else:     0.00413 seconds

如你所见,它们几乎相等。
另外,如果您分解这两个语句,您会发现它们生成几乎相同的字节代码。
需要指出的一个区别是,if-else检查调用对象的__eq__()方法,而match-case在内部进行比较。
最后,这里有一个基准测试,它还包括一个哈希表(字典):

Average time for match_case:  0.00438 seconds
Average time for if_else:     0.00433 seconds
Average time for dict_lookup: 0.00083 seconds

因此,如果你对性能很感兴趣,你应该使用哈希表。尽管match-case类似于C风格的switch case,但它不是:match-case用于结构模式匹配,而不是用于替换性能哈希表和查找表。

55ooxyrt

55ooxyrt4#

从python 3.10开始,可以使用match... case和默认选项(所谓的通配符),用case _表示

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the internet"

文件https://docs.python.org/3/whatsnew/3.10.html#simple-pattern-match-to-a-literal

性能

如果您关心match... caseif.. elif... else的性能,则它们相同

正如您所看到的,Python的(andilabs:看汇编指令)match-case语句只是一系列隐藏的比较,就像if-else方法一样。这就是为什么当我们对这两种方法进行基准测试时,它们的性能是一样的。
来源:https://betterprogramming.pub/pythons-match-case-is-too-slow-if-you-don-t-understand-it-8e8d0cf927d

使用字典方法如何?

关于使用旧pythonic方法切换字典的旁注-它比较慢,因此对于python3.10+和match... case,它可能会成为真正的老式方法。

相关问题