跳转到主要内容

Python 3的延迟评估

项目描述

build status

我稍后会写...

什么是延迟?

lazy 是一个用于使python 延迟评估 (有点像) 的模块。

lazy 在python 3.5和3.4下运行。

为什么选择延迟?

为什么不选择延迟?

我认为延迟计算非常酷,我也认为python非常酷;将它们结合起来更是双倍酷。

如何使用延迟?

使用延迟代码有3种方式

  1. lazy_function

  2. run_lazy

  3. IPython单元格和行魔法

lazy_function

lazy_function 接受一个python函数并返回一个新的函数,它是延迟版本的。这可以用作装饰器。

示例

@lazy_function
def f(a, b):
    return a + b

调用 f(1, 2) 将返回一个 thunk,当需要严格评估时,它将添加1和2。对返回的thunk进行任何操作都将继续链式调用更多计算,直到必须严格评估。

延迟函数还允许词法闭包

@lazy_function
def f(a):
    def g(b):
        return a + b
    return g

当我们调用 f(1) 时,我们将得到一个我们预期的 thunk;然而,这个thunk封装了函数 g。因为 g 是在延迟上下文中创建的,它也将隐式地成为一个 lazy_function。这意味着 type(f(1)(2))thunk;但是,f(1)(2) == 3

我们可以使用strict来严格评估延迟函数的部分,例如

>>> @lazy_function
... def no_strict():
...    print('test')
...
>>> strict(no_strict())

在这个例子中,我们从未强制打印,所以我们从未看到函数调用的结果。考虑这个函数呢

>>> @lazy_function
... def with_strict():
...    strict(print('test'))
...
>>> strict(with_strict())
test
>>> result = with_strict()
>>> strict(result)
test

在这里,我们可以看到在懒函数内部如何使用严格模式。 strict 会强制对参数进行严格评估,从而强制调用打印。我们还可以看到,仅仅调用 with_strict 并不足以评估函数,我们需要强制对结果产生依赖。

这是在字节码级别实现的,以提前加载使用懒加载机制的大部分成本。在函数调用时几乎没有开销,因为大部分开销都是在函数创建(定义)时产生的。

run_lazy

我们可以使用 run_lazy 函数将普通 Python 转换为懒 Python,该函数接受字符串、'名称'、全局和局部变量。这对于懒 Python 来说就像 execeval。这将修改提供的全局和局部变量,以便我们可以访问懒评估的代码。

示例

>>> code = """
print('not lazy')
strict(print('lazy'))
"""
>>> run_lazy(code)
lazy

它也使用与 lazy_function 相同的字节码操作,因此它们将产生相同的结果。

IPython单元格和行魔法

如果您已安装 IPython,您可以使用单元格和行魔法机制来编写和评估懒代码。例如

In [1]: from lazy import strict

In [2]: %lazy 2 + 2  # line magic acts as an expression
Out[2]: 4

In [3]: type(_2)
Out[3]: lazy._thunk.thunk

In [4]: %%lazy  # cell magic is treated as a statement
   ...: print('lazy')
   ...: strict(print('strict'))
   ...:
strict

thunk

在核心上,懒加载只是将表达式转换为称为 thunk 的延迟计算对象树。thunk 通过不在需要值之前评估它们来包装正常函数。包装的 thunk 函数可以接受 thunk 作为参数;这就是树是如何构建的。有些计算不能延迟,因为需要某些状态来构建 thunk,或者 Python 标准定义某些方法返回特定类型。这些被称为“严格点”。严格点的例子是 strbool,因为 Python 标准表示这些函数必须返回它们自己的类型的实例。大多数这些转换器都是严格的;然而,一些其他东西是严格的,因为它解决了解释器中的递归问题,如访问 __class__

自定义严格属性

strict 实际上是一个不能放入 thunk 中的类型。例如

>>> type(thunk(strict, 2))
int

请注意,这不是一个 thunk,并且已经进行了严格评估。

要创建自定义严格对象,可以继承 strict。这防止了对象被包装在 thunk 中,从而允许您创建严格的数据结构。

对象还可以定义一个 __strict__ 方法来定义如何严格评估对象。例如,对象可以这样定义

class StrictFive(object):
    def __strict__(self):
        return 5

这将使 strict(StrictFive()) 返回 5,而不是 StrictFive 的实例。

undefined

undefined 是一个不能严格评估的值。它作为计算占位符很有用。

我们可以将 undefined 想象成 Python 中的

@thunk.fromexpr
@Exception.__new__
class undefined(Exception):
    def __strict__(self):
        raise self

当评估时,该对象将引发其自身的一个实例。这被表示为等效的定义,尽管实际上是在 C 中编写的,以生成更清晰的堆栈跟踪。

已知问题

目前,以下问题已知无法工作

递归定义的 thunk

递归定义的 thunk 是在其自己的图中出现两次的 thunk。例如

>>> a = thunk(lambda: a)
>>> strict(a)

这会导致无限循环,因为为了严格评估 a,我们将调用返回 a 的函数,然后我们将尝试严格评估它。

状态:错误,可能修复。

基本上是正确的,例如

>>> a = lambda: a()
>>> a()
...
RuntimeError: maximum recursion depth exceeded

在thunk示例中的区别是我们将进入c代码以执行递归,因此它不会在合理的时间内终止。

可能的修复方法是尝试检测这些循环并引发一些异常;然而,这可能在理想情况下是一个非常昂贵的检查,使得thunk评估变得非常缓慢。

注意事项

我在repl中打开了它,一切都很严格!

因为Python规范说对象的__repr__必须返回一个str,所以对repr的调用必须严格评估内容,这样我们才能看到它。repl会隐式地对显示的内容调用repr。我们可以通过这样做来看到这是一个thunk:

>>> a = thunk(operator.add, 2, 3)
>>> type(a)
lazy.thunk.thunk
>>> a
5

再次强调,因为我们需要计算一些内容来表示它,所以repl不是使用它的好用例,并且可能会让它看起来像是始终严格。

当我认为它应该是懒的时候,x正在被严格评估

有一些情况,根据Python语言规范,东西必须是严格的。因为这不是一种真正的新语言,而是一种编写非常低效Python的自动化方法,所以必须遵循Python的规则。

例如,__bool__、__int__和其他转换器期望返回类型必须是正确的类型。这被视为需要严格性的地方。

但这可能不是情况,相反,我可能漏掉了什么,你是正确的,它应该是懒的。如果你认为我漏掉了什么,请打开一个问题,我将尽快解决它。

某些有状态的东西坏了

对不起,你正在使用未管理的状态和懒评估,这是你应得的。thunks缓存了规范形式,所以再次严格调用将引用缓存的值。如果这依赖于某个有状态函数,那么它将不会按预期工作。

我试图用一个thunk做x,它坏了!

这个库可能是有问题的。这是心血来潮写的,我几乎没有考虑使用情况。

请打开一个问题,我将尽快回复你。

备注

  1. 在LazyTransformer(如thunk(int, your_thunk))中,构造函数的函数调用将被懒化,所以虽然这是一个需要严格性的地方,但它仍然可以被“优化”掉。

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分布

lazy_python-0.2.1.tar.gz (24.1 kB 查看哈希值)

上传时间

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面