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!'
(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)
.
Vous pouvez:
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
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.
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
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
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 .
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
É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
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.
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)