设计 Python UDFs¶
本主题帮助设计 Python UDFs。
本主题内容:
备注
通过矢量化 Python UDFs,您可以定义 Python 函数,该函数接收批量输入行作为 Pandas DataFrames (https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html),并返回批量结果作为 Pandas 数组 (https://pandas.pydata.org/docs/reference/api/pandas.array.html) 或 Series (https://pandas.pydata.org/docs/reference/series.html)。批处理接口在机器学习推理方案中可获得更好的性能。有关更多信息,请参阅 。
选择数据类型¶
在编写代码之前,请执行以下操作:
选择函数应接受作为实参的数据类型,以及函数应返回的数据类型。
考虑与时区相关的问题。
决定如何处理 NULL 值。
有关 Snowflake 如何映射 Python 和 SQL 数据类型的详细信息,请参阅 SQL-Python 数据类型映射。
TIMESTAMP_LTZ 值和时区¶
Python UDF 在很大程度上与调用它的环境相隔离。但是,时区是从调用环境继承的。如果调用方的会话在调用 Python UDF 之前设置了默认时区,则 Python UDF 具有相同的默认时区。有关时区的详细信息,请参阅 TIMEZONE。
NULL 值¶
对于除 Variant 之外的所有 Snowflake 类型,Python UDF 的 SQL NULL
实参转换为 Python None
值,返回的 Python None
值转换回 SQL NULL
。
Variant 类型值可以是:SQL NULL
或 VARIANT JSON null
。有关 Snowflake VARIANT NULL 的信息,请参阅 NULL 值。
VARIANT JSON
null
转换成 PythonNone
。SQL
NULL
转换为具有is_sql_null
属性的 Python 对象。
有关示例,请参阅 Python UDFs 中的 NULL 处理。
设计保持在 Snowflake 施加的约束范围内的 Python UDFs¶
为了确保 Snowflake 环境中的稳定性,Snowflake 对 Python UDFs 施加了以下约束。除非另有说明,否则这些限制将在执行 UDF 时强制执行,而不是在创建时强制执行。
训练机器学习 (ML) 模型有时会非常耗费资源。Snowpark-Optimized Warehouses 是一种 Snowflake 虚拟仓库,可用于需要大量内存和计算资源的工作负载。有关机器学习模型和 Snowpark Python 的信息,请参阅 使用 Snowpark Python 训练机器学习模型。
内存¶
避免使用过多内存。
较大的数据值可能会使用大量内存。
过多的堆栈深度会使用大量内存。
如果它们使用过多内存,则 UDFs 返回错误。具体限制可能会发生变化。
如果 UDFs 由于使用过多内存而失败,请考虑使用 Snowpark-Optimized Warehouses。
时间¶
避免每次调用占用大量时间的算法。
如果 UDF 完成时间过长,Snowflake 会终止 SQL 语句并向用户返回错误。这限制了无限循环等错误的影响和成本。
设计模块¶
当 SQL 语句调用 Python UDF 时,Snowflake 会调用您编写的 Python 函数。Python 函数称为“处理程序函数”,简称为“处理程序”。处理程序是在用户提供的模块中实现的函数。
与任何 Python 函数一样,函数必须声明为模块的一部分。
对于传递给 Python UDF 的每一行,处理程序都会调用一次。包含该函数的模块不会为每一行重新导入。Snowflake 可以多次调用同一模块的处理程序函数。
为了优化代码的执行,Snowflake 假定初始化可能很慢,而处理程序函数的执行速度很快。Snowflake 设置的执行初始化超时(包括加载 UDF 的时间和初始化模块的时间)比执行处理程序(使用一行输入调用处理程序的时间)更长。
有关设计模块的其他信息,请参阅 创建 Python UDFs。
在标量 UDFs 中优化初始化和控制全局状态¶
大多数标量 UDFs 应遵循以下准则:
如果需要初始化不跨行更改的共享状态,请在模块中初始化,而不是在处理程序函数中初始化。
编写线程安全的处理程序函数。
避免跨行存储和共享动态状态。
如果 UDF 无法遵循这些准则,请注意 Snowflake 需要独立处理标量 UDFs。依赖调用之间共享的状态可能会导致意外行为,因为系统可以按任何顺序处理行,并将这些调用分散到多个实例中。此外,同一个 Python 解释器中的同一个处理程序函数可以在多个线程中多次执行。
UDFs 应避免在对处理程序函数的调用之间依赖共享状态。但是,在以下两种情况下,可能希望 UDF 存储共享状态:
包含不希望对每一行重复的昂贵初始化逻辑的代码。
跨行(如缓存)利用共享状态的代码。
当需要维护将在处理程序调用之间共享的全局状态时,必须使用 线程 – 基于线程的并行性 (https://docs.python.org/3.8/library/threading.html) 中描述的同步原语来保护全局状态免受数据争用。
针对规模和性能进行优化¶
将矢量化 Python UDFs 与数据科学库配合使用¶
当代码将使用机器学习或数据科学库时,请使用矢量化 Python UDFs 来定义批量接收输入行的 Python 函数,这些库针对这些函数进行了优化操作。
有关更多信息,请参阅 。
编写单线程 UDF 处理程序¶
编写单线程的 UDF 处理程序。Snowflake 将处理数据分区和跨虚拟仓库计算资源扩展 UDF。
将昂贵的初始化放入模块¶
将昂贵的初始化代码放入模块范围。在那里,它将在 UDF 初始化时执行一次。避免在每次调用 UDF 处理程序时重新运行昂贵的初始化代码。
处理错误¶
用作 UDF 的 Python 函数可以使用常规的 Python 异常处理技术来捕获函数中的错误。
如果函数内部发生异常且未被函数捕获,则 Snowflake 会引发一个错误,其中包含异常的堆栈跟踪。启用 记录未处理异常 后,Snowflake 会在事件表中记录有关未处理异常的数据。
为了结束查询并产生 SQL 错误,可以显式抛出异常而不捕获它。例如:
if (x < 0):
raise ValueError("x must be non-negative.");
调试时,可以在 SQL 错误消息文本中包含值。为此,请将整个 Python 函数正文放在 try-catch 块中;将实参值追加到捕获的错误消息中;并使用扩展消息抛出异常。若要避免泄露敏感数据,请在部署到生产环境之前移除实参值。
遵循良好的安全实践¶
要帮助确保处理程序以安全的方式运行,请参阅 UDFs 和过程的安全实践 中所述的最佳实践。