【问题标题】:Defer variable initialization until evaluation推迟变量初始化直到评估
【发布时间】:2021-09-12 12:24:42
【问题描述】:

我需要一种将全局变量的初始化推迟到第一次访问它的方法,总体思路用以下 Python 伪代码表示:

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()

到目前为止,我正在考虑以某种方式告诉 Python 在每次评估其实例时调用一个特殊的类方法,但我似乎无法用谷歌搜索它:

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()

所以,基本上我需要知道是否有与 makeup __class__instance__access__ 方法等效的东西。

【问题讨论】:

  • 一个考虑因素是这将使使用全局变量的代码很难进行单元测试

标签: python


【解决方案1】:

如果您必须推迟初始化,您可能在__init__ 方法中做的太多。但是,如果您不控制该代码,那么您似乎需要类似代理类的东西,所以您可以这样做:

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

一种方法,请参阅:Python proxy class

在那个答案中,一个实例化的对象被代理(而不是类),所以如果你想推迟 __init__ 调用,你必须进行一些修改。

【讨论】:

    【解决方案2】:

    从 Python 3.7 开始,可以定义 module __getattr__ method 以编程方式提供“全局”属性。更早且更普遍的是,one can define a custom module type to provide such a method

    假设需要Bar()来初始化全局FOO,则可以使用以下__getattr__在模块范围

    # 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}")
    

    这使得FOO 在作为属性访问时以编程方式可用,即作为module.FOO 或导入。它仅在第一次此类访问之后在全局范围内可用。

    如果对FOO的访问预计首先发生在模块内部,那么提供一个“getter”函数会更容易。

    def get_FOO() -> Bar:
        global _FOO
        try:
            return _FOO
        except NameError:
            _FOO = Bar()
            return _FOO
    

    【讨论】:

      【解决方案3】:

      您可能想考虑只使用一个实际的全局变量并使用global &lt;variable&gt; 访问它,但我不能说这是否适合用例。如果您只是在寻找一些缓存逻辑,它应该可以正常工作。

      您可以使用metaclasses 来执行此操作,这是一种在实例化类时修改类的方法。这是否有用取决于您要达到的目标。

      【讨论】:

        【解决方案4】:

        如果你控制类代码,你可以使用__getattribute__来延迟初始化,直到你第一次访问一个属性。

        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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-03-25
          • 2018-05-31
          • 2017-04-03
          • 2015-07-03
          • 2014-03-28
          相关资源
          最近更新 更多