代码之家  ›  专栏  ›  技术社区  ›  norok2

如果输入可以是“str”或“bytes”,则检查是否相等`

  •  0
  • norok2  · 技术社区  · 2 年前

    我正在尝试编写一个函数,检查两个字符串(仅包含ASCII内容)或字节是否相等。

    现在我有:

    import typing as typ
    
    
    def is_equal_str_bytes(
        a: typ.Union[str, bytes],
        b: typ.Union[str, bytes],
    ) -> bool:
        if isinstance(a, str):
            a = a.encode()
        if isinstance(b, str):
            b = b.encode()
        return a == b
    

    这适用于以下任意组合 str bytes 类型,而 == 操作员将返回 False (正确地)如果这两种类型不同。

    import itertools
    
    
    ss = "ciao", b"ciao"
    for a, b in itertools.product(ss, repeat=2):
        print(f"{a!r:<8} {b!r:<8} {is_equal_str_bytes(a, b)} {a == b}")
    # 'ciao'   'ciao'   True True
    # 'ciao'   b'ciao'  True False
    # b'ciao'  'ciao'   True False
    # b'ciao'  b'ciao'  True True
    

    有没有更简单/更快的方法?

    0 回复  |  直到 2 年前
        1
  •  1
  •   Kelly Bundy    2 年前

    一些基准测试具有随机相等的字符串/百万个字符的字节( on TIO Python 3.8预发行版,但我在3.10.2中获得了类似的时间):

      186.88 us  s.encode()
      187.39 us  s.encode("utf-8")
      183.85 us  s.encode("ascii")
       94.62 us  b.decode()
       94.27 us  b.decode("utf-8")
      137.91 us  b.decode("ascii")
       79.93 us  s == s2
       82.69 us  b == b2
      182.72 us  s + "a"
      177.06 us  b + b"a"
        0.08 us  len(s)
        0.07 us  len(b)
        1.14 us  s[:1000].encode()
        0.97 us  b[:1000].decode()
        2.06 us  s[::1000].encode()
        1.45 us  b[::1000].decode()
        1.91 us  hash(s)
        1.56 us  hash(b)
      508.62 us  hash(s2)
      546.00 us  hash(b2)
        2.85 us  str(s)
     9142.59 us  str(b)
    13541.64 us  repr(s)
     9100.34 us  repr(b)
    

    基于此的想法:

    • 我想对于更简单的代码,也许我们可以申请 str repr 然后以某种方式比较得到的字符串(如删除后 b 前缀),但基准测试表明这将非常缓慢。
    • 获取长度非常便宜,所以我会先比较一下。回来 False 如果不同,则继续。
    • 如果你已经对它们进行了散列运算,或者以后还要进行散列运算,那么你可以比较散列(并返回 错误 如果不同则继续)。看见 ASCII str / bytes hash collision 为什么相等的ASCII字符串和ASCII字节具有相同的散列。(但我不确定它是否有语言保证,所以它可能不安全,我不确定)。请注意,第一次哈希很慢(请参阅哈希时间 s2 / b2 )但是对存储的散列的后续查找是快速的(请参阅有关散列的时间 s / b )。
    • 解码似乎比编码快,所以应该这样做。
    • 只有当类型不同时才解码(一个是字符串,一个是字节),否则只使用 ==
    • 如果第一个字节已经不匹配,那么解码一百万个字节是浪费的。因此,解码/比较长度较短的块而不是整件事可能是值得的,或者在测试整件事之前测试一些短前缀或横截面。

    因此,这里有一些使用上述优化的潜在更快的优化(未经过测试/基准测试,部分原因是它取决于您的数据):

    import typing as typ
    
    def is_equal_str_bytes(
        a: typ.Union[str, bytes],
        b: typ.Union[str, bytes],
    ) -> bool:
        if len(a) != len(b):
            return False
        if hash(a) != hash(b):
            return False
        if type(a) is type(b):
            return a == b
        if isinstance(a, bytes):  # make a=str, b=bytes
            a, b = b, a
        if a[:1000] != b[:1000].decode():
            return False
        if a[::1000] != b[::1000].decode():
            return False
        return a == b.decode()
    

    我的基准代码:

    import os
    from timeit import repeat
    
    n = 10**6
    b = bytes(x & 127 for x in os.urandom(n))
    s = b.decode()
    assert hash(s) == hash(b)
    
    setup = '''
    from __main__ import s, b
    s2 = b.decode()  # Always fresh so it doesn't have a hash stored already 
    b2 = s.encode()
    assert s2 is not s and b2 is not b
    '''
    
    exprs = [
        's.encode()',
        's.encode("utf-8")',
        's.encode("ascii")',
        'b.decode()',
        'b.decode("utf-8")',
        'b.decode("ascii")',
        's == s2',
        'b == b2',
        's + "a"',
        'b + b"a"',
        'len(s)',
        'len(b)',
        's[:1000].encode()',
        'b[:1000].decode()',
        's[::1000].encode()',
        'b[::1000].decode()',
        'hash(s)',
        'hash(b)',
        'hash(s2)',
        'hash(b2)',
        'str(s)',
        'str(b)',
        'repr(s)',
        'repr(b)',
    ]
    
    for _ in range(3):
        for e in exprs:
            number = 100 if exprs.index(e) < exprs.index('hash(s)') else 1
            t = min(repeat(e, setup, number=number)) / number
            print('%8.2f us ' % (t * 1e6), e)
        print()
    
        2
  •  0
  •   LTJ    2 年前

    恐怕没有更简单的方法可以做到这一点,如果直接从源代码中为处理目的向str键入所有内容不是一种选择的话。

    如果你想让函数本身稍微快一点,你可以添加另一个检查来减少不必要的转换:

    if type(a) == type(b):
        return a == b
    

    第三种选择是引入一个新的子类,例如str的衍生物,并添加一个比较函数或一个伪函数 decode() 作用然后使用它,而不是带有 __builtin__.str = my_str

    推荐文章