【问题标题】:Python, lazy listPython,懒人列表
【发布时间】:2010-03-08 03:52:09
【问题描述】:

是否可以在 Python 中懒惰地评估列表?

例如

a = 1
list = [a]
print list
#[1]
a = 2
print list
#[1]

如果列表设置为惰性计算,那么最后一行将是 [2]

【问题讨论】:

  • 这不是我所理解的惰性求值。我会说它更像这样:我们想要找到其平方可被 3 整除的前 30 个正数。使用急切的语言,您可以为性能编写代码 - while(list.length < 30) if(i*i % 3 == 0) list += i++; 或表达性(但丢弃大量不必要的列表)方式)-list1 = range 0..10000; for i in list1 if i * i % 3 == 0 list2.add i; list2.trim(30)。使用惰性时,您编写的内容更像后者,但获得了前者的性能。陶丹尼尔explains it brilliantly.
  • 一种懒惰的方式可能看起来像这样:range().filter(var where var*var % 3 == 0).take(30) - 它写得很有表现力,但代码不是强制运行的。它运行:range 生成 1。Filter - 失败,因为1*1%3 != 0。然后range 生成 2。Filter 失败。然后range 生成3。Filter 通过因为3*3%3==0Take 将其作为答案排队。在take 取 30 后,算法停止。它只使用所需的内存,但它也是高度可读(和可并行化)的代码。

标签: python lazy-evaluation


【解决方案1】:

“惰性”评估的概念通常伴随着函数式语言——但在那些语言中,您无法将两个不同的值重新分配给同一个标识符,因此,即使您的示例也无法重现。

重点根本不是懒惰——而是保证使用标识符与获取对标识符所引用的相同值的引用相同,并且重新分配标识符,一个裸名,一个不同的值,保证使标识符引用与它们不同的值。对第一个值(对象)的引用不会丢失。

考虑一个类似的例子,其中没有重新分配给一个裸名,而是任何其他类型的突变(对于可变对象,当然 - 数字和字符串是不可变的),包括对某物的分配 其他而不是一个简单的名字:

>>> a = [1]
>>> list = [a]
>>> print list
[[1]]
>>> a[:] = [2]
>>> print list
[[2]]

由于没有a - ... 重新分配裸名称a,而是a[:] = ... 重新分配a内容,因此很容易将Python 设置为“懒惰” ” 如你所愿(确实需要一些努力才能使其“渴望”!-)......如果懒惰与渴望与这些情况中的任何一种有关(它没有;-)。

请注意“分配给一个裸名”的非常简单的语义(与分配给其他任何东西,可以通过适当地使用您自己的类型进行各种调整和控制),以及“懒惰与渴望”的视觉错觉" 可能会消失;-)

【讨论】:

    【解决方案2】:

    在寻找真正的惰性列表实现时偶然发现了这篇文章,但尝试和解决这听起来很有趣。

    以下实现基本上完成了最初的要求:

    from collections import Sequence
    
    class LazyClosureSequence(Sequence):
        def __init__(self, get_items):
            self._get_items = get_items
    
        def __getitem__(self, i):
            return self._get_items()[i]
    
        def __len__(self):
            return len(self._get_items())
    
        def __repr__(self):
            return repr(self._get_items())
    

    你可以这样使用它:

    >>> a = 1
    >>> l = LazyClosureSequence(lambda: [a])
    >>> print l
    [1]
    >>> a = 2
    >>> print l
    [2]
    

    这显然太可怕了。

    【讨论】:

    • 还是很有趣的。谢谢
    【解决方案3】:

    Python 是 not really very lazy in general

    您可以使用生成器来模拟惰性数据结构(如无限列表等),但就使用普通列表语法等而言,您不会有惰性。

    【讨论】:

      【解决方案4】:

      这是一个只读的惰性列表,它只需要一个预定义的 length 和一个 cache-update 函数:

      import copy
      import operations
      from collections.abc import Sequence
      from functools import partialmethod
      from typing import Dict, Union
      
      def _cmp_list(a: list, b: list, op, if_eq: bool, if_long_a: bool) -> bool:
          """utility to implement gt|ge|lt|le class operators"""
          if a is b:
              return if_eq
          for ia, ib in zip(a, b):
              if ia == ib:
                  continue
              return op(ia, ib)
      
          la, lb = len(a), len(b)
          if la == lb:
              return if_eq
          if la > lb:
              return if_long_a
          return not if_long_a
      
      
      class LazyListView(Sequence):
          def __init__(self, length):
              self._range = range(length)
              self._cache: Dict[int, Value] = {}
      
          def __len__(self) -> int:
              return len(self._range)
      
          def __getitem__(self, ix: Union[int, slice]) -> Value:
              length = len(self)
      
              if isinstance(ix, slice):
                  clone = copy.copy(self)
                  clone._range = self._range[slice(*ix.indices(length))]  # slicing
                  return clone
              else:
                  if ix < 0:
                      ix += len(self)  # negative indices count from the end
                  if not (0 <= ix < length):
                      raise IndexError(f"list index {ix} out of range [0, {length})")
                  if ix not in self._cache:
                      ...  # update cache
                  return self._cache[ix]
      
          def __iter__(self) -> dict:
              for i, _row_ix in enumerate(self._range):
                  yield self[i]
      
          __eq__ = _eq_list
          __gt__ = partialmethod(_cmp_list, op=operator.gt, if_eq=False, if_long_a=True)
          __ge__ = partialmethod(_cmp_list, op=operator.ge, if_eq=True, if_long_a=True)
          __le__ = partialmethod(_cmp_list, op=operator.le, if_eq=True, if_long_a=False)
          __lt__ = partialmethod(_cmp_list, op=operator.lt, if_eq=False, if_long_a=False)
      
          def __add__(self, other):
              """BREAKS laziness and returns a plain-list"""
              return list(self) + other
      
          def __mul__(self, factor):
              """BREAKS laziness and returns a plain-list"""
              return list(self) * factor
      
          __radd__ = __add__
          __rmul__ = __mul__
      
      
      

      请注意,this SO 中也讨论了此类。

      【讨论】:

        猜你喜欢
        • 2022-11-14
        • 1970-01-01
        • 2020-09-08
        • 2014-05-31
        • 2016-04-29
        • 1970-01-01
        • 2022-01-11
        • 2013-03-15
        • 2022-01-09
        相关资源
        最近更新 更多