Insert str into larger str in the most pythonic way I want to insert a small str into a larger one in the most pythonic way.

Maybe I missed a useful str method or function ? The function that insert the small str into the larger one is insert_tag.

I have tried something with reduce but it didn't work and it was too complex.

Code:

from random import choice, randrange
from typing import List


def insert_tag(text: str, offsets: List[int], tags: List[str]) -&gt; str:
    text: str = text
    for offset, tag in zip(offsets, tags):
        # text = f'{text[:offset]}{tag}{text[offset:]}'  # solution 1
        text = text[:offset] + tag + text[offset:]  # solution 2
    return text


if __name__ == '__main__':
    tag_nbr = 30
    # Text example
    base_text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do ' \
                'eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut ' \
                'enim ad minim veniam, quis nostrud exercitation ullamco laboris ' \
                'nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor ' \
                'in reprehenderit in voluptate velit esse cillum dolore eu fugiat ' \
                'nulla pariatur. Excepteur sint occaecat cupidatat non proident, ' \
                'sunt in culpa qui officia deserunt mollit anim id est laborum.' \
        .replace(',', '').replace('.', '')
    # Random list of tag only for testing purpose
    tag_li: List[str] = [f'&lt;{choice(base_text.split())}&gt;' for _ in range(tag_nbr)]
    # Ordered list of random offset only for testing purpose
    offset_li: List[int] = [randrange(len(base_text)) for _ in range(tag_nbr)]
    offset_li.sort(reverse=True)
    # Debug
    print(tag_li, offset_li, sep='\n')

    # Review
    new_text: str = insert_tag(base_text, offset_li, tag_li)
    # End Review

    # Debug
    print(new_text)


Output:

"Lorem ips&lt;consectetur&gt;u&lt;Lorem&gt;m dolo&lt;Lorem&gt;r sit amet consecte&lt;Lorem&gt;tur &lt;nostrud&gt;adipiscing eli&lt;aute&gt;t sed d&lt;anim&gt;o eiusmod tempo&lt;voluptate&gt;r &lt;exercitation&gt;incid&lt;anim&gt;i&lt;non&gt;dunt ut l&lt;quis&gt;abor&lt;do&gt;e et dol&lt;reprehenderit&gt;ore magna aliqua Ut enim ad minim veniam quis nostrud exercitatio&lt;est&gt;n ullamco laboris nisi ut aliquip ex&lt;nulla&gt; ea &lt;consectetur&gt;commodo consequat Duis aute irure dolor in repreh&lt;deserunt&gt;enderit in voluptate vel&lt;sint&gt;it esse cillum dolore eu f&lt;qui&gt;ugiat nulla pariatur &lt;magna&gt;Excepteur sin&lt;ex&gt;t occaecat cup&lt;labore&gt;i&lt;ut&gt;datat non proident sun&lt;est&gt;t&lt;in&gt; in cul&lt;fugiat&gt;pa qui officia&lt;enim&gt; deserunt mollit anim i&lt;in&gt;&lt;anim&gt;d est laborum"


PS: The rest of the code is not my primary concern, but if it can be improved, tell me, thanks.
The core code (I'd prefer the f-string if you can guarantee Python 3.6+, else the addition or "{}{}{}".format(...) if you need to support older Python versions) looks fine. However, you overwrite text each loop, but continue to use the originally provided offset's, which could be very confusing since the offsets will become incorrect after the first tag insertion. The code doesn't crash, but I'd argue it doesn't "work", certainly not as I'd intuitively expect:

In [1]: insert_tag('abc', [1, 2], ['x', 'y'])
Out[1]: 'axyyxbc'


I'd expect this to produce 'axbyc'. If that output looks correct to you, ignore the rest of this answer.

It would be better to break the original string apart first, then perform the insertions. As is typical in Python, we want to avoid manual looping if possible, and as is typical for string generation, we want to create as few new strings as possible. I'd try something like the following:

In [2]: def insert_tags(text: str, offsets: [int], tags: [str]):
   ...:     offsets = [0] + list(offsets)
   ...:     chunks = [(text[prev_offset:cur_offset], tag) for prev_offset, cur_offset, tag in zip(offsets[:-1], offset
   ...: s[1:], tags)]
   ...:     chunks.append([text[offsets[-1]:]])
   ...:     return ''.join(piece for chunk_texts in chunks for piece in chunk_texts)


In [3]: insert_tags('abc', [1, 2], ['x', 'y'])
Out[3]: 'axbyc'


Other stylistic considerations:


Your function supports the insertion of multiple tags, so it should probably be called "insert_tags" (plural) for clarity.
Type annotation are generally good (though recall that they lock you in to newer versions of Python 3.X), but getting them right can be a nuisance. E.g., in this case I'd suggest using Sequence instead of List, since you really just need to be able to loop on the offsets and tags (e.g., they could also be tuples, sets, or even dicts with interesting keys). I also find them to often be more clutter than useful, so consider if adding type annotations really makes this code clearer, or if something simpler, like a succinct docstring, might be more helpful.