Как заменить несколько подстрок строки?

Я хотел бы использовать .заменить функцию для замены нескольких строк.

и

string.replace("condition1", "")

но хотел бы иметь что-то вроде

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

хотя это не похоже на хороший синтаксис

как правильно это сделать? вроде как в grep / regex вы можете сделать и для замены полей на определенные строки поиска

19 ответов


вот короткий пример, который должен помочь с регулярными выражениями:

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)

например:

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

вы могли бы просто сделать маленький цикл функции.

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

здесь text полный строку и dic словарь - каждое определение представляет собой строку, которая заменит соответствовать термину.

Примечание: в Python 3, iteritems() был заменен items()


осторожно: словари Python не имеют надежного порядка для итерации. Это решение только решает вашу проблему, если:

  • порядок замены не имеет значения
  • это нормально для замены, чтобы изменить результаты предыдущих замен

например:

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

возможный выход #1:

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

возможный выход #2

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

одним из возможных решений является использование 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)

выход:

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

осторожно #2: неэффективно, если ваш text строка слишком большая или в словаре много пар.


вот вариант первого решения с использованием reduce, если вам нравится быть функциональным. :)

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

Мартино еще лучше вариант:

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

Я построил это на превосходном ответе 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)

один выстрел использование:

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

обратите внимание, что, поскольку замена производится всего за один проход, "кафе" меняется на" чай", но не возвращается к"кафе".

Если вам нужно сделать такую же замену много времен, то вы можете создать функцию замены легко:

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

улучшения:

  • превратил код в функцию
  • добавлена многострочный поддержка
  • Исправлена ошибка при вылете
  • легко создать функцию для конкретной множественной замены

наслаждайтесь! :-)


почему не одно такое решение?

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

Это просто более краткое резюме F. J и MiniQuark отличные ответы. Все, что вам нужно для достижения несколько одновременных замен строк следующая функция:

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)

использование:

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

Если вы желаете, то вы можете сделать ваши собственные преданные функции замены старт с этим более простым одним.


Я хотел бы предложить использование шаблонов строк. Просто поместите строку для замены в словаре, и все будет установлено! Пример 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 0').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'

в моем случае мне нужна была простая замена уникальных ключей именами, поэтому я придумал это:

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

мне нужно было решение, в котором заменяемые строки могут быть регулярными выражениями, например, чтобы помочь в нормализации длинного текста, заменив несколько символов пробела на один. Основываясь на цепочке ответов от других, включая MiniQuark и mmj, это то, что я придумал:

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)

он работает для примеров, приведенных в других ответах, например:

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

главное для меня, что вы можете использовать регулярные выражения для пример для замены только целых слов или нормализации пробела:

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

если вы хотите использовать ключи словаря как обычные строки, вы можете избежать их перед вызовом multiple_replace, используя, например, эту функцию:

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"

следующая функция может помочь в поиске ошибочных регулярных выражений среди ваших ключей словаря (так как сообщение об ошибке из multiple_replace не очень говорит):

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

обратите внимание, что он не цепляет замены, вместо этого выполняет их одновременно. Это делает его более эффективным без ограничения того, что он может сделать. Чтобы имитировать эффект цепочки, вам может потребоваться добавить больше пар замены строк и обеспечить ожидаемый порядок пар:

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

вот мои $0.02. Он основан на ответе Эндрю Кларка, только немного яснее, и он также охватывает случай, когда строка для замены является подстрокой другой строки для замены (более длинная строка выигрывает)

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)

в этой в этом суть, не стесняйтесь изменять его, если у вас есть какие-либо предложения.


вы действительно не должны делать это таким образом, но я просто нахожу это слишком круто:

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

теперь answer в результате всех замен в свою очередь

опять же, это очень hacky и это не то, что вы должны использовать регулярно. Но просто приятно знать, что ты можешь сделать что-то подобное, если тебе когда-нибудь понадобится.


вот образец, который более эффективен на длинных строках со многими небольшими заменами.

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)

дело в том, чтобы избежать многих конкатенаций длинных строк. Мы режем исходную строку на фрагменты, заменяя некоторые фрагменты по мере формирования списка, а затем объединяем все это обратно в строку.


или просто для быстрого взлома:

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)

вот еще один способ сделать это со словарем:

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)

начиная с драгоценного ответа Андрея я разработал сценарий, который загружает словарь из файла и разрабатывает все файлы в открытой папке, чтобы сделать замены. Скрипт загружает сопоставления из внешнего файла, в котором можно задать разделитель. Я новичок, но я нашел этот скрипт очень полезным при выполнении нескольких замен в нескольких файлах. Он загрузил словарь с более чем 1000 записей в сек. Это не элегантно, но это сработало для мне

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

Это мое решение проблемы. Я использовал его в чате, чтобы сразу заменить разные слова.

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

это станет The cat hunts the dog


другой пример : Список ввода

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

желаемый результат будет

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

код :

[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]] 

Я не знаю о скорости, но это мое рабочее быстрое исправление:

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

... но мне нравится ответ #1 regex выше. Примечание-если одно новое значение является подстрокой другого, то операция не является коммутативной.


насчет "в 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"