Gleb Kisenkov
Gleb Kisenkov

Reputation: 37

Defer variable initialization until evaluation

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

Answers (4)

MisterMiyagi
MisterMiyagi

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

luther
luther

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

HGM
HGM

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

Exelian
Exelian

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

Related Questions