In my mental model of modules ModuleType
was more of a metaclass and when I write the module I kinda of create a
class Foo(metaclass=types.ModuleType):
def __dir__(self) ...
...
And only on import I create an instance that is cached in sys.modules
, but I still can create another instance of the same module. And the way it is in CPython is more an implementation detail.
One of my use-cases is to invalidate cache when some module attribute is changed, for example
# module.py
default_arg2 = 1
_result_cache = {}
def foo(arg1, arg2=None):
arg2 = default_arg2 if arg2 is None else arg2
if (result := _result_cache.get((arg1, arg2))) is not None:
return result
# slow computation.
return _result_cache.setdefault((arg1, arg2), result)
def __setattr__(self, name, value):
if name == "default_arg2":
_result_cache = {k: v for k, v in _result_cache.items() if k[1] == default_arg2}
globals()[name] = value
Right now I have a module with a class that holds default_arg, cache and computation method and instance of it. And I also have foo = class_instance.foo
to make computation, and to change default I expect users to do module.class_instance.default_arg =
.
It was written that way before I knew about module.__class__
trick, and for my case I would be ok to pay 2x slowdown for attribute access (it shouldn’t happen often enough to be noticeable for normal workloads), I can imagine cases where it’s more a concern.
Well, I think cache is one of the cases of more general “module have some invariant and atrribute mutation should keep the invariant” and I think almost any invariant imaginabe for regular class can exists for module.