Python 3的延迟评估
项目描述
我稍后会写...
什么是延迟?
lazy 是一个用于使python 延迟评估 (有点像) 的模块。
lazy 在python 3.5和3.4下运行。
为什么选择延迟?
为什么不选择延迟?
我认为延迟计算非常酷,我也认为python非常酷;将它们结合起来更是双倍酷。
如何使用延迟?
使用延迟代码有3种方式
lazy_function
run_lazy
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 来说就像 exec 或 eval。这将修改提供的全局和局部变量,以便我们可以访问懒评估的代码。
示例
>>> 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 标准定义某些方法返回特定类型。这些被称为“严格点”。严格点的例子是 str 和 bool,因为 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不是使用它的好用例,并且可能会让它看起来像是始终严格。
print没有做任何事情!
嗯,你认为它会做什么?
如果我们写
@lazy_function
def f(a, b):
print('printing the sum of %s and %s' % (a, b))
return a + b
那么没有理由执行print调用。没有计算依赖于结果,所以它被随意跳过。
解决方案是强制依赖
@lazy_function
def f(a, b):
strict(print('printing the sum of %s and %s' % (a, b)))
return a + b
strict是一个用于严格评估内容的函数。因为函数体被解释为懒Python,函数调用被转换为thunk,因此我们可以对它进行strict操作。
这对于任何有副作用的函数调用都是正确的。
当我认为它应该是懒的时候,x正在被严格评估
有一些情况,根据Python语言规范,东西必须是严格的。因为这不是一种真正的新语言,而是一种编写非常低效Python的自动化方法,所以必须遵循Python的规则。
例如,__bool__、__int__和其他转换器期望返回类型必须是正确的类型。这被视为需要严格性的地方。
但这可能不是情况,相反,我可能漏掉了什么,你是正确的,它应该是懒的。如果你认为我漏掉了什么,请打开一个问题,我将尽快解决它。
某些有状态的东西坏了
对不起,你正在使用未管理的状态和懒评估,这是你应得的。thunks缓存了规范形式,所以再次严格调用将引用缓存的值。如果这依赖于某个有状态函数,那么它将不会按预期工作。
我试图用一个thunk做x,它坏了!
这个库可能是有问题的。这是心血来潮写的,我几乎没有考虑使用情况。
请打开一个问题,我将尽快回复你。
备注
在LazyTransformer(如thunk(int, your_thunk))中,构造函数的函数调用将被懒化,所以虽然这是一个需要严格性的地方,但它仍然可以被“优化”掉。
项目详情
lazy_python-0.2.1.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 5fc4c838ff1654d823f777fef96f1a69084a286431391552d6b849c3bac2c61e |
|
MD5 | 2c95c0d40671746851ce96c7c4aaaf84 |
|
BLAKE2b-256 | 02d7de0b5f24ffabfa165195181b48577c88473e19f5744fbfb70d6c9227ec40 |