规范

Dask 是一种规范,用于使用普通的 Python 数据结构(即字典、元组、函数和任意 Python 值)编码一个图——特别是具有数据依赖关系的任务的有向无环图。

定义

Dask图 是一个将 映射到 计算 的字典:

{'x': 1,
 'y': 2,
 'z': (add, 'x', 'y'),
 'w': (sum, ['x', 'y', 'z']),
 'v': [(sum, ['w', 'z']), 2]}

可以是 str、bytes、int、float 或它们的元组:

'x'
('x', 2, 3)

任务 是一个以可调用对象为第一个元素的元组。任务表示旨在由单个工作者运行的工作原子单位。示例:

(add, 'x', 'y')

我们将任务表示为一个元组,其中 第一个元素是一个可调用函数*(如 ``add``),后续元素是该函数的 *参数。一个 参数 可以是任何有效的 计算

一个 计算 可以是以下之一:

  1. Dask 图中的任何 ,例如 'x'

  2. 任何其他值,如 1,将按字面意思解释

  3. 一个像 (inc, 'x') 这样的 任务 (见下文)

  4. 一个 计算 列表,例如 [1, 'x', (inc, 'x')]

因此,以下所有内容都是有效的 计算

np.array([...])
(add, 1, 2)
(add, 'x', 2)
(add, (inc, 'x'), 2)
(sum, [1, 2])
(sum, ['x', (inc, 'x')])
(np.dot, np.array([...]), np.array([...]))
[(sum, ['x', 'y']), 'z']

要编码关键字参数,我们推荐使用 functools.partialtoolz.curry

应该期望哪些功能

在类似 (add, 'x', 'y') 的情况下,像 add 这样的函数接收的是具体值而不是键。Dask 调度器会在调用 add 函数之前,将键(如 'x''y')替换为它们的计算值(如 12)。

入口点 - get 函数

get 函数是所有 调度器 的计算入口点。该函数获取与给定键关联的值。该键可能引用存储的数据,如 'x' 的情况,或引用一个任务,如 'z' 的情况。在后一种情况下,get 应执行所有必要的计算以检索计算值。

>>> from dask.threaded import get

>>> from operator import add

>>> dsk = {'x': 1,
...        'y': 2,
...        'z': (add, 'x', 'y'),
...        'w': (sum, ['x', 'y', 'z'])}
>>> get(dsk, 'x')
1

>>> get(dsk, 'z')
3

>>> get(dsk, 'w')
6

此外,如果给定一个 list ,get 应该同时获取多个键的值:

>>> get(dsk, ['x', 'y', 'z'])
[1, 2, 3]

因为我们接受键列表作为键,所以我们支持嵌套列表:

>>> get(dsk, [['x', 'y'], ['z', 'w']])
[[1, 2], [3, 6]]

在内部,get 可以任意复杂,调用分布式计算,使用缓存等。

为什么使用元组

通过 (add, 'x', 'y'),我们希望编码调用 add 函数在键 'x''y' 对应的值上的结果。

我们打算表达以下含义:

add('x', 'y')  # after x and y have been replaced

但这会出错,因为 Python 在知道 'x''y' 的值之前就立即执行了函数。

我们将执行延迟,通过将左括号向左移动一个项,创建一个元组:

Before: add( 'x', 'y')
After: (add, 'x', 'y')

这使我们能够将所需的计算作为数据存储,可以使用其他Python代码进行分析,而不是立即执行。

LISP 用户会将此识别为 s-表达式,或作为一种基本的引用形式。