If the code is coming from an untrusted source (ie: anyone on the internet can send random python code for you to run), you should run that code in a way that won't jeopardize the system it's running on. Either within a docker container, a chroot jail, a virtual machine you can just restart after the code's run to undo whatever changes it made, etc.
As to how you'd actually run it,
compile()
is a builtin function that compiles a string into a code object/ast, which you can then pass to exec().
Just make sure you pass empty dicts to exec() for globals() and locals...
>>> code = '''
... for i in range(2):
... code = 'print("new code")'
... print(i)
... '''
>>> block = compile(code, '<string>', 'exec')
>>> block
<code object <module> at 0x000001C7A2AC4A80, file "<string>", line 2>
>>> code
'\nfor i in range(2):\n code = \'print("new code")\'\n print(i)\n'
>>> exec(block)
0
1
>>> code
'print("new code")'
>>> # !!! the code body was overwritten by the dynamic code
>>> code = '''
... for i in range(2):
... code = 'print("new code")'
... print(i)
... '''
>>> # for a little more protection, pass empty globals and locals
>>> exec(block, {}, {})
0
1
>>> code
'\nfor i in range(2):\n code = \'print("new code")\'\n print(i)\n'