I've found that the snippet below when reloading a module test
unexpectedly has all variables already defined in globals()
/locals()
.
Why this happens?
I've noticed this "xxx" in locals()
pattern a lot in Blender Python scripts as typically people use it to check whether module was reloaded before (you typically reload those scripts a lot during the development). Since everyone's using it, I guess it should work most of the times.
But is it really safe to use this caveat to identify whether module was already loaded before (any ideas of cases when it wouldn't work?)? I mean not just for the development and testing stuff as it does looks like more of an implementation detail that could break in the production.
# prints False, False, False
import test
import importlib
# prints True, True, True
importlib.reload(test)
test.py:
print("a" in globals(), "b" in globals(), "c" in globals())
# NameError: name 'a' is not defined
# print(a)
a = 25
b = 35
c = 45
As stated by @jasonharper in the comments: yes, the __dict__
of the module is retained when importlib.reload
is used. (From the importlib.reload() documentation: "The names in the module namespace are updated to point to any new or changed objects" and "When a module is reloaded, its dictionary (containing the module’s global variables) is retained". Quoted from the docs by @jasonharper)
For regular code that is never an issue, since all names that a module will use have to be defined first - either as an assignemtn of as a function or class declaration. This assignment will update the dict and replace the previous references that is there.
But checking if names as strings are present in globals()
like you are doing will detect the pre-existing names, and can be used to know if the module is in the middle of a reloading operation.
However, if the module is maually "deleted" from sys.modules
prior to reloading, then it will run with a fresh globals()
dictionary. This can only be done intentionally - so if you plan to base any behavior on whether an item is present in globals()
it is ok.
To see the behavior I mean, try this snippet:
# prints False, False, False
import test
import importlib
# prints True, True, True
importlib.reload(test)
import sys
del sys.modules("test")
importlib.reload(test)
# or even:
del sys.modules("test")
# module is reloaded, even without a `.reload()` call,
# as it is not in sys.modules anymore
import test