Reputation: 37
I need a way to defer the initialization of a global variable until the firs access to it, the overall idea is expressed in the following Python pseudocode:
FOO = bar
FOO.some_method_on_bar() # Init bar: bar = Bar(); bar.some_method_on_bar()
FOO.some_method_on_bar() # Use cached bar: bar.some_method_on_bar()
So far I'm thinking of somehow telling Python to call a special class method every time its instance is evaluated, but I can't seem to google it up:
class LazyGetter:
def __init__(self, get_value) -> None:
self.get_value = get_value
def __class__instance__access__(self):
return self.get_value()
FOO = LazyGetter(get_value=lambda: Bar())
FOO # = LazyGetter.__class__instance__access__()
FOO.some_method_on_bar() # = LazyGetter.__class__instance__access__().some_method_on_bar()
So, basically I need to know if there's something equivalent to the madeup __class__instance__access__
method.
Upvotes: 0
Views: 890
Reputation: 50126
Since Python 3.7, one can define a module __getattr__
method to programmatically provide "global" attributes. Earlier and more generally, one can define a custom module type to provide such a method.
Assuming that Bar()
is needed to initialise the global FOO
, the following __getattr__
at module scope can be used.
# can type-annotate to "hint" that FOO will exist at some point
FOO: Bar
# called if module.<item> fails
def __getattr__(item: str):
if item == "FOO":
global FOO # add FOO to global scope
FOO = Bar()
return FOO
raise AttributeError(f"module {__name__!r} has no attribute {item!r}")
This makes FOO
available programmatically when accessed as an attribute, i.e. as module.FOO
or an import. It is only available in the global scope after the first such access.
If the access to FOO
is expected to happen inside the module first, it is easier to provide a "getter" function instead.
def get_FOO() -> Bar:
global _FOO
try:
return _FOO
except NameError:
_FOO = Bar()
return _FOO
Upvotes: 1
Reputation: 5554
If you control the class code, you can use __getattribute__
to delay initialization until the first time you access an attribute.
class Bar:
def __init__(self, *args):
self._args = args
def __getattribute__(self, name):
args = super().__getattribute__('_args')
if args is not None:
# Initialize the object here.
self.data = args[0]
self.args = None
return super().__getattribute__(name)
def some_method_on_bar(self):
return self.data
Upvotes: 0
Reputation: 26
If you have to defer initialization, you may be doing too much in the __init__
method. But if you don't control that code, then you seem to be needing something like a proxy class, so you can do:
proxied_bar = Proxy(Bar)
...
proxied_bar.some_bar_method() # this would initialize Bar, if it isn't yet initialized, and then call the some_bar_method
One way to do so, see: Python proxy class
In that answer an instantiated object is proxied (rather than the class), so you have to make some modifications if you want to defer the __init__
call.
Upvotes: 1
Reputation: 5888
You might want to consider just having an actual global variable and accessing it with global <variable>
but I can't say if that fits the use-case. It should work fine if you're just looking for some caching logic.
You might be able to do this with metaclasses which is a way of modifying a class when it's instantiated. Whether this is useful depends on what you're trying to achieve.
Upvotes: 0