Проверьте, является ли строка шестнадцатеричной
Я знаю, что самый простой способ - использовать регулярное выражение , но мне интересно, есть ли другие способы сделать эту проверку.
Зачем мне это нужно? Я пишу Python script, который читает текстовые сообщения (SMS) с карты SIM. В некоторых ситуациях появляется шестнадцатеричное сообщение, и мне нужно выполнить некоторую обработку для них, поэтому мне нужно проверить, является ли принятое сообщение шестнадцатеричным.
При отправке следующих SMS:
Hello world!
И мой script получает
00480065006C006C006F00200077006F0072006C00640021
Но в некоторых ситуациях я получаю обычные текстовые сообщения (не hex). Поэтому мне нужно сделать элемент if hex.
Я использую Python 2.6.5.
UPDATE:
Причиной этой проблемы являются (как-то) сообщения, отправленные мной, как hex
, в то время как сообщения, отправленные оператором (информационные сообщения и объявления.), принимаются как обычная строка. Поэтому я решил сделать чек и убедиться, что у меня есть сообщение в правильном формате строки.
Дополнительные сведения. Я использую модем Huawei 3G и PyHumod для чтения данных с SIM-карты.
Возможно наилучшее решение моей ситуации:
Лучший способ обработки таких строк - использовать a2b_hex
(a.k.a. 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!'
Ответы
Ответ 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..9
и a-f/A-F
. string.hexdigits
('0123456789abcdefABCDEF'
) полезен для этого, поскольку он содержит как верхний, так и нижний регистр цифр.
import string
all(c in string.hexdigits for c in s)
вернет либо True
, либо False
в зависимости от действительности ваших данных в строке s
.
Краткий пример:
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 в комментарии ниже, подход int()
будет работать, если ваша строка содержит 0x
в начале, в то время как проверка символа по символу будет сбой. Кроме того, проверка на набор символов выполняется быстрее, чем строка символов, но это сомнительно, что это будет иметь значение с короткими строками SMS, если вы не обработаете много (много!) Из них последовательно, и в этом случае вы можете преобразовать stringhexditigs в набор с set(string.hexdigits)
.
Ответ 2
Вы можете:
- проверьте, содержит ли строка только шестнадцатеричные цифры (0... 9, A... F)
- попробуйте преобразовать строку в целое число и посмотреть, не сработает ли она.
Вот код:
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
Ответ 3
Я знаю, что в операторе упоминаются регулярные выражения, но я хотел бы предложить такое решение для полноты картины:
def is_hex(s):
return re.fullmatch(r"^[0-9a-fA-F]$", s or "") is not None
Спектакль
Чтобы оценить производительность различных решений, предложенных здесь, я использовал модуль Python Timeit. Входные строки генерируются случайным образом для трех разных длин: 10
, 100
, 1000
:
s=''.join(random.choice('0123456789abcdef') for _ in range(10))
Решения Левона:
# 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()
является победителем для более коротких строк.
Ответ 4
Другая опция:
def is_hex(s):
hex_digits = set("0123456789abcdef")
for char in s:
if not (char in hex_digits):
return False
return True
Ответ 5
В большинстве предложенных решений не учитывается, что любое десятичное целое также может быть декодировано как шестнадцатеричное, поскольку набор десятичных цифр является подмножеством шестизначных цифр. Поэтому 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
Ответ 6
Еще одно простое и короткое решение, основанное на преобразовании строки в набор и проверке на подмножество (не проверяет префикс "0x"):
import string
def is_hex_str(s):
return set(s).issubset(string.hexdigits)
Больше информации здесь.
Ответ 7
Это будет охватывать случай, если строка начинается с "0x" или "0X": [0x | 0X] [0-9a-fA-F]
d='0X12a'
all(c in 'xX' + string.hexdigits for c in d)
True
Ответ 8
Используя Python, вы хотите определить True или False, я бы использовал метод eumero is_hex по методу Levon one. Следующий код содержит getcha...
if int(input_string, 16):
print 'it is hex'
else:
print 'it is not hex'
Он неверно сообщает строку '00' как не шестнадцатеричную, так как ноль имеет значение False.
Ответ 9
В 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)
Ответ 10
Поскольку все приведенные выше регулярные выражения занимают примерно одинаковое количество времени, я предполагаю, что большую часть времени они были связаны с преобразованием строки в регулярное выражение. Ниже приведены данные, которые я получил при предварительной компиляции регулярного выражения.
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