Parfois, lorsque je reçois une entrée d'un fichier ou de l'utilisateur, je reçois une chaîne contenant 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, supposons que myString
soit défini comme suit:
>>> myString = "spam\\neggs"
>>> print(myString)
spam\neggs
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 dans Python (répertorié dans un tableau dans le lien ci-dessus).
Est-ce que Python a une fonction pour le faire?
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 le AST ou eval. L'utilisation des codecs de chaîne est beaucoup plus sûre.
unicode_escape
ne fonctionne pas en généralIl s'avère que le string_escape
ou unicode_escape
La solution ne fonctionne pas en général - en particulier, elle ne fonctionne pas en présence de Unicode.
Si vous pouvez être sûr que chaque caractère non-ASCII sera échappé (et rappelez-vous, tout élément 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 mal.
unicode_escape
est fondamentalement conçu pour convertir des octets en texte Unicode. Mais dans de nombreux endroits - par exemple, Python code source) - les données source sont déjà du texte Unicode.
Cela ne peut fonctionner correctement que si vous encodez d'abord le texte en octets. UTF-8 est l'encodage judicieux de tout le texte. Cela devrait donc fonctionner, n'est-ce pas?
Les exemples suivants sont dans Python 3, de sorte que les littéraux de chaîne sont plus propres, mais le même problème existe avec des manifestations légèrement différentes à la fois 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 méthode recommandée pour utiliser les codecs qui décodent du texte en texte consiste à appeler codecs.decode
directement. Est ce que ça aide?
>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve test
Pas du tout. (En outre, ce qui précède est une erreur UnicodeError sur Python 2.)
Le unicode_escape
_ codec, malgré son nom, suppose que tous les octets non-ASCII sont au 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 Latin-1, comme si Unicode n'avait jamais été inventé!
>>> 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)
(Étonnamment, nous n’avons pas maintenant deux problèmes.)
Ce que nous devons faire est d’appliquer uniquement le unicode_escape
décodeur à des choses dont nous sommes certains ASCII text. En particulier, nous pouvons être sûrs de ne l'appliquer qu'à des séquences d'échappement Python valides, qui sont garantis être ASCII text.
Le plan est, nous allons trouver des séquences d'échappement en 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 cela:
>>> 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
Détails concernant codecs.escape_decode
:
codecs.escape_decode
est un décodeur d'octets à octetscodecs.escape_decode
_ décode les séquences d'échappement ascii, telles que: b"\\n"
-> b"\n"
, b"\\xce"
-> b"\xce"
.codecs.escape_decode
_ ne se soucie pas ou n'a pas besoin de connaître le codage de l'objet octet, mais le codage des octets échappés doit correspondre à celui du reste de l'objet.Contexte:
unicode_escape
est la solution incorrecte pour python3. Ceci est dû au fait 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.codecs.escape_decode
de cette réponse à "comment puis-je codec ('string-escape') en Python3?" . Comme l'indique cette réponse, cette fonction n'est actuellement pas documentée pour python 3.Le ast.literal_eval
la fonction se rapproche, mais on s'attendra à ce que la chaîne soit correctement citée en premier.
Bien sûr, l'interprétation par Python des échappements de barre oblique inverse dépend de la façon dont la chaîne est citée (""
contre r""
contre u""
, triples guillemets, etc.) afin que vous souhaitiez envelopper les entrées de l'utilisateur entre guillemets appropriés et les transmettre à literal_eval
. Le mettre entre guillemets évitera également literal_eval
de renvoyer un numéro, un tuple, un dictionnaire, etc.
Les choses peuvent encore devenir délicates si l’utilisateur tape des guillemets du type que vous voulez enrouler autour de la chaîne.
C'est une mauvaise façon de le faire, mais cela a fonctionné pour moi lorsque j'essayais d'interpréter les octaux échappés transmis dans un argument de chaîne.
input_string = eval('b"' + sys.argv[1] + '"')
Il convient de mentionner qu’il existe une différence entre eval et ast.literal_eval (eval étant bien plus dangereux). Voir tilisation de eval () de python vs ast.literal_eval ()?
Le code ci-dessous devrait fonctionner car il est nécessaire que\n soit 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)