Traiter les séquences d'échappement dans une chaîne en Python
Parfois, lorsque je reçois une entrée d'un fichier ou de l'utilisateur, j'obtiens une chaîne avec des séquences d'échappement. Je voudrais traiter les séquences d'échappement de la même manière que Python traite les séquences d'échappement dans les littéraux de chaîne .
Par exemple, disons que {[2] } est défini comme:
>>> myString = "spam\neggs"
>>> print(myString)
spamneggs
Je veux une fonction (je l'appellerai process
) qui fait ceci:
>>> print(process(myString))
spam
eggs
Il est important que la fonction puisse traiter toutes les séquences d'échappement en Python (listées dans une table dans le lien surtout).
Python a-t-il une fonction pour le faire?
7 réponses
La bonne chose à faire est d'utiliser le code' string-escape ' pour décoder la chaîne.
>>> myString = "spam\\neggs"
>>> decoded_string = bytes(myString, "utf-8").decode("unicode_escape") # python3
>>> decoded_string = myString.decode('string_escape') # python2
>>> print(decoded_string)
spam
eggs
N'utilisez pas L'AST ou l'eval. L'utilisation des codecs string est beaucoup plus sûre.
unicode_escape
ne fonctionne pas en général
Il s'avère que la solution string_escape
ou unicode_escape
ne fonctionne pas en général-en particulier, elle ne fonctionne pas en présence D'Unicode réel.
Si vous pouvez être sûr que chaque caractère non-ASCII sera échappé (et rappelez-vous, tout ce qui est au-delà des 128 premiers caractères est non-ASCII), unicode_escape
fera la bonne chose pour vous. Mais s'il y a déjà des caractères littéraux non-ASCII dans votre chaîne, les choses iront Faux.
unicode_escape
est fondamentalement conçu pour convertir des octets en texte Unicode. Mais dans de nombreux endroits - par exemple, le code source Python-les données source sont déjà du texte Unicode.
La seule façon dont cela peut fonctionner correctement est si vous encodez le texte en octets en premier. UTF-8 est l'encodage sensible pour tout le texte, donc cela devrait fonctionner, non?
Les exemples suivants sont en Python 3, de sorte que les littéraux de chaîne sont plus propres, mais le même problème existe avec légèrement différent manifestations sur Python 2 et 3.
>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve test
Eh Bien, c'est faux.
La nouvelle façon recommandée d'utiliser les codecs qui décodent le texte en texte est d'appeler codecs.decode
directement. Cela vous aide?
>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve test
Pas du tout. (En outre, ce qui précède est une UnicodeError sur Python 2.)
Le codec unicode_escape
, malgré son nom, suppose que tous les octets non-ASCII sont dans le codage Latin-1 (ISO-8859-1). Donc, vous devriez le faire comme ceci:
>>> print(s.encode('latin-1').decode('unicode_escape'))
naïve test
Mais c'est terrible. Cela vous limite aux 256 caractères latins-1, comme si Unicode n'avait jamais été inventé du tout!
>>> print('Ernő \\t Rubik'.encode('latin-1').decode('unicode_escape'))
UnicodeEncodeError: 'latin-1' codec can't encode character '\u0151'
in position 3: ordinal not in range(256)
Ajout d'une expression régulière pour résoudre le problème
(Étonnamment, nous n'avons deux problèmes.)
Ce que nous devons faire est seulement d'appliquer le décodeur unicode_escape
à des choses que nous sommes certains d'être du texte ASCII. En particulier, nous pouvons nous assurer de l'appliquer uniquement aux séquences d'échappement Python valides, qui sont garanties comme du texte ASCII.
Le plan est, nous trouverons séquences d'échappement utilisant une expression régulière, et utiliser une fonction comme argument de re.sub
pour les remplacer par leur valeur non échappée.
import re
import codecs
ESCAPE_SEQUENCE_RE = re.compile(r'''
( \\U........ # 8-digit hex escapes
| \\u.... # 4-digit hex escapes
| \\x.. # 2-digit hex escapes
| \\[0-7]{1,3} # Octal escapes
| \\N\{[^}]+\} # Unicode characters by name
| \\[\\'"abfnrtv] # Single-character escapes
)''', re.UNICODE | re.VERBOSE)
def decode_escapes(s):
def decode_match(match):
return codecs.decode(match.group(0), 'unicode-escape')
return ESCAPE_SEQUENCE_RE.sub(decode_match, s)
Et avec ça:
>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő Rubik
La réponse réellement correcte et pratique pour python 3:
>>> import codecs
>>> myString = "spam\\neggs"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
spam
eggs
>>> myString = "naïve \\t test"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
naïve test
Les Détails concernant codecs.escape_decode
:
- {[1] } est un décodeur d'octets à octets
-
codecs.escape_decode
décode ascii des séquences d'échappement, tels que:b"\\n"
->b"\n"
,b"\\xce"
->b"\xce"
. -
codecs.escape_decode
ne se soucie pas ou n'a pas besoin de connaître l'encodage de l'objet byte, mais l'encodage des octets échappés doit correspondre à l'encodage du reste de la objet.
Contexte:
-
@rspeer est correct:
unicode_escape
est la solution incorrecte pour python3. En effet,unicode_escape
décode les octets échappés, puis décode les octets en chaîne unicode, mais ne reçoit aucune information concernant le codec à utiliser pour la deuxième opération. - @ Jerub est correct: évitez L'AST ou l'eval.
- j'ai d'abord découvert
codecs.escape_decode
de cette réponse à " comment puis-je .décoder ('string-escape') en Python3?". En tant que la réponse indique que cette fonction n'est actuellement pas documentée pour python 3.
Le ast.literal_eval
la fonction se rapproche, mais elle s'attend à ce que la chaîne soit correctement Citée en premier.
Bien sûr, l'interprétation de python des échappements antislash dépend de la façon dont la chaîne est entre guillemets (""
vs r""
vs u""
, guillemets triples, etc.), donc vous pouvez envelopper l'entrée de l'utilisateur dans des guillemets appropriés et passer à literal_eval
. L'envelopper entre guillemets empêchera également literal_eval
de renvoyer un nombre, un tuple, un dictionnaire, etc.
Les choses pourraient encore devenir difficiles si l'utilisateur tape unquoted guillemets du type que vous avez l'intention d'enrouler autour de la chaîne.
La réponse de Rspeer souligne correctement que unicode-escape
implique un décodage implicite en utilisant latin-1
, mais ne le suit pas. Si unicode-escape
décode correctement les échappements, mais gère incorrectement les octets bruts non-ASCII en les décodant comme latin-1
, alors la solution simple n'est pas d'impliquer l'expression régulière, mais de les réencoder comme latin-1
par la suite( pour annuler la partie erronée du processus), puis de décoder dans le codage correct. Par exemple, l'exemple de mauvaise utilisation de:
>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve test
Peut être rendu trivialement correct en ajoutant .encode('latin-1').decode('utf-8')
, ce qui le rend:
>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape').encode('latin-1').decode('utf-8'))
naïve test
# Or using codecs.decode to replace the first encode/decode pair with a single text->text transform:
>>> print(codecs.decode(s, 'unicode_escape').encode('latin-1').decode('utf-8'))
naïve test
Certes, c'est beaucoup de va - et-vient, et je ne voudrais pas vraiment l'intégrer dans mon code, mais il peut être pris en compte dans une fonction autonome qui fonctionne à la fois pour str
et bytes
(avec une étape de décodage facultative pour le bytes
si le résultat est dans un encodage connu):
def decode_escapes(s, encoding=None):
if isinstance(s, str):
if encoding is not None:
return TypeError("Do not pass encoding for string arguments")
# UTF-8 will allow correct interpretation of escapes when bytes form
# interpreted as latin-1
s = s.encode('utf-8')
encoding = 'utf-8'
decoded = s.decode('unicode_escape').encode('latin-1')
if encoding is not None:
# If encoding is provided, or we started with an arbitrary string, decode
decoded = decode.decode(encoding)
return decoded
Le code ci-dessous devrait fonctionner car \n doit être affiché sur la chaîne.
import string
our_str = 'The String is \\n, \\n and \\n!'
new_str = string.replace(our_str, '/\\n', '/\n', 1)
print(new_str)
Si vous faites confiance à la source des données, il suffit de taper des guillemets autour d'elle et eval () il?
>>> myString = 'spam\\neggs'
>>> print eval('"' + myString.replace('"','') + '"')
spam
eggs
PS. ajout de la contre-mesure evil-code-exec - maintenant, il va supprimer tout "
avant d'eval-ing