Sprawdź, czy łańcuch jest szesnastkowy

Wiem, że najprostszym sposobem jest użycie wyrażenia regularnego , ale zastanawiam się, czy są inne sposoby, aby to sprawdzić.

Po co mi to? Piszę skrypt Pythona, który odczytuje wiadomości tekstowe (SMS) z karty SIM. W niektórych sytuacjach pojawiają się wiadomości szesnastkowe i muszę je przetworzyć, więc muszę sprawdzić, czy otrzymana wiadomość jest szesnastkowa.

Kiedy wysyłam następujący SMS:

Hello world!

I mój skrypt otrzymuje

00480065006C006C006F00200077006F0072006C00640021

Ale w w niektórych sytuacjach otrzymuję zwykłe wiadomości tekstowe (nie szesnastkowe). Więc muszę wykonać if hex control.

Używam Pythona 2.6.5.

Aktualizacja:

Powodem tego problemu jest to, (jakoś) wiadomości, które wysłałem, są odbierane jako hex, podczas gdy wiadomości wysyłane przez operatora (wiadomości informacyjne i reklamy.) są odbierane jako zwykły ciąg znaków. Więc postanowiłem sprawdzić i upewnić się, że mam wiadomość w prawidłowym formacie ciągu.

Kilka dodatkowych szczegółów : używam Modem 3G Huawei i PyHumod {[9] } do odczytu danych z karty sim.

możliwe najlepsze rozwiązanie mojej sytuacji:

Najlepszym sposobem obsługi takich łańcuchów jest użycie a2b_hex (vel unhexlify) i utf-16 big endian encoding (jak wspomniał @ JonasWielicki):

from binascii import unhexlify  # unhexlify is another name of a2b_hex

mystr = "00480065006C006C006F00200077006F0072006C00640021"
unhexlify(mystr).encode("utf-16-be")
>> u'Hello world!'
 32
Author: Peter Mortensen, 2012-07-21

8 answers

(1) użycie int () Działa do tego ładnie, a Python sprawdza wszystko za Ciebie:)

int('00480065006C006C006F00200077006F0072006C00640021', 16)
6896377547970387516320582441726837832153446723333914657L

Zadziała. W przypadku awarii otrzymasz wyjątek ValueError.

krótki przykład:

int('af', 16)
175

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

(2) alternatywą byłoby przemieszczenie danych i upewnienie się, że wszystkie znaki mieszczą się w zakresie0..9 i a-f/A-F. string.hexdigits ('0123456789abcdefABCDEF') jest to przydatne, ponieważ zawiera zarówno duże jak i małe litery cyfry.

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

Zwróci True lub False na podstawie ważności Twoich danych w łańcuchu s.

krótki przykład:

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

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

uwagi:

Jak @ScottGriffiths zauważa poprawnie w komentarzu poniżej, podejście int() będzie działać, jeśli twój ciąg znaków zawiera 0x na początku, podczas gdy sprawdzanie znak po znaku nie powiedzie się z tym. Ponadto sprawdzanie przed zestawem znaków jest szybsze niż łańcuch z znaków, ale wątpliwe, że będzie to miało znaczenie przy krótkich ciągach SMS, chyba że przetworzysz wiele (wiele!) z nich w kolejności, w którym to przypadku można przekonwertować stringhexditigs do zbioru z set(string.hexdigits).

 56
Author: Levon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-21 14:16:43

Możesz:

  1. sprawdź, czy łańcuch zawiera tylko cyfry szesnastkowe (0...9,A...F)
  2. spróbuj przekonwertować łańcuch na liczbę całkowitą i sprawdź, czy się nie powiedzie.

Oto kod:

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
 15
Author: eumiro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-21 13:59:14

Znam op wspomniane wyrażenia regularne , ale chciałem dodać takie rozwiązanie ze względu na kompletność:

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

Wydajność

W celu oceny wydajności różnych rozwiązań zaproponowanych tutaj, użyłem modułu timeit Pythona. Ciągi wejściowe są generowane losowo dla trzech różnych długości, 10, 100, 1000:

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

rozwiązania Levona:

# 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

Inne odpowiedzi to wariacje z tych dwóch. Użycie wyrażenia regularnego:

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

Wybór właściwego rozwiązania zależy więc od długości łańcucha wejściowego i tego, czy wyjątki mogą być bezpiecznie obsługiwane. Wyrażenie regularne z pewnością obsługuje duże struny znacznie szybciej (i nie rzuca ValueError na przepełnienie), ale int() jest zwycięzcą dla krótszych strun.

 9
Author: Jens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:25

Inna opcja:

def is_hex(s):
    hex_digits = set("0123456789abcdef")
    for char in s:
        if not (char in hex_digits):
            return False
    return True
 2
Author: Lance Lefebure,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-24 18:48:35

Większość rozwiązań zaproponowanych powyżej nie bierze pod uwagę, że każda liczba dziesiętna może być również dekodowana jako hex, ponieważ zestaw cyfr dziesiętnych jest podzbiorem zestawu cyfr szesnastkowych. Więc Python z radością weźmie {[2] } i założy, że to 0123 hex:

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

Może to wydawać się oczywiste, ale w większości przypadków będziesz szukał czegoś, co było kodowane szesnastkowo, np. hash, a nie czegoś, co może być dekodowane szesnastkowo. Więc chyba bardziej solidne rozwiązanie powinno również sprawdzić równomierną długość z ciągu sześciokątnego:

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
 2
Author: kravietz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-29 19:42:24

W Python3 próbowałem:

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 ''

Powinno być lepiej niż droga: int (x, 16)

 0
Author: 69444091,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-04 15:40:58

Używając Pythona, którego szukasz do określenia True lub False, użyłbym metody is_hex eumero zamiast metody Levona. Poniższy kod zawiera "gotcha"...

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

Niepoprawnie zgłasza łańcuch " 00 " jako , a nie hex, ponieważ zero jest obliczane jako False.

 0
Author: andrew pate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-14 06:55:36

To obejmie przypadek, jeśli łańcuch zaczyna się od '0x' lub '0x': [0x|0x] [0-9A-fA-F]

d='0X12a'
all(c in 'xX' + string.hexdigits for c in d)
True
 -1
Author: raju_d,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-16 21:44:32