Potrzebowałem rozwiązania, w którym ciągi do zamiany mogą być wyrażeniami regularnymi, na przykład, aby pomóc w normalizacji długiego tekstu przez zastąpienie wielu białych znaków jednym. Opierając się na szeregu odpowiedzi od innych, w tym MiniQuark i mmj, wymyśliłem:
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)
Działa z przykładami podanymi w innych odpowiedziach, na przykład:
>>> 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.'
Najważniejsze dla mnie jest to, że możesz również używać wyrażeń regularnych, na przykład, aby zamieniać tylko całe słowa lub normalizować białe znaki:
>>> 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"
Jeśli chcesz używać kluczy słownika jako normalnych ciągów znaków, możesz uciec przed nimi przed wywołaniem wielu_replace przy użyciu np. Tej funkcji:
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"
Poniższa funkcja może pomóc w znalezieniu błędnych wyrażeń regularnych wśród kluczy słownika (ponieważ komunikat o błędzie z wielu_replace nie jest zbyt wymowny):
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())
Pamiętaj, że nie łączy łańcuchów zamienników, zamiast tego wykonuje je jednocześnie. To sprawia, że jest bardziej wydajny bez ograniczania tego, co może zrobić. Aby naśladować efekt łączenia, konieczne może być dodanie większej liczby par zamieniających łańcuchy i zapewnienie oczekiwanego uporządkowania par:
>>> multiple_replace("button", {"but": "mut", "mutton": "lamb"})
'mutton'
>>> multiple_replace("button", [("button", "lamb"),
... ("but", "mut"), ("mutton", "lamb")])
'lamb'