web-dev-qa-db-fra.com

Comment remplacer plusieurs sous-chaînes d'une chaîne?

Je voudrais utiliser la fonction .replace pour remplacer plusieurs chaînes.

J'ai actuellement

string.replace("condition1", "")

mais aimerait avoir quelque chose comme

string.replace("condition1", "").replace("condition2", "text")

bien que cela ne semble pas être une bonne syntaxe

quelle est la bonne façon de faire cela? un peu comme dans grep/regex vous pouvez utiliser \1 et \2 pour remplacer les champs de certaines chaînes de recherche

235
CQM

Voici un court exemple qui devrait faire l'affaire avec des expressions régulières:

import re

rep = {"condition1": "", "condition2": "text"} # define desired replacements here

# use these three lines to do the replacement
rep = dict((re.escape(k), v) for k, v in rep.iteritems()) 
#Python 3 renamed dict.iteritems to dict.items so use rep.items() for latest versions
pattern = re.compile("|".join(rep.keys()))
text = pattern.sub(lambda m: rep[re.escape(m.group(0))], text)

Par exemple:

>>> pattern.sub(lambda m: rep[re.escape(m.group(0))], "(condition1) and --condition2--")
'() and --text--'
234
Andrew Clark

Vous pouvez juste faire une jolie petite fonction en boucle.

def replace_all(text, dic):
    for i, j in dic.iteritems():
        text = text.replace(i, j)
    return text

text est la chaîne complète et dic est un dictionnaire - chaque définition est une chaîne qui remplacera une correspondance avec le terme.

Note: dans Python 3, iteritems() a été remplacé par items()


Attention: Python les dictionnaires n'ont pas d'ordre fiable pour les itérations. Cette solution ne résout votre problème que si:

  • l'ordre de remplacement est sans importance
  • il est correct pour un remplaçant de changer les résultats des remplacements précédents

Par exemple:

d = { "cat": "dog", "dog": "pig"}
mySentence = "This is my cat and this is my dog."
replace_all(mySentence, d)
print(mySentence)

Sortie possible n ° 1:

"Ceci est mon cochon et c'est mon cochon."

Sortie possible n ° 2

"Ceci est mon chien et ceci est mon cochon."

Une solution possible consiste à utiliser un OrderedDict.

from collections import OrderedDict
def replace_all(text, dic):
    for i, j in dic.items():
        text = text.replace(i, j)
    return text
od = OrderedDict([("cat", "dog"), ("dog", "pig")])
mySentence = "This is my cat and this is my dog."
replace_all(mySentence, od)
print(mySentence)

Sortie:

"This is my pig and this is my pig."

Attention # 2: Inefficace si votre chaîne text est trop grosse ou s'il y a beaucoup de paires dans le dictionnaire.

108
Joseph Hansen

Voici une variante de la première solution utilisant réduire, au cas où vous aimeriez être fonctionnel. :)

repls = {'hello' : 'goodbye', 'world' : 'earth'}
s = 'hello, world'
reduce(lambda a, kv: a.replace(*kv), repls.iteritems(), s)

la version encore meilleure de martineau:

repls = ('hello', 'goodbye'), ('world', 'earth')
s = 'hello, world'
reduce(lambda a, kv: a.replace(*kv), repls, s)
83
Björn Lindqvist

Pourquoi pas une solution comme celle-ci?

s = "The quick brown fox jumps over the lazy dog"
for r in (("brown", "red"), ("lazy", "quick")):
    s = s.replace(*r)

#output will be:  The quick red fox jumps over the quick dog
73
Enrico Bianchi

Ceci est juste une récapitulation plus concise des excellentes réponses de F.J et MiniQuark. Tout ce dont vous avez besoin pour obtenir plusieurs remplacements de chaîne simultanés est la fonction suivante:

def multiple_replace(string, rep_dict):
    pattern = re.compile("|".join([re.escape(k) for k in sorted(rep_dict,key=len,reverse=True)]), flags=re.DOTALL)
    return pattern.sub(lambda x: rep_dict[x.group(0)], string)

Usage:

>>>multiple_replace("Do you like cafe? No, I prefer tea.", {'cafe':'tea', 'tea':'cafe', 'like':'prefer'})
'Do you prefer tea? No, I prefer cafe.'

Si vous le souhaitez, vous pouvez créer vos propres fonctions de remplacement dédiées à partir de cette fonction plus simple.

33
mmj

J'ai construit ceci sur l'excellente réponse de F.J.s:

import re

def multiple_replacer(*key_values):
    replace_dict = dict(key_values)
    replacement_function = lambda match: replace_dict[match.group(0)]
    pattern = re.compile("|".join([re.escape(k) for k, v in key_values]), re.M)
    return lambda string: pattern.sub(replacement_function, string)

def multiple_replace(string, *key_values):
    return multiple_replacer(*key_values)(string)

Un coup d'utilisation:

>>> replacements = (u"café", u"tea"), (u"tea", u"café"), (u"like", u"love")
>>> print multiple_replace(u"Do you like café? No, I prefer tea.", *replacements)
Do you love tea? No, I prefer café.

Notez que puisque le remplacement est effectué en un seul passage, "café" devient "thé" mais ne redevient pas "café".

Si vous devez effectuer plusieurs fois le même remplacement, vous pouvez facilement créer une fonction de remplacement:

>>> my_escaper = multiple_replacer(('"','\\"'), ('\t', '\\t'))
>>> many_many_strings = (u'This text will be escaped by "my_escaper"',
                       u'Does this work?\tYes it does',
                       u'And can we span\nmultiple lines?\t"Yes\twe\tcan!"')
>>> for line in many_many_strings:
...     print my_escaper(line)
... 
This text will be escaped by \"my_escaper\"
Does this work?\tYes it does
And can we span
multiple lines?\t\"Yes\twe\tcan!\"

Améliorations:

  • transformé le code en une fonction
  • support multiligne ajouté
  • correction d'un bug dans l'échappement
  • facile à créer une fonction pour un remplacement multiple spécifique

Enjoy!: -)

28
MiniQuark

Je voudrais proposer l'utilisation de modèles de chaîne. Il suffit de placer la chaîne à remplacer dans un dictionnaire et tout est défini! Exemple de docs.python.org

>>> from string import Template
>>> s = Template('$who likes $what')
>>> s.substitute(who='tim', what='kung pao')
'tim likes kung pao'
>>> d = dict(who='tim')
>>> Template('Give $who $100').substitute(d)
Traceback (most recent call last):
[...]
ValueError: Invalid placeholder in string: line 1, col 10
>>> Template('$who likes $what').substitute(d)
Traceback (most recent call last):
[...]
KeyError: 'what'
>>> Template('$who likes $what').safe_substitute(d)
'tim likes $what'
20
Fredrik Pihl

Dans mon cas, j'avais besoin d'un simple remplacement de clés uniques avec des noms, alors j'ai pensé à cela:

a = 'This is a test string.'
b = {'i': 'I', 's': 'S'}
for x,y in b.items():
    a = a.replace(x, y)
>>> a
'ThIS IS a teSt StrIng.'
12
James Koss

Voici mon 0.02 $. Il est basé sur la réponse d'Andrew Clark, un peu plus claire, et couvre également le cas où une chaîne à remplacer est une sous-chaîne d'une autre chaîne à remplacer (plus longue chaîne gagne)

def multireplace(string, replacements):
    """
    Given a string and a replacement map, it returns the replaced string.

    :param str string: string to execute replacements on
    :param dict replacements: replacement dictionary {value to find: value to replace}
    :rtype: str

    """
    # Place longer ones first to keep shorter substrings from matching
    # where the longer ones should take place
    # For instance given the replacements {'ab': 'AB', 'abc': 'ABC'} against 
    # the string 'hey abc', it should produce 'hey ABC' and not 'hey ABc'
    substrs = sorted(replacements, key=len, reverse=True)

    # Create a big OR regex that matches any of the substrings to replace
    regexp = re.compile('|'.join(map(re.escape, substrs)))

    # For each match, look up the new string in the replacements
    return regexp.sub(lambda match: replacements[match.group(0)], string)

C'est dans ce cet Gist , n'hésitez pas à le modifier si vous avez une proposition.

9
bgusach

À partir de Python 3.8 et de l'introduction de expressions d'affectation (PEP 572) (opérateur :=), nous pouvons appliquer les remplacements dans une liste de compréhension:

# text = "The quick brown fox jumps over the lazy dog"
# replacements = [("brown", "red"), ("lazy", "quick")]
[text := text.replace(a, b) for a, b in replacements]
# text = 'The quick red fox jumps over the quick dog'
5
Xavier Guihot

J'avais besoin d'une solution où les chaînes à remplacer peuvent être des expressions régulières, par exemple pour aider à normaliser un texte long en remplaçant plusieurs caractères d'espacement par un seul. En me basant sur une chaîne de réponses d’autres personnes, notamment MiniQuark et mmj, voici ce que j’ai trouvé:

def multiple_replace(string, reps, re_flags = 0):
    """ Transforms string, replacing keys from re_str_dict with values.
    reps: dictionary, or list of key-value pairs (to enforce ordering;
          earlier items have higher priority).
          Keys are used as regular expressions.
    re_flags: interpretation of regular expressions, such as re.DOTALL
    """
    if isinstance(reps, dict):
        reps = reps.items()
    pattern = re.compile("|".join("(?P<_%d>%s)" % (i, re_str[0])
                                  for i, re_str in enumerate(reps)),
                         re_flags)
    return pattern.sub(lambda x: reps[int(x.lastgroup[1:])][1], string)

Cela fonctionne pour les exemples donnés dans d'autres réponses, par exemple:

>>> multiple_replace("(condition1) and --condition2--",
...                  {"condition1": "", "condition2": "text"})
'() and --text--'

>>> multiple_replace('hello, world', {'hello' : 'goodbye', 'world' : 'earth'})
'goodbye, earth'

>>> multiple_replace("Do you like cafe? No, I prefer tea.",
...                  {'cafe': 'tea', 'tea': 'cafe', 'like': 'prefer'})
'Do you prefer tea? No, I prefer cafe.'

Pour moi, l'essentiel est que vous puissiez également utiliser des expressions régulières, par exemple pour remplacer des mots entiers ou pour normaliser des espaces:

>>> s = "I don't want to change this name:\n  Philip II of Spain"
>>> re_str_dict = {r'\bI\b': 'You', r'[\n\t ]+': ' '}
>>> multiple_replace(s, re_str_dict)
"You don't want to change this name: Philip II of Spain"

Si vous souhaitez utiliser les clés du dictionnaire comme des chaînes normales, vous pouvez les échapper avant d'appeler multiple_replace, par ex. cette fonction:

def escape_keys(d):
    """ transform dictionary d by applying re.escape to the keys """
    return dict((re.escape(k), v) for k, v in d.items())

>>> multiple_replace(s, escape_keys(re_str_dict))
"I don't want to change this name:\n  Philip II of Spain"

La fonction suivante peut aider à trouver des expressions rationnelles erronées parmi les clés de votre dictionnaire (car le message d'erreur de multiple_replace n'est pas très révélateur):

def check_re_list(re_list):
    """ Checks if each regular expression in list is well-formed. """
    for i, e in enumerate(re_list):
        try:
            re.compile(e)
        except (TypeError, re.error):
            print("Invalid regular expression string "
                  "at position {}: '{}'".format(i, e))

>>> check_re_list(re_str_dict.keys())

Notez que les remplacements ne sont pas enchaînés, mais exécutés simultanément. Cela le rend plus efficace sans limiter ce qu'il peut faire. Pour reproduire l’effet de l’enchaînement, il vous suffira d’ajouter davantage de paires de remplacement de chaîne et d’assurer l’ordre prévu des paires:

>>> multiple_replace("button", {"but": "mut", "mutton": "lamb"})
'mutton'
>>> multiple_replace("button", [("button", "lamb"),
...                             ("but", "mut"), ("mutton", "lamb")])
'lamb'
4
user2443147

Voici un exemple qui est plus efficace sur les longues chaînes avec beaucoup de petits remplacements.

source = "Here is foo, it does moo!"

replacements = {
    'is': 'was', # replace 'is' with 'was'
    'does': 'did',
    '!': '?'
}

def replace(source, replacements):
    Finder = re.compile("|".join(re.escape(k) for k in replacements.keys())) # matches every string we want replaced
    result = []
    pos = 0
    while True:
        match = Finder.search(source, pos)
        if match:
            # cut off the part up until match
            result.append(source[pos : match.start()])
            # cut off the matched part and replace it in place
            result.append(replacements[source[match.start() : match.end()]])
            pos = match.end()
        else:
            # the rest after the last match
            result.append(source[pos:])
            break
    return "".join(result)

print replace(source, replacements)

Le but est d'éviter de nombreuses concaténations de longues chaînes. Nous découpons la chaîne source en fragments, en remplaçant certains des fragments lors de la constitution de la liste, puis réunions le tout dans une chaîne.

2
9000

Je ne connais pas la vitesse, mais voici ma solution miracle pour le travail:

reduce(lambda a, b: a.replace(*b)
    , [('o','W'), ('t','X')] #iterable of pairs: (oldval, newval)
    , 'tomato' #The string from which to replace values
    )

... mais j'aime la réponse n ° 1 regex ci-dessus. Remarque - Si une nouvelle valeur est une sous-chaîne d'une autre, l'opération n'est pas commutative.

1
del_hol

Vous ne devriez vraiment pas le faire de cette façon, mais je trouve ça trop cool:

>>> replacements = {'cond1':'text1', 'cond2':'text2'}
>>> cmd = 'answer = s'
>>> for k,v in replacements.iteritems():
>>>     cmd += ".replace(%s, %s)" %(k,v)
>>> exec(cmd)

Maintenant, answer est le résultat de tous les remplacements

encore une fois, c’est très hacky et ce n’est pas quelque chose que vous devriez utiliser régulièrement. Mais il est bon de savoir que vous pouvez faire quelque chose comme ceci si vous en avez besoin.

0
inspectorG4dget

Vous pouvez utiliser la bibliothèque pandas et la fonction replace qui prennent en charge les correspondances exactes ainsi que les remplacements de regex. Par exemple:

df = pd.DataFrame({'text': ['Billy is going to visit Rome in November', 'I was born in 10/10/2010', 'I will be there at 20:00']})

to_replace=['Billy','Rome','January|February|March|April|May|June|July|August|September|October|November|December', '\d{2}:\d{2}', '\d{2}/\d{2}/\d{4}']
replace_with=['name','city','month','time', 'date']

print(df.text.replace(to_replace, replace_with, regex=True))

Et le texte modifié est:

0    name is going to visit city in month
1                      I was born in date
2                 I will be there at time

Vous pouvez trouver un exemple ici . Notez que les remplacements sur le texte sont effectués avec l'ordre dans lequel ils apparaissent dans les listes

0
George Pipis

c'est ma solution au problème. Je l'ai utilisé dans un chatbot pour remplacer les différents mots à la fois.

def mass_replace(text, dct):
    new_string = ""
    old_string = text
    while len(old_string) > 0:
        s = ""
        sk = ""
        for k in dct.keys():
            if old_string.startswith(k):
                s = dct[k]
                sk = k
        if s:
            new_string+=s
            old_string = old_string[len(sk):]
        else:
            new_string+=old_string[0]
            old_string = old_string[1:]
    return new_string

print mass_replace("The dog hunts the cat", {"dog":"cat", "cat":"dog"})

cela deviendra The cat hunts the dog

0
emorjon2

Autre exemple: liste d'entrée

error_list = ['[br]', '[ex]', 'Something']
words = ['how', 'much[ex]', 'is[br]', 'the', 'fish[br]', 'noSomething', 'really']

La sortie souhaitée serait

words = ['how', 'much', 'is', 'the', 'fish', 'no', 'really']

Code:

[n[0][0] if len(n[0]) else n[1] for n in [[[w.replace(e,"") for e in error_list if e in w],w] for w in words]] 
0
Akhil Thayyil

Voici une autre façon de le faire avec un dictionnaire:

listA="The cat jumped over the house".split()
modify = {Word:word for number,Word in enumerate(listA)}
modify["cat"],modify["jumped"]="dog","walked"
print " ".join(modify[x] for x in listA)
0
Stefan Gruenwald

Je suggère que le code devrait être, par exemple:

z = "My name is Ahmed, and I like coding "
print(z.replace(" Ahmed", " Dauda").replace(" like", " Love" ))

Il imprimera toutes les modifications comme demandé.

0
Ahmed dauda

Ou juste pour un hack rapide:

for line in to_read:
    read_buffer = line              
    stripped_buffer1 = read_buffer.replace("term1", " ")
    stripped_buffer2 = stripped_buffer1.replace("term2", " ")
    write_to_file = to_write.write(stripped_buffer2)
0
Brandon H

À partir de la précieuse réponse d’Andrew, j’ai développé un script qui charge le dictionnaire à partir d’un fichier et élabore tous les fichiers du dossier ouvert pour effectuer les remplacements. Le script charge les mappages à partir d'un fichier externe dans lequel vous pouvez définir le séparateur. Je suis débutant, mais j’ai trouvé ce script très utile pour effectuer plusieurs substitutions dans plusieurs fichiers. Il a chargé un dictionnaire avec plus de 1000 entrées en quelques secondes. Ce n'est pas élégant mais cela a fonctionné pour moi

import glob
import re

mapfile = input("Enter map file name with extension eg. codifica.txt: ")
sep = input("Enter map file column separator eg. |: ")
mask = input("Enter search mask with extension eg. 2010*txt for all files to be processed: ")
suff = input("Enter suffix with extension eg. _NEW.txt for newly generated files: ")

rep = {} # creation of empy dictionary

with open(mapfile) as temprep: # loading of definitions in the dictionary using input file, separator is prompted
    for line in temprep:
        (key, val) = line.strip('\n').split(sep)
        rep[key] = val

for filename in glob.iglob(mask): # recursion on all the files with the mask prompted

    with open (filename, "r") as textfile: # load each file in the variable text
        text = textfile.read()

        # start replacement
        #rep = dict((re.escape(k), v) for k, v in rep.items()) commented to enable the use in the mapping of re reserved characters
        pattern = re.compile("|".join(rep.keys()))
        text = pattern.sub(lambda m: rep[m.group(0)], text)

        #write of te output files with the prompted suffice
        target = open(filename[:-4]+"_NEW.txt", "w")
        target.write(text)
        target.close()
0
Tommaso Sandi