The below piece of code is aimed at converting a factorial into its products. E.g. "4!" --> "(4*3*2*1)". This code does not work
Let's have a quick look at the docs:
Help on built-in function exec in module builtins:
exec(source, globals=None, locals=None, /) Execute the given source in the context of globals and locals.
The source may be a string representing one or more Python statements or a code object as returned by compile(). The globals must be a dictionary and locals can be any mapping, defaulting to the current globals and locals. If only globals is given, locals defaults to it.
and
Help on built-in function locals in module builtins:
locals() Return a dictionary containing the current scope's local variables.
NOTE: Whether or not updates to this dictionary will affect name lookups in the local scope and vice-versa is *implementation dependent* and not covered by any backwards compatibility guarantees.
This final note would appear to explain your troubles.
To be more specific, you are calling exec with one parameter, so it will execute in the default environment of locals() on top of globals(). It is important to realize, that these are not necessarily identical with the actual global and local scopes, but may in theory be proxies or copies or whatever. Now, if you, for example, assign a variable one of these dictionaries gets updated accordingly. What the note in the docs for locals says, is that there is no guarantee that this update will be propagated to the actual local scope. And this I think is why your program doesn't work.
How to work around this:
Based on the above one easy fix is
(1) make a contract with yourself that codeToRun assigns to stringToCheck.
(2) keep a reference to the instance of locals() you pass to exec
(3) use this to explicitly set stringToCheck
So your except block would look somewhat like
l = locals()
exec(codeToRun, globals(), l)
stringToCheck = l['stringToCheck']