web-dev-qa-db-fra.com

Traiter les séquences d'échappement d'une chaîne dans Python

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?

89
dln385

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.

118
Jerub

unicode_escape ne fonctionne pas en général

Il 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)

Ajout d'une expression régulière pour résoudre le problème

(É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
103
rspeer

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 à octets
  • codecs.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:

  • @ rspeer est correct: 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.
  • @ Jerub est correct: évitez le AST ou eval.
  • J'ai d'abord découvert 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.
25
user19087

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.

8
Greg Hewgill

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 ()?

2
LimeTr33

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)
1
Vignesh Ramsubbose