懒洋洋地设置词典

dan*_*ast 11 python lazy-loading lazy-evaluation

假设我在python中有这个字典,在模块级别定义(mysettings.py):

settings = {
    'expensive1' : expensive_to_compute(1),
    'expensive2' : expensive_to_compute(2),
    ...
}
Run Code Online (Sandbox Code Playgroud)

我希望在访问密钥时计算这些值:

from mysettings import settings # settings is only "prepared"

print settings['expensive1'] # Now the value is really computed.
Run Code Online (Sandbox Code Playgroud)

这可能吗?怎么样?

Gua*_* Li 10

不要继承内置字典。即使你覆盖了dict.__getitem__()方法,dict.get()也不会像你预期的那样工作。

正确的方法是abc.Mappingcollections.

from collections.abc import Mapping

class LazyDict(Mapping):
    def __init__(self, *args, **kw):
        self._raw_dict = dict(*args, **kw)

    def __getitem__(self, key):
        func, arg = self._raw_dict.__getitem__(key)
        return func(arg)

    def __iter__(self):
        return iter(self._raw_dict)

    def __len__(self):
        return len(self._raw_dict)
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

settings = LazyDict({
    'expensive1': (expensive_to_compute, 1),
    'expensive2': (expensive_to_compute, 2),
})
Run Code Online (Sandbox Code Playgroud)

我还在此处列出示例代码和示例:https : //gist.github.com/gyli/9b50bb8537069b4e154fec41a4b5995a

  • 继承“abc.Mapping”并重写“__iter__()”和“__len__()”比继承“dict”并仅重写“get()”有什么优势? (3认同)

mic*_*yer 6

如果您不将参数与可调用对象分开,我认为这是不可能的。但是,这应该有效:

class MySettingsDict(dict):

    def __getitem__(self, item):
        function, arg = dict.__getitem__(self, item)
        return function(arg)


def expensive_to_compute(arg):
    return arg * 3
Run Code Online (Sandbox Code Playgroud)

现在:

>>> settings = MySettingsDict({
'expensive1': (expensive_to_compute, 1),
'expensive2': (expensive_to_compute, 2),
})
>>> settings['expensive1']
3
>>> settings['expensive2']
6
Run Code Online (Sandbox Code Playgroud)

编辑:

如果要expensive_to_compute多次访问 ,您可能还想缓存 的结果。像这样的东西

class MySettingsDict(dict):

    def __getitem__(self, item):
        value = dict.__getitem__(self, item)
        if not isinstance(value, int):
            function, arg = value
            value = function(arg)
            dict.__setitem__(self, item, value)
        return value
Run Code Online (Sandbox Code Playgroud)

现在:

>>> settings.values()
dict_values([(<function expensive_to_compute at 0x9b0a62c>, 2),
(<function expensive_to_compute at 0x9b0a62c>, 1)])
>>> settings['expensive1']
3
>>> settings.values()
dict_values([(<function expensive_to_compute at 0x9b0a62c>, 2), 3])
Run Code Online (Sandbox Code Playgroud)

您可能还想dict根据您想如何使用 dict来覆盖其他方法。

  • 存储函数并覆盖 __getitem__` 很聪明,而我认为继承 abc.Mapping 而不是内置 dict 会更好。否则,它不支持.get()。您可以在这里查看我的示例 https://gist.github.com/ligyxy/9b50bb8537069b4e154fec41a4b5995a (2认同)

Hen*_*nyH 5

将函数的引用存储为键的值,即:

def A():
    return "that took ages"
def B():
    return "that took for-ever"
settings = {
    "A": A,
    "B": B,
}

print(settings["A"]())
Run Code Online (Sandbox Code Playgroud)

这样,您仅在访问并调用它时评估与键关联的函数。可以处理非惰性值的合适类是:

import types
class LazyDict(dict):
    def __getitem__(self,key):
        item = dict.__getitem__(self,key)
        if isinstance(item,types.FunctionType):
            return item()
        else:
            return item
Run Code Online (Sandbox Code Playgroud)

用法:

settings = LazyDict([("A",A),("B",B)])
print(settings["A"])
>>> 
that took ages
Run Code Online (Sandbox Code Playgroud)