在Python中进行懒惰评估

Vip*_*pul 43 python lazy-evaluation python-3.x

什么是Python中的懒惰评估?

一个网站说:

在Python 3.x中,range()函数返回一个特殊的范围对象,它根据需要计算列表的元素(延迟或延迟评估):

>>> r = range(10)
>>> print(r)
range(0, 10)
>>> print(r[3])
3
Run Code Online (Sandbox Code Playgroud)

这是什么意思?

bco*_*rso 65

range()(或xrange()在Python2.x中)返回的对象称为生成器.

生成器不是在整个[0,1,2,..,9]存储器中存储整个范围,而是(i=0; i<10; i+=1)仅在需要时存储定义并计算下一个值(AKA惰性评估).

从本质上讲,生成器允许您返回类似结构的列表,但这里有一些差异:

  1. 列表在创建时存储所有元素.生成器在需要时生成下一个元素.
  2. 列表可以遍历的,因为你需要,发电机只能通过迭代尽可能准确地一次.
  3. 列表可以按索引获取元素,生成器不能 - 它只从头到尾生成一次值.

可以通过两种方式创建生成器:

(1)与列表理解非常相似:

# this is a list, create all 5000000 x/2 values immediately, uses []
lis = [x/2 for x in range(5000000)]

# this is a generator, creates each x/2 value only when it is needed, uses ()
gen = (x/2 for x in range(5000000)) 
Run Code Online (Sandbox Code Playgroud)

(2)作为函数,yield用于返回下一个值:

# this is also a generator, it will run until a yield occurs, and return that result.
# on the next call it picks up where it left off and continues until a yield occurs...
def divby2(n):
    num = 0
    while num < n:
        yield num/2
        num += 1

# same as (x/2 for x in range(5000000))
print divby2(5000000)
Run Code Online (Sandbox Code Playgroud)

注意:即使range(5000000)是Python3.x中的生成器,[x/2 for x in range(5000000)]仍然是一个列表.range(...)做它的工作并一次生成x一个,但是在x/2创建此列表时将计算整个值列表.

  • 实际上,`range`(或2.x中的`xrange`)确实*不*返回生成器.生成器是一个迭代器 - 对于任何生成器`g`,你可以调用`next(g)`.`range`对象实际上是可迭代的.你可以在它上面调用`iter`来获得一个迭代器,但它本身不是一个迭代器(你不能在它上面调用`next`).除此之外,这意味着您可以多次迭代单个范围对象. (11认同)
  • “生成器只能迭代一次。” 不是真的 您可以使用next(),中断for循环或根本不访问它来进行少于一次的迭代。也许删除“完全”一词或将其更改为“至多”。 (2认同)

Bur*_*lid 14

简而言之,延迟评估意味着在需要时评估对象,而不是在创建对象时评估对象.

在Python 2中,range将返回一个列表 - 这意味着如果你给它一个大数字,它将计算范围并在创建时返回:

>>> i = range(100)
>>> type(i)
<type 'list'>
Run Code Online (Sandbox Code Playgroud)

但是在Python 3中,您将获得一个特殊的范围对象:

>>> i = range(100)
>>> type(i)
<class 'range'>
Run Code Online (Sandbox Code Playgroud)

只有当你消费它时,它才会被实际评估 - 换句话说,它只会在你真正需要它时返回范围内的数字.


小智 8

一个名为python 模式维基百科的github仓库告诉我们什么是惰性求值。

延迟 expr 的 eval 直到需要它的值并避免重复 evals。

range 在 python3 中不是一个完整的惰性求值,因为它不能避免重复求值。

惰性求值的一个更经典的例子是cached_property

import functools

class cached_property(object):
    def __init__(self, function):
        self.function = function
        functools.update_wrapper(self, function)

    def __get__(self, obj, type_):
        if obj is None:
            return self
        val = self.function(obj)
        obj.__dict__[self.function.__name__] = val
        return val
Run Code Online (Sandbox Code Playgroud)

cached_property(又名 lazy_property)是一个装饰器,它将 func 转换为惰性求值属性。第一次访问属性时,调用 func 以获取结果,然后在下次访问该属性时使用该值。

例如:

class LogHandler:
    def __init__(self, file_path):
        self.file_path = file_path

    @cached_property
    def load_log_file(self):
        with open(self.file_path) as f:
            # the file is to big that I have to cost 2s to read all file
            return f.read()

log_handler = LogHandler('./sys.log')
# only the first time call will cost 2s.
print(log_handler.load_log_file)
# return value is cached to the log_handler obj.
print(log_handler.load_log_file)
Run Code Online (Sandbox Code Playgroud)

用一个恰当的词来说,像range这样的 python 生成器对象更像是通过call_by_need模式设计的,而不是惰性求值

  • 截至 2018 年,维基百科文章将“按需调用”和“懒惰评估”视为同义词。因为 Python 3 版本的 `range()` 确实没有创建一个完整的数字列表,而是在我们停止请求一个数字之前生成数字(例如通过中断 for 循环),它确实是(原文如此)“延迟了一个数字的 eval expr 直到需要它的值”。我会说你将它们视为两个不同的概念是错误的。 (2认同)