Here's one opportunity to save cycles. The patterns for the brackets are getting compiled every time hasClosedBrackets is called.
Something like this could be added right after BRACKETS is defined:
brackets_compiled = {
(o, c): re.compile(r"(?<=\{}).+?(?=\{})".format(o, c)) for o, c in BRACKETS.items()
}
Then the following three lines can be modified:
From:
for (o, c) in BRACKETS.items():
pattern = r'(?<=\{}).+?(?=\{})'.format(o, c)
for match in re.findall(pattern, S):
To:
for (o, c), pattern in brackets_compiled.items():
for match in pattern.findall(S):
With this, the patterns are compiled once and that can be looped through each time. This can also be changed to the following if (o, c) aren't needed:
for pattern in brackets_compiled.values():
for match in pattern.findall(S):
```