The language features that make this possible are a) late-binding and b) resident development tools (compiler, debugger, interpreter, inspector, trace, ... are all included in the runtime).
foo.py
def foo1(x): return x+1
bar.py from foo import foo1
def bar1(x): return foo1(x)\*2
I can modify foo.foo1 in REPL: foo.foo1 = lambda x: x+3
but bar.bar1 will still use the old definition!bar1 lookups foo1 via a dict which is constructed when bar is imported, so it won't be updated unless I reload all files.
So dicts are just wrong for this, there are too many of them. Symbols are basically perfect for this as they provide an easily manageable point of indirection and also much more performant.