web-dev-qa-db-fra.com

Remplacer plusieurs caractères par Python

Je dois remplacer certains caractères comme suit: & -> \&, # -> \#, ...

J'ai codé comme suit, mais je suppose qu'il devrait y avoir un meilleur moyen. Des allusions?

strs = strs.replace('&', '\&')
strs = strs.replace('#', '\#')
...
141
prosseek

Remplacement de deux personnages

J'ai chronométré toutes les méthodes dans les réponses actuelles avec un supplémentaire.

Avec une chaîne d'entrée de abc&def#ghi et en remplaçant les caractères & ->\& et # -> #, le plus rapide était d'enchaîner les remplacements tels que: text.replace('&', '\&').replace('#', '\#').

Timings pour chaque fonction:

  • a) 1000000 boucles, meilleur de 3: 1,47 μs par boucle
  • b) 1000000 boucles, meilleur de 3: 1,51 μs par boucle
  • c) 100 000 boucles, au mieux 3: 12,3 μs par boucle
  • d) 100 000 boucles, au mieux 3: 12 μs par boucle
  • e) 100 000 boucles, au mieux 3: 3,27 μs par boucle
  • f) 1000000 boucles, meilleur de 3: 0,817 μs par boucle
  • g) 100 000 boucles, meilleur de 3: 3,64 μs par boucle
  • h) 1000000 boucles, meilleur de 3: 0,927 µs par boucle
  • i) 1000000 boucles, meilleur de 3: 0,814 µs par boucle

Voici les fonctions:

def a(text):
    chars = "&#"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['&','#']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([&#])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('&#')
def e(text):
    esc(text)


def f(text):
    text = text.replace('&', '\&').replace('#', '\#')


def g(text):
    replacements = {"&": "\&", "#": "\#"}
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('&', r'\&')
    text = text.replace('#', r'\#')


def i(text):
    text = text.replace('&', r'\&').replace('#', r'\#')

Chronométré comme ceci:

python -mtimeit -s"import time_functions" "time_functions.a('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.b('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.c('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.d('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.e('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.f('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.g('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.h('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.i('abc&def#ghi')"

Remplacement de 17 caractères

Voici un code similaire pour faire la même chose mais avec plus de caractères à échapper (\ `* _ {}> # + -.! $):

def a(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([\\`*_{}[]()>#+-.!$])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('\\`*_{}[]()>#+-.!$')
def e(text):
    esc(text)


def f(text):
    text = text.replace('\\', '\\\\').replace('`', '\`').replace('*', '\*').replace('_', '\_').replace('{', '\{').replace('}', '\}').replace('[', '\[').replace(']', '\]').replace('(', '\(').replace(')', '\)').replace('>', '\>').replace('#', '\#').replace('+', '\+').replace('-', '\-').replace('.', '\.').replace('!', '\!').replace('$', '\$')


def g(text):
    replacements = {
        "\\": "\\\\",
        "`": "\`",
        "*": "\*",
        "_": "\_",
        "{": "\{",
        "}": "\}",
        "[": "\[",
        "]": "\]",
        "(": "\(",
        ")": "\)",
        ">": "\>",
        "#": "\#",
        "+": "\+",
        "-": "\-",
        ".": "\.",
        "!": "\!",
        "$": "\$",
    }
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('\\', r'\\')
    text = text.replace('`', r'\`')
    text = text.replace('*', r'\*')
    text = text.replace('_', r'\_')
    text = text.replace('{', r'\{')
    text = text.replace('}', r'\}')
    text = text.replace('[', r'\[')
    text = text.replace(']', r'\]')
    text = text.replace('(', r'\(')
    text = text.replace(')', r'\)')
    text = text.replace('>', r'\>')
    text = text.replace('#', r'\#')
    text = text.replace('+', r'\+')
    text = text.replace('-', r'\-')
    text = text.replace('.', r'\.')
    text = text.replace('!', r'\!')
    text = text.replace('$', r'\$')


def i(text):
    text = text.replace('\\', r'\\').replace('`', r'\`').replace('*', r'\*').replace('_', r'\_').replace('{', r'\{').replace('}', r'\}').replace('[', r'\[').replace(']', r'\]').replace('(', r'\(').replace(')', r'\)').replace('>', r'\>').replace('#', r'\#').replace('+', r'\+').replace('-', r'\-').replace('.', r'\.').replace('!', r'\!').replace('$', r'\$')

Voici les résultats pour la même chaîne d'entrée abc&def#ghi:

  • a) 100 000 boucles, au mieux 3: 6,72 μs par boucle
  • b) 100000 boucles, meilleur de 3: 2,64 μs par boucle
  • c) 100 000 boucles, au mieux 3: 11,9 μs par boucle
  • d) 100 000 boucles, au mieux de 3: 4,92 µs par boucle
  • e) 100000 boucles, meilleur de 3: 2,96 µs par boucle
  • f) 100 000 boucles, meilleur de 3: 4,29 μs par boucle
  • g) 100 000 boucles, meilleur de 3: 4,68 µs par boucle
  • h) 100 000 boucles, meilleure des 3: 4,73 μs par boucle
  • i) 100 000 boucles, meilleur de 3: 4,24 μs par boucle

Et avec une chaîne de saisie plus longue (## *Something* and [another] thing in a longer sentence with {more} things to replace$):

  • a) 100 000 boucles, au mieux 3: 7,59 μs par boucle
  • b) 100 000 boucles, au mieux de 3: 6,54 μs par boucle
  • c) 100 000 boucles, le meilleur des 3: 16,9 μs par boucle
  • d) 100 000 boucles, meilleure des 3: 7,29 μs par boucle
  • e) 100 000 boucles, meilleur de 3: 12,2 μs par boucle
  • f) 100000 boucles, meilleur de 3: 5,38 µs par boucle
  • g) 10000 boucles, meilleur de 3: 21,7 μs par boucle
  • h) 100000 boucles, meilleur de 3: 5,7 μs par boucle
  • i) 100000 boucles, meilleur de 3: 5.13 μs par boucle

Ajout de quelques variantes:

def ab(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        text = text.replace(ch,"\\"+ch)


def ba(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        if c in text:
            text = text.replace(c, "\\" + c)

Avec l'entrée la plus courte:

  • ab) 100 000 boucles, meilleur de 3: 7,05 μs par boucle
  • ba) 100000 boucles, meilleur de 3: 2,4 μs par boucle

Avec l'entrée la plus longue:

  • ab) 100 000 boucles, meilleur de 3: 7,71 μs par boucle
  • ba) 100 000 boucles, meilleur de 3: 6,08 μs par boucle

Je vais donc utiliser ba pour la lisibilité et la rapidité.

Addenda

Invité par les haccks dans les commentaires, une différence entre ab et ba est le contrôle if c in text:. Testons-les contre deux autres variantes:

def ab_with_check(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)

def ba_without_check(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)

Les temps exprimés en μs par boucle sur Python 2.7.14 et 3.6.3, ainsi que sur un ordinateur différent de celui du jeu précédent, ne peuvent donc pas être comparés directement.

╭────────────╥──────┬───────────────┬──────┬──────────────────╮
│ Py, input  ║  ab  │ ab_with_check │  ba  │ ba_without_check │
╞════════════╬══════╪═══════════════╪══════╪══════════════════╡
│ Py2, short ║ 8.81 │    4.22       │ 3.45 │    8.01          │
│ Py3, short ║ 5.54 │    1.34       │ 1.46 │    5.34          │
├────────────╫──────┼───────────────┼──────┼──────────────────┤
│ Py2, long  ║ 9.3  │    7.15       │ 6.85 │    8.55          │
│ Py3, long  ║ 7.43 │    4.38       │ 4.41 │    7.02          │
└────────────╨──────┴───────────────┴──────┴──────────────────┘

Nous pouvons conclure que:

  • Ceux avec le chèque sont jusqu'à 4x plus rapide que ceux sans le chèque

  • ab_with_check est légèrement en avance sur Python 3, mais ba (avec contrôle) a une avance plus grande sur Python 2

  • Cependant, la plus grande leçon ici est Python 3 est jusqu'à 3 fois plus rapide que Python 2! Il n'y a pas une énorme différence entre le plus lent sur Python 3 et le plus rapide sur Python 2!

326
Hugo
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi
71
ghostdog74

Chaînez simplement les fonctions replace comme ceci

strs = "abc&def#ghi"
print strs.replace('&', '\&').replace('#', '\#')
# abc\&def\#ghi

Si les remplacements vont être plus nombreux, vous pouvez le faire de cette manière générique

strs, replacements = "abc&def#ghi", {"&": "\&", "#": "\#"}
print "".join([replacements.get(c, c) for c in strs])
# abc\&def\#ghi
27
thefourtheye

Voici une méthode python3 utilisant str.translate et str.maketrans :

_s = "abc&def#ghi"
print(s.translate(str.maketrans({'&': '\&', '#': '\#'})))
_

La chaîne imprimée est _abc\&def\#ghi_.

15
tommy.carstensen

Allez-vous toujours prévoir une barre oblique inverse? Si oui, essayez

import re
rx = re.compile('([&#])')
#                  ^^ fill in the characters here.
strs = rx.sub('\\\\\\1', strs)

Ce n'est peut-être pas la méthode la plus efficace, mais je pense que c'est la plus simple.

14
kennytm

Vous pouvez envisager d'écrire une fonction d'échappement générique:

def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])

>>> esc = mk_esc('&#')
>>> print esc('Learn & be #1')
Learn \& be \#1

De cette façon, vous pouvez configurer votre fonction avec une liste de caractères à échapper.

6
Victor Olex

En retard à la fête, mais j'ai perdu beaucoup de temps avec cette question jusqu'à ce que j'ai trouvé ma réponse.

Court et doux, translate est supérieur à replace. Si vous êtes plus intéressé par l'optimisation de la fonctionnalité dans le temps, n'utilisez pas replace.

Utilisez également translate si vous ne savez pas si le jeu de caractères à remplacer chevauche le jeu de caractères à remplacer.

Exemple:

En utilisant replace, vous vous attendriez naïvement à ce que l'extrait de code "1234".replace("1", "2").replace("2", "3").replace("3", "4") renvoie "2344", mais il renverra en fait "4444".

La traduction semble réaliser ce que l’opérateur avait initialement souhaité.

4
Sebastialonso

Pour votre information, cela n’a que peu ou pas d’utilisation pour le PO mais peut être utile pour les autres lecteurs (veuillez ne pas abaisser le vote, je suis au courant de cela).

Comme exercice un peu ridicule mais intéressant, je voulais voir si je pouvais utiliser la programmation fonctionnelle python pour remplacer plusieurs caractères. Je suis sûr que cela ne bat PAS d'appeler simplement en remplaçant () deux fois. Et si les performances étaient un problème, vous pourriez facilement le battre avec Rust, C, Julia, Perl, Java, javascript et peut-être même awk. Il utilise un paquet 'helpers' externe appelé pytoolz , accéléré via cython ( cytoolz, c'est un paquet pypi ).

from cytoolz.functoolz import compose
from cytoolz.itertoolz import chain,sliding_window
from itertools import starmap,imap,ifilter
from operator import itemgetter,contains
text='&hello#hi&yo&'
char_index_iter=compose(partial(imap, itemgetter(0)), partial(ifilter, compose(partial(contains, '#&'), itemgetter(1))), enumerate)
print '\\'.join(imap(text.__getitem__, starmap(slice, sliding_window(2, chain((0,), char_index_iter(text), (len(text),))))))

Je ne vais même pas expliquer cela parce que personne ne se soucierait de l'utiliser pour effectuer plusieurs remplacements. Néanmoins, je me suis senti un peu accompli en faisant cela et j'ai pensé que cela pourrait inspirer d’autres lecteurs ou gagner un concours d’obfuscation de code.

3
parity3

En utilisant une réduction qui est disponible dans python2.7 et python3. *, Vous pouvez facilement remplacer plusieurs sous-chaînes de manière propre et pythonique.

# Lets define a helper method to make it easy to use
def replacer(text, replacements):
    return reduce(
        lambda text, ptuple: text.replace(ptuple[0], ptuple[1]), 
        replacements, text
    )

if __== '__main__':
    uncleaned_str = "abc&def#ghi"
    cleaned_str = replacer(uncleaned_str, [("&","\&"),("#","\#")])
    print(cleaned_str) # "abc\&def\#ghi"

En python2.7, vous n'avez pas besoin d'importer réduire, mais en python3. *, Vous devez l'importer à partir du module functools.

1
CasualCoder3
>>> a = '&#'
>>> print a.replace('&', r'\&')
\&#
>>> print a.replace('#', r'\#')
&\#
>>> 

Vous voulez utiliser une chaîne 'brute' (indiquée par le préfixe 'r' qui préfixe la chaîne de remplacement), car les chaînes brutes ne traitent pas spécialement la barre oblique inversée.

0
jonesy