TIL: you can replace with a function in `re.sub`

Published April 21, 2026

I was in a conversation on the Python Discord earlier this week about match/case and I gave an example of a place where I had used it. This was in the __format__ mathod and I was using it to pick which format flag was being sent. Someone looked at the code sample I shared and pointed out that a simpler implementation would use re.sub. He then proceeded to provide me with a code snippet that did what I was doing in a couple of dozen lines of code in 12 that was cleaner and easier to read:


        def expand_format_specifier(match: re.Match[str]) -> str:
            char, spec = match.groups()
            if char == 'c':
                return self.notes if self.notes else ''
            if char == 'n':
                return self.name
            if char == 'q':
                if not spec or not self.quantity:
                    return str(self.quantity)
                return format(self.quantity, spec)
            return match.group(0)  # pragma: no cover

        return re.sub(r'%([cnq])(?:(?<=[q])\[([^]]*)(?:]|$))?', expand_format_specifier, format_spec)

He then nerd sniped himself into opening a PR for it.

This was something I had never seen before, and I have been using re.sub in so many places and this would make my life so much easier in so many cases. The method signature for re.sub is:


sub(pattern, repl, string, count=0, flags=0)

Per the documentation:

If repl is a function, it is called for every non-overlapping occurrence of pattern. The function takes a single Match argument, and returns the replacement string.

So the next time I need to do a more complicated replacement, I have a better tool in my box to handle it.


Previous: Multiple git users on one machine.