通用惰性初始化器 class 和魔术方法

Generic lazy initializer class and magic methods

我想创建一个 class,它在第一次使用对象时在后台延迟初始化对象,但它的行为与初始化对象完全一样。它应该是线程安全的。例如:

In[1]: l = LazyInitializer(list, (1, 2, 3, 4, 5))
In[2]: l.__len__()
5

我目前的实现是这样的:

class LazyInitializer:

    def __init__(self, initializer, *args, **kwargs):
        self.__initializer = initializer
        self.__args = args
        self.__kwargs = kwargs

        self.__obj = None
        self.__lock = Lock()

    @property
    def _obj(self):
        with self.__lock:
            if self.__obj is None:
                self.__obj = self.__initializer(*self.__args, **self.__kwargs)

        return self.__obj

    def __getattr__(self, item):
        return getattr(self._obj, item)

这适用于常规对象成员(类似的函数和属性),但不适用于魔法方法,例如

In[2]: l.__len__()  # works
5
In[3]: len(l)  # doesn't work
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-e75269d816bd> in <module>
----> 1 len(l)

TypeError: object of type 'LazyInitializer' has no len()

一个临时解决方案可能是在 LazyInitializer 上显式实施所有可能的魔术方法。不过,有没有更好的办法呢?

确实如此。 实际上,我就在本周重新讨论了这个问题。问题是,对于使用魔术方法,通常是执行运算符的结果,Python 不会进行“常规”属性访问:出于性能原因 - 这是语言规范中的一部分,而不仅仅是一个cPython 的详细信息,它将快捷地检查 class 或其中一个超级 class 是否已经存在魔术方法(__getattr____getattribute__ 未勾选),调用它。

因此,确保以透明方式调用任何可能的魔术方法的唯一方法是,如果您的代理惰性对象具有所有魔术方法。

唯一可以以其他方式工作的“代理”是“super”本身:但它相当特殊,并且是该语言的关键组成部分。通过研究 super 在 C 中的实现,您可能会想出一个类似的透明代理(使用 C 中的一些代码),但无法保证。

我曾经放置了一个类似的代理,可以将任何计算卸载到子进程,这样我就有了一个“透明的未来对象”——它不是漂亮的代码,但我什至在生产中偶尔也使用过它:

https://bitbucket.org/jsbueno/lelo/src/master/