web-dev-qa-db-fra.com

Vérifiez si une chaîne est hexadécimale

Je sais que la façon la plus simple est d'utiliser un expression régulière , mais je me demande s'il existe d'autres façons de faire cette vérification.

Pourquoi ai-je besoin de ça? J'écris un script Python qui lit les messages texte (SMS) à partir d'une carte SIM . Dans certaines situations, les messages hexadécimaux arrive et j'ai besoin de faire un traitement pour eux, donc je dois vérifier si un message reçu est hexadécimal.

Lorsque j'envoie un SMS suivant:

Hello world!

Et mon script reçoit

00480065006C006C006F00200077006F0072006C00640021

Mais dans certaines situations, je reçois des messages texte normaux (pas hexadécimaux). Je dois donc faire un if hex control.

J'utilise Python 2.6.5.

MISE À JOUR:

La raison de ce problème est que (en quelque sorte) les messages que j'ai envoyés sont reçus en tant que hex tandis que les messages envoyés par l'opérateur (messages d'information et annonces.) Sont reçus en tant que chaîne normale. J'ai donc décidé de faire une vérification et de m'assurer que j'ai le message dans le bon format de chaîne.

Quelques détails supplémentaires : J'utilise un modem Huawei 3G et PyHumod pour lire les données de la carte SIM.

Meilleure solution possible à ma situation:

La meilleure façon de gérer de telles chaînes est d'utiliser a2b_hex (alias unhexlify) et utf-16 big endian encoding (comme @JonasWielicki l'a mentionné):

from binascii import unhexlify  # unhexlify is another name of a2b_hex

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

(1) L'utilisation de int () fonctionne bien pour cela, et Python fait tout la vérification pour vous :)

int('00480065006C006C006F00200077006F0072006C00640021', 16)
6896377547970387516320582441726837832153446723333914657L

marchera. En cas d'échec, vous recevrez une exception ValueError.

Petit exemple:

int('af', 16)
175

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

(2) Une alternative serait de parcourir les données et de s'assurer que tous les caractères se trouvent dans la plage de 0..9 Et a-f/A-F. string.hexdigits ('0123456789abcdefABCDEF') Est utile pour cela car il contient les deux majuscules et minuscules.

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

renverra soit True ou False selon la validité de vos données dans la chaîne s.

Petit exemple:

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

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

Notes:

Comme @ScottGriffiths le note correctement dans un commentaire ci-dessous, l'approche int() fonctionnera si votre chaîne contient 0x Au début, tandis que la vérification caractère par caractère échouera avec cela. En outre, la vérification par rapport à un set de caractères est plus rapide qu'une chaîne de caractères, mais il est douteux que cela importera avec les courts SMS chaînes, sauf si vous en traitez plusieurs (plusieurs!) dans l'ordre, auquel cas vous pouvez convertir stringhexditigs en un ensemble avec set(string.hexdigits).

65
Levon

Vous pouvez:

  1. tester si la chaîne ne contient que des chiffres hexadécimaux (0… 9, A… F)
  2. essayez de convertir la chaîne en entier et voyez si elle échoue.

Voici le code:

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
20
eumiro

Je connais l'op mentionné expressions régulières , mais je voulais apporter une telle solution par souci d'exhaustivité:

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

Performances

Afin d'évaluer les performances des différentes solutions proposées ici, j'ai utilisé le module timeit de Python. Les chaînes d'entrée sont générées de façon aléatoire pour trois longueurs différentes, 10, 100, 1000:

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

Levon's solutions:

# 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

D'autres réponses sont des variantes de ces deux. Utilisation d'une expression régulière:

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

Choisir la bonne solution dépend donc de la longueur de la chaîne d'entrée et de la possibilité de gérer les exceptions en toute sécurité. L'expression régulière gère certainement les grandes chaînes beaucoup plus rapidement (et ne lance pas un ValueError en cas de débordement), mais int() est le gagnant pour les chaînes plus courtes.

14
Jens

Une autre option:

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

La plupart des solutions proposées ci-dessus ne tiennent pas compte du fait que tout entier décimal peut également être décodé en hexadécimal car l'ensemble de chiffres décimaux est un sous-ensemble de chiffres hexadécimaux. Donc Python prendra volontiers 123 et supposons que c'est 0123 hex:

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

Cela peut sembler évident, mais dans la plupart des cas, vous rechercherez quelque chose qui était en fait codé en hexadécimal, par exemple un hachage et non rien qui peut être décodé en hexadécimal. Donc, probablement une solution plus robuste devrait également vérifier une longueur uniforme de la chaîne hexadécimale:

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
kravietz

Une autre solution simple et courte basée sur la transformation de la chaîne à définir et la vérification du sous-ensemble (ne vérifie pas le préfixe "0x"):

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

Plus d'informations ici .

2
Roman

Cela couvrira le cas si la chaîne commence par '0x' ou '0X': [0x | 0X] [0-9a-fA-F]

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

Étant donné que toutes les expressions régulières ci-dessus ont pris environ le même temps, je suppose que la plupart du temps était lié à la conversion de la chaîne en une expression régulière. Voici les données que j'ai obtenues lors de la pré-compilation de l'expression régulière.

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
0
Alan Robertson

En utilisant Python vous cherchez à déterminer Vrai ou Faux, j'utiliserais la méthode is_hex d'eumero sur la méthode un de Levon. Le code suivant contient un gotcha ...

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

Il signale de façon incorrecte la chaîne "00" comme pas hex car zéro est évalué à faux.

0
andrew pate

En Python3, j'ai essayé:

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

Ça devrait être mieux que la façon: int (x, 16)

0
Xb74Dkjb