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?

76
demandé sur dln385 2010-10-26 07:43:56

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.

108
répondu Jerub 2010-10-26 06:29:28

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
78
répondu rspeer 2014-07-01 22:09:03

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.
13
répondu user19087 2017-05-23 12:02:45

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.

6
répondu Greg Hewgill 2010-10-26 03:50:06

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
1
répondu ShadowRanger 2018-08-18 02:46:14

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)
0
répondu Vignesh Ramsubbose 2018-03-26 09:42:50

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

-4
répondu Nas Banov 2010-10-26 05:10:03