¿Cómo reemplazar múltiples subcadenas de una cadena?


Me gustaría usar el .función replace para reemplazar varias cadenas.

Actualmente tengo

string.replace("condition1", "")

Pero me gustaría tener algo como

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

Aunque eso no se siente como una buena sintaxis

¿Cuál es la manera correcta de hacer esto? como en grep / regex puedes hacer \1 y \2 para reemplazar los campos a ciertas cadenas de búsqueda

Author: martineau, 2011-05-25

19 answers

Aquí hay un breve ejemplo que debería hacer el truco con expresiones regulares:

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())
pattern = re.compile("|".join(rep.keys()))
text = pattern.sub(lambda m: rep[re.escape(m.group(0))], text)

Por ejemplo:

>>> pattern.sub(lambda m: rep[re.escape(m.group(0))], "(condition1) and --condition2--")
'() and --text--'
 189
Author: Andrew Clark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-05 16:56:42

Usted podría simplemente hacer una pequeña función de bucle agradable.

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

Donde text es la cadena completa y dic es un diccionario - cada definición es una cadena que reemplazará una coincidencia con el término.

Nota : en Python 3, iteritems() ha sido reemplazado por items()


Cuidado: Los diccionarios Python no tienen un orden confiable para la iteración. Esta solución solo resuelve su problema si:

  • el orden de los reemplazos es irrelevante
  • está bien que un reemplazo cambie los resultados de reemplazos anteriores

Por ejemplo:

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

Posible salida # 1:

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

Posible salida # 2

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

Una posible solución es usar 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)

Salida:

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

Cuidado #2: Ineficiente si tu text cadena es demasiado grande o hay muchos pares en el diccionario.

 86
Author: Joseph Hansen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-14 14:12:22

Aquí hay una variante de la primera solución usando reduce, en caso de que te guste ser funcional. :)

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

La versión aún mejor de Martineau:

repls = ('hello', 'goodbye'), ('world', 'earth')
s = 'hello, world'
reduce(lambda a, kv: a.replace(*kv), repls, s)
 70
Author: Björn Lindqvist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-05 08:42:52

Construí esto sobre la excelente respuesta de F. J.:

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)

Uso de un solo disparo:

>>> 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é.

Tenga en cuenta que dado que el reemplazo se realiza en una sola pasada, "café" cambia a "té", pero no cambia de nuevo a "café".

Si necesita hacer el mismo reemplazo muchas veces, puede crear una función de reemplazo fácilmente:

>>> 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!\"

Mejoras:

  • convirtió el código en una función
  • se agregó soporte multilínea
  • se ha corregido un error en el escape
  • fácil de crear una función para un reemplazo múltiple específico

¡Disfruta! :-)

 25
Author: MiniQuark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-05 10:09:12

¿Por qué no una solución como esta?

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
 25
Author: Enrico Bianchi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-17 06:50:46

Esto es solo un resumen más conciso de las grandes respuestas de F. J y MiniQuark. Todo lo que necesita para lograr múltiples reemplazos simultáneos de cadenas es la siguiente función:

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)

Uso:

>>>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 lo desea, puede hacer sus propias funciones de reemplazo dedicadas a partir de esta más simple.

 24
Author: mmj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-22 10:16:28

Me gustaría proponer el uso de plantillas de cadenas. Simplemente coloque la cadena a reemplazar en un diccionario y todo está listo! Ejemplo 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'
 18
Author: Fredrik Pihl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-24 21:54:14

En mi caso, necesitaba un simple reemplazo de claves únicas con nombres, así que pensé esto:

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.'
 7
Author: James Koss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-13 19:05:20

Necesitaba una solución donde las cadenas a reemplazar pueden ser expresiones regulares, por ejemplo, para ayudar a normalizar un texto largo reemplazando varios caracteres de espacio en blanco por uno solo. Sobre la base de una cadena de respuestas de otros, incluyendo MiniQuark y mmj, esto es lo que se me ocurrió:

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)

Funciona para los ejemplos dados en otras respuestas, por ejemplo:

>>> 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.'

Lo principal para mí es que también puede usar expresiones regulares, por ejemplo, para reemplazar palabras completas solamente, o para normalizar el espacio en blanco:

>>> 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 desea utilizar las teclas del diccionario como cadenas normales, puedes escaparlos antes de llamar a multiple_replace usando, por ejemplo, esta función:

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 siguiente función puede ayudar a encontrar expresiones regulares erróneas entre las teclas del diccionario (ya que el mensaje de error de multiple_replace no es muy revelador):

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

Tenga en cuenta que no encadena los reemplazos, sino que los realiza simultáneamente. Este hace más eficiente sin limitar lo que puede hacer. Para imitar el efecto del encadenamiento, es posible que solo necesite agregar más pares de reemplazo de cadenas y garantizar el orden esperado de los pares:

>>> multiple_replace("button", {"but": "mut", "mutton": "lamb"})
'mutton'
>>> multiple_replace("button", [("button", "lamb"),
...                             ("but", "mut"), ("mutton", "lamb")])
'lamb'
 5
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-20 13:26:53

Aquí mis $0.02. Se basa en la respuesta de Andrew Clark, solo un poco más clara, y también cubre el caso cuando una cadena a reemplazar es una subcadena de otra cadena a reemplazar (cadena más larga gana)

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)

Está en este esta esencia, siéntase libre de modificarlo si tiene alguna propuesta.

 4
Author: bgusach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-14 11:16:08

Realmente no deberías hacerlo de esta manera, pero me parece demasiado genial:

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

Ahora, answer es el resultado de todos los reemplazos a su vez

De nuevo, esto es muy hacky y no es algo que deberías usar regularmente. Pero es bueno saber que puedes hacer algo como esto si alguna vez necesita.

 1
Author: inspectorG4dget,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-24 21:27:24

Aquí hay una muestra que es más eficiente en cadenas largas con muchos reemplazos pequeños.

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)

El punto es evitar muchas concatenaciones de cadenas largas. Cortamos la cadena de origen en fragmentos, reemplazando algunos de los fragmentos a medida que formamos la lista, y luego unimos todo de nuevo en una cadena.

 1
Author: 9000,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-24 21:49:46

O simplemente para un hackeo rápido:

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
Author: Brandon H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-16 04:09:04

Aquí hay otra forma de hacerlo con un diccionario:

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
Author: Stefan Gruenwald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-28 07:00:30

A partir de la preciosa respuesta de Andrew desarrollé un script que carga el diccionario desde un archivo y elabora todos los archivos en la carpeta abierta para hacer los reemplazos. El script carga las asignaciones desde un archivo externo en el que puede establecer el separador. Soy un principiante, pero encontré este script muy útil cuando hago varias sustituciones en varios archivos. Cargó un diccionario con más de 1000 entradas en segundos. No es elegante, pero funcionó para mí

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
Author: Tommaso Sandi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-20 10:23:42

Esta es mi solución al problema. Lo usé en un chatbot para reemplazar las diferentes palabras a la vez.

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"})

Esto se convertirá en The cat hunts the dog

 0
Author: emorjon2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-19 13:29:28

Otro ejemplo : Lista de entrada

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

La salida deseada sería

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

Código:

[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
Author: Akhil Thayyil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-06 10:50:06

No se acerca la velocidad pero esta es mi solución rápida diaria:

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

... pero me gusta la respuesta regex #1 anterior. Nota-si un nuevo valor es una subcadena de otro entonces la operación no es conmutativa.

 0
Author: del_hol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-26 11:20:42

¿Qué pasa con " commons-lang3"s " StringUtils.replaceEach "?

StringUtils.replaceEach(null, *, *)        = null
StringUtils.replaceEach("", *, *)          = ""
StringUtils.replaceEach("aba", null, null) = "aba"
StringUtils.replaceEach("aba", new String[0], null) = "aba"
StringUtils.replaceEach("aba", null, new String[0]) = "aba"
StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
(example of how it does not repeat)
StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
 0
Author: myuce,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-27 08:42:50