Lazy module variables--can it be done?
You can't do it with modules, but you can disguise a class "as if" it was a module, e.g., in itun.py
, code...:
import sys
class _Sneaky(object):
def __init__(self):
self.download = None
@property
def DOWNLOAD_PATH(self):
if not self.download:
self.download = heavyComputations()
return self.download
def __getattr__(self, name):
return globals()[name]
# other parts of itun that you WANT to code in
# module-ish ways
sys.modules[__name__] = _Sneaky()
Now anybody can import itun
... and get in fact your itun._Sneaky()
instance. The __getattr__
is there to let you access anything else in itun.py
that may be more convenient for you to code as a top-level module object, than inside _Sneaky
!_)
I used Alex' implementation on Python 3.3, but this crashes miserably: The code
def __getattr__(self, name):
return globals()[name]
is not correct because an AttributeError
should be raised, not a KeyError
.
This crashed immediately under Python 3.3, because a lot of introspection is done
during the import, looking for attributes like __path__
, __loader__
etc.
Here is the version that we use now in our project to allow for lazy imports
in a module. The __init__
of the module is delayed until the first attribute access
that has not a special name:
""" config.py """
# lazy initialization of this module to avoid circular import.
# the trick is to replace this module by an instance!
# modelled after a post from Alex Martelli :-)
Lazy module variables--can it be done?
class _Sneaky(object):
def __init__(self, name):
self.module = sys.modules[name]
sys.modules[name] = self
self.initializing = True
def __getattr__(self, name):
# call module.__init__ after import introspection is done
if self.initializing and not name[:2] == '__' == name[-2:]:
self.initializing = False
__init__(self.module)
return getattr(self.module, name)
_Sneaky(__name__)
The module now needs to define an init function. This function can be used to import modules that might import ourselves:
def __init__(module):
...
# do something that imports config.py again
...
The code can be put into another module, and it can be extended with properties as in the examples above.
Maybe that is useful for somebody.
The proper way of doing this, according to the Python docs, is to subclass types.ModuleType
and then dynamically update the module's __class__
. So, here's a solution loosely on Christian Tismer's answer but probably not resembling it much at all:
import sys
import types
class _Sneaky(types.ModuleType):
@property
def DOWNLOAD_FOLDER_PATH(self):
if not hasattr(self, '_download_folder_path'):
self._download_folder_path = '/dev/block/'
return self._download_folder_path
sys.modules[__name__].__class__ = _Sneaky
It turns out that as of Python 3.7, it's possible to do this cleanly by defining a __getattr__()
at the module level, as specified in PEP 562 and documented in the data model chapter in the Python reference documentation.
# mymodule.py
from typing import Any
DOWNLOAD_FOLDER_PATH: str
def _download_folder_path() -> str:
global DOWNLOAD_FOLDER_PATH
DOWNLOAD_FOLDER_PATH = ... # compute however ...
return DOWNLOAD_FOLDER_PATH
def __getattr__(name: str) -> Any:
if name == "DOWNLOAD_FOLDER_PATH":
return _download_folder_path()
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")