【问题标题】:Check if a string is hexadecimal检查字符串是否为十六进制
【发布时间】:2012-07-20 11:48:59
【问题描述】:

我知道最简单的方法是使用regular expression,但我想知道是否还有其他方法可以进行此检查。

我为什么需要这个?我正在编写一个从SIM 卡读取短信(SMS)的 Python 脚本。在某些情况下,十六进制消息到达,我需要对其进行一些处理,因此我需要检查接收到的消息是否为十六进制。

当我发送以下短信时:

Hello world!

我的脚本收到

00480065006C006C006F00200077006F0072006C00640021

但在某些情况下,我会收到正常的短信(不是十六进制的)。所以我需要做一个 if hex 控制。

我使用的是 Python 2.6.5。

更新:

这个问题的原因是,(不知何故)我发送的消息被接收为hex,而操作员发送的消息(信息消息和广告。)被接收为普通字符串。所以我决定检查一下,确保我收到的消息是正确的字符串格式。

一些额外的细节:我正在使用华为 3G 调制解调器和PyHumod 从 SIM 卡读取数据。

我的情况可能的最佳解决方案:

处理此类字符串的最佳方法是使用a2b_hex(又名unhexlify)和utf-16 big endian encoding(如@JonasWielicki 所述):

from binascii import unhexlify  # unhexlify is another name of a2b_hex

mystr = "00480065006C006C006F00200077006F0072006C00640021"
unhexlify(mystr).encode("utf-16-be")
>> u'Hello world!'

【问题讨论】:

  • 我不认为这个问题看起来很简单,如果你读到“333 445”之类的东西,它可以是电话号码(字符串)或十六进制值,如何你能确定吗?我认为真正的问题是你为什么要同时阅读?
  • @mouad 这本身就是一个问题,但在我的情况下,我不会打扰。
  • 顺便说一下,扩展的十六进制代码看起来很像 UCS-2 大端编码。
  • 我认为也许正则表达式 wiz(我不是)可以使用 RE 进行检查。

标签: python hex


【解决方案1】:

(1) 使用 int() 可以很好地解决这个问题,Python 会为您完成所有检查:)

int('00480065006C006C006F00200077006F0072006C00640021', 16)
6896377547970387516320582441726837832153446723333914657L

会起作用。如果失败,您将收到ValueError 异常。

简短的例子:

int('af', 16)
175

int('ah', 16)
 ...
ValueError: invalid literal for int() with base 16: 'ah'

(2)替代方法是遍历数据并确保所有字符都在0..9a-f/A-F 的范围内。 string.hexdigits ('0123456789abcdefABCDEF') 对此很有用,因为它包含 大写和小写数字。

import string
all(c in string.hexdigits for c in s)

将根据字符串s 中数据的有效性返回TrueFalse

简短的例子:

s = 'af'
all(c in string.hexdigits for c in s)
True

s = 'ah'
all(c in string.hexdigits for c in s)
False

备注

正如@ScottGriffiths 在下面的评论中正确指出的那样,如果您的字符串在开头包含0x,则int() 方法将起作用,而逐个字符的检查将因此而失败。此外,检查一个 set 字符比检查一个 string 字符要快,但是这对于短 SMS 字符串是否重要值得怀疑,除非您处理很多(很多!)它们按顺序排列,在这种情况下,您可以将 stringhexditigs 转换为带有 set(string.hexdigits) 的集合。

【讨论】:

  • 一个小问题是这两种方法并不完全等效(eumiro 的答案也是如此)。对于以0x0X 开头的字符串,转换为int 会成功,但其他方法不会。
  • @ScottGriffiths 好点,我会在我的答案中添加一个注释以防万一,尽管对于 OP 作为示例输入显示的数据,解决方案有效。谢谢
  • 第一个可能是错误提示在某些地方使用,因为负数int('-a', 16) -> 10 不要引发 ValueError where - 运算符不应被视为十六进制字符串的一部分。
  • all 测试将在空字符串上返回 True。
【解决方案2】:

你可以:

  1. 测试字符串是否只包含十六进制数字(0…9,A…F)
  2. 尝试将字符串转换为整数,看看是否失败。

代码如下:

import string
def is_hex(s):
     hex_digits = set(string.hexdigits)
     # if s is long, then it is faster to check against a set
     return all(c in hex_digits for c in s)

def is_hex(s):
    try:
        int(s, 16)
        return True
    except ValueError:
        return False

【讨论】:

  • @JonasWielicki 输入可能有大写和小写
  • 这就是为什么我建议在测试字符串中添加ABCDEF,除了abcdef
  • return all(c.lower() in '0123456789abcdef' for c in s) 比 return all(c in '0123456789abcdef' for c in s.lower()) 更快
  • @Pooya - '7890' 是十进制和十六进制数,就像 '1010' 可以是二进制、八进制、十进制、十六进制以及任何数字......
  • @Pooya:当你说“return all(c.lower() in '0123456789abcdef' for c in s) 比 return all(c in '0123456789abcdef' for c在 s.lower())”中。如果你需要c.lower(),你会多次调用lower(),而如果你需要s.lower(),你只需要调用一次lower()。当然,我认为最好完全避免lower(),并按照乔纳斯的建议(已编辑为答案)。
【解决方案3】:

我知道提到的操作 regular expressions,但为了完整起见,我想贡献这样一个解决方案:

def is_hex(s):
    return re.fullmatch(r"^[0-9a-fA-F]$", s or "") is not None

性能

为了评估这里提出的不同解决方案的性能,我使用了 Python 的 timeit 模块。输入字符串随机生成三种不同长度,101001000

s=''.join(random.choice('0123456789abcdef') for _ in range(10))

Levon's解决方案:

# int(s, 16)
  10: 0.257451018987922
 100: 0.40081690801889636
1000: 1.8926858339982573

# all(_ in string.hexdigits for _ in s)
  10:  1.2884491360164247
 100: 10.047717947978526
1000: 94.35805322701344

其他答案是这两者的变体。使用正则表达式:

# re.fullmatch(r'^[0-9a-fA-F]$', s or '')
  10: 0.725040541990893
 100: 0.7184272820013575
1000: 0.7190397029917222

因此,选择正确的解决方案取决于输入字符串的长度以及是否可以安全地处理异常。正则表达式当然可以更快地处理大字符串(并且不会在溢出时抛出 ValueError),但 int() 是较短字符串的赢家。

【讨论】:

  • 因为完全匹配在 Python 2.7 中不可用,您可以使用 return re.search(r'^[0-9A-Fa-f]+$', s) is not None
  • 这个答案的结论有些错误。它应该使用re.fullmatch(r'[0-9a-fA-F]+', s or '')+ 量词。如果您使用它,那么在我的测试中(在 Python 3.6 上),int(s, 16) 对于 所有字符串长度 是最快的。但是,正则表达式可能仍然是更好的选择,因为 int(s, 16) 接受诸如 "0x0" 之类的字符串。
  • 如果将正则表达式更改为'^(0[xX])?[0-9a-fA-F]+$',则处理可能的0x 前缀的问题得到解决。
【解决方案4】:

一种基于字符串转换以设置和检查子集(不检查'0x'前缀)的更简单和简短的解决方案:

import string
def is_hex_str(s):
    return set(s).issubset(string.hexdigits)

更多信息here

【讨论】:

    【解决方案5】:

    另一种选择:

    def is_hex(s):
        hex_digits = set("0123456789abcdef")
        for char in s:
            if not (char in hex_digits):
                return False
        return True
    

    【讨论】:

      【解决方案6】:

      上面提出的大多数解决方案都没有考虑到任何十进制整数也可能被解码为十六进制,因为十进制数字集是十六进制数字集的子集。所以 Python 很乐意接受 123 并假设它是 0123 hex:

      >>> int('123',16)
      291
      

      这听起来很明显,但在大多数情况下,您会寻找实际上是十六进制编码的东西,例如一个哈希值,而不是任何 可以 被十六进制解码的东西。所以可能一个更健壮的解决方案还应该检查十六进制字符串的偶数长度:

      In [1]: def is_hex(s):
         ...:     try:
         ...:         int(s, 16)
         ...:     except ValueError:
         ...:         return False
         ...:     return len(s) % 2 == 0
         ...: 
      
      In [2]: is_hex('123')
      Out[2]: False
      
      In [3]: is_hex('f123')
      Out[3]: True
      

      【讨论】:

        【解决方案7】:

        这将涵盖字符串以“0x”或“0X”开头的情况:[0x|0X][0-9a-fA-F]

        d='0X12a'
        all(c in 'xX' + string.hexdigits for c in d)
        True
        

        【讨论】:

        • 其实"xxxXXX""123xXxABC"不是十六进制字符串,但是你上面的表达式会返回True
        【解决方案8】:

        使用 Python 来确定 True 或 False,我会使用 eumero 的 is_hex 方法而不是 Levon 的方法一。以下代码包含一个陷阱...

        if int(input_string, 16):
            print 'it is hex'
        else:
            print 'it is not hex'
        

        它错误地将字符串 '00' 报告为 not 十六进制,因为零的计算结果为 False。

        【讨论】:

          【解决方案9】:

          由于上述所有正则表达式花费的时间大致相同,我猜大部分时间都与将字符串转换为正则表达式有关。下面是我预编译正则表达式时得到的数据。

          int_hex  
          0.000800 ms 10  
          0.001300 ms 100  
          0.008200 ms 1000  
          
          all_hex  
          0.003500 ms 10  
          0.015200 ms 100  
          0.112000 ms 1000  
          
          fullmatch_hex  
          0.001800 ms 10  
          0.001200 ms 100  
          0.005500 ms 1000
          

          【讨论】:

            【解决方案10】:

            简单的解决方案,如果您需要一个模式来验证前缀十六进制或二进制以及十进制

            \b(0x[\da-fA-F]+|[\d]+|0b[01]+)\b
            

            示例:https://regex101.com/r/cN4yW7/14

            然后在 python 中做int('0x00480065006C006C006F00200077006F0072006C00640021', 0) 给出 6896377547970387516320582441726837832153446723333914657

            base 0 调用前缀猜测行为。 这为我省去了很多麻烦。希望对您有所帮助!

            【讨论】:

              【解决方案11】:

              大多数解决方案都不能正确检查前缀为0x的字符串

              >>> is_hex_string("0xaaa")  
              False  
              >>> is_hex_string("0x123")  
              False  
              >>> is_hex_string("0xfff")  
              False  
              >>> is_hex_string("fff")  
              True  
              

              【讨论】:

                【解决方案12】:

                在 Python3 中,我尝试过:

                def is_hex(s):
                    try:
                        tmp=bytes.fromhex(hex_data).decode('utf-8')
                        return ''.join([i for i in tmp if i.isprintable()])
                    except ValueError:
                        return ''
                

                应该比方式更好:int(x, 16)

                【讨论】:

                • 为什么会比int(s, 16)更好?您的函数采用s 参数并且不使用它(我假设它应该是hex_data)。它还调用decode(),对于每个不正确的 UTF8 编码的 Unicode 字符都会失败——其中有很多考虑随机十六进制输入。 isprintable()的目的是什么?
                猜你喜欢
                • 2012-02-22
                • 2012-07-10
                • 1970-01-01
                • 1970-01-01
                • 2014-11-14
                • 2014-03-26
                • 2015-05-23
                • 1970-01-01
                • 2018-01-31
                相关资源
                最近更新 更多