It does not "run foo3 again", it runs the foo3.py script again. The first time foo3.py was running was to produce the module __main__, the second time to produce the module foo3.py.
The behaviour is in fact (almost) as if you had a file named __main__.py and another named foo3.py both with these exact same content and you then run python __main__.py. This is what is happening.
Only, Python fakes it so that it looks as if the program started from a script named __main__.py no matter what the actual Python file was. The only telltale sign to contrary is that __file__ would tell the filename of the actual script, i.e. /spam/ham/eggs/foo3.py.
The reason why it does not go to an infinite loop is that import looks for a module with the given name in sys.modules - if it is already present there it does not execute any new files. Upon startup Python will create an entry for __main__ in sys.modules, and the code of the startup script (foo3.py) is executed within the scope of this module.
Then when it executes the statement import foo3 it will check if foo3 has an entry in sys.modules. As it is not there, a new empty module named foo3 is created, placed into sys.modules, and the code of foo3.py is executed within the scope of this new empty module.
It eventually executes the import 2nd time. This time there is foo3 in sys.modules, so importing does not create or load any more scripts, just returns the already-loaded module.
To get the "infinite" loop you can delete the already-imported module reference from sys.module prior to importing foo3 again:
import sys
def functionA():
print("a1")
if 'foo3' in sys.modules:
del sys.modules['foo3']
from foo3 import functionB
print("a2")
functionB()
print("a3")
def functionB():
print("b")
print("t1")
print("m1")
functionA()
print("m2")
print("t2")
And when run you'll get
[....]
File ".../foo3.py", line 7, in functionA
from foo3 import functionB
File ".../foo3.py", line 17, in <module>
functionA()
File ".../foo3.py", line 7, in functionA
from foo3 import functionB
RuntimeError: maximum recursion depth exceeded while calling a Python object