Python Mutli-replace

 ·  ☕ 4 min read

Module for multiple runs of the .replace string method

Do you need to replace a substring with another one multiple times on the same string?
Do you do that frequently and which there was a nicer way to write it?
This module might be for you


Basic Usage

It takes in a string, and 2 lists. Each item in the first list will be replaced by the corresponding item in the second list.

For example, the below:

1
2
astring = "A Horse walking in to a bar. Ouch!"
astring.replace("Horse", "Person").replace("bar", "dark room").replace("Ouch", "Surprise")

Would be replaced with:

1
2
3
import mreplace
astring = "A Horse walking in to a bar. Ouch!"
mreplace(astring, ["Horse", "bar", "Ouch"], ["Person", "darkroom", "Surprise"])

This might not seem like a saver at first but there are a few use cases where it could come in handy.
If the replacements being made are created at runtime (especially the number of replacements being done on a string) then you don’t need to hardcode long chains of .replace() functions.

The formatMode parameter

Another advantage of the mreplace function, if the default behaviour:
The formatMode parameter allows switching between an ‘all-at-once’ or ‘one-by-one’ replacement mode.

When using chains of .replace() functions (analogous to ‘one-by-one’ mode) if you have a replacement from “A” to “B” and later in the chain have a replacement of “B” to “C”, then all “A"s and all “B"s will be changed to “C”, which might not be desireable.
Or perhaps you want to switch 2 sub-strings: take for example the string “E before I” and you need to switch the letters “E” and “I”. If you used a chain of .replace() functions then such as in the function:

1
"E before I".replace("E", "I").replace("I", "E")

you would end up with the string “E before E”.

The ‘all-at-once’ mode (when formatMode is True) uses the .format() function to evaluate each replacement as though the previous ones didn’t happen, therefore the order is not important and swaps are possible.

Setting formatMode to False means the mreplace function will run a loop of .replace() calls instead.

The Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from typing import *


def mreplace(instring: str, old: List[str], new: List[str], formatMode: bool = True) -> str:
    """
    For a given string, replace all items in the old list with the corresponding item in the
        new list.

    If formatMode is set to False, then the replacement pairs will be evaluated sequentially.
        What this means is if you have A -> B and then B -> C, the final result will have
        converted A -> C.

    When formatMode is True, you can still have '{' or '}' in the instring, but neither
        '{' nor '}' can be items in the old list (but they can be in the new list).

    Args:
        instring (str): The string on which replacements will be applied.
        old (List[str]): Each item that should be replaced.
        new (List[str]): Each item that replaces the corresponding item in old.
        formatMode (bool, optional): Whether to do a formatMode replace or a sequential replace.
            Defaults to True.

    Raises:
        ValueError: The lists 'old' and 'new' must have the same length, else this exception is
            raised

    Returns:
        str: instring after it has undergone the replacement
    """

    # Lists must be the same length, raise Exception otherwise
    if len(old) != len(new):
        raise ValueError(
            "Lists must be the same length.\nOld ({OLDLEN}): {OLD}\nNew ({NEWLEN}): {NEW}"
            .format(OLD=old, OLDLEN=len(old), NEW=new, NEWLEN=len(new))
        )

    if formatMode:

        # Escape the formatting braces
        instring = instring.replace('{', '{{')
        instring = instring.replace('}', '}}')

        # To only evaluate items that will actually be replaced,
        # we first remove unused replacement pairs
        for item in old[:]:
            if item not in instring:
                itemIndex = old.index(item)
                del old[itemIndex]
                del new[itemIndex]

        # Replace all items with the relevant index in braces,
        # then .format() to replace the {index} placeholder
        # with the new value all at once rather than sequentially
        for pairIndex in range(0, len(old)):
            instring = instring.replace(old[pairIndex], '{' + str(pairIndex) + '}')
        print(instring)
        instring = instring.format(*new)

    else:
        # For each pair, perform a .replace() operation
        for index in range(0, len(old)):
            instring = instring.replace(old[index], new[index])
    return instring


# Example
print(
    mreplace(
        instring="this is only a test, only a very {good one, but all the same",
        old=['only', 'test', 'chacha', 'this', 'google', 'very', 'one'],
        new=['simply', 'this', 'mama', 'very', 'ppoop', 'many', '}'],
        formatMode=True
    )
)

Kieran Goldsworthy
WRITTEN BY
Kieran Goldsworthy
Cloud Engineer and Architect


What's on this Page