为了账号安全,请及时绑定邮箱和手机立即绑定

用Snowpark解决Advent of Code编程谜题(第三篇)

用存储过程来模块化你的代码

照片由Tom FiskPexels网站上拍摄。

这是我的系列文章中的一期,在这期系列中,我们通过解谜来学习Snowpark。如果你还没有看过,这里分别是第一部分第二部分,它们是关于通过解决Advent of Code谜题来学习Snowpark的。

在上一节课中,我们学习了如何将带有输入数据的文件上传到内部的Snowflake阶段,并注册一个用户定义的函数(UDF),该函数可以读取文件、解谜并返回结果。

今天我们来学习如何将代码模块化。我们不希望把所有的代码都塞在一个文件里,特别是当代码量很大时。相反,我们会把代码拆分出来,成为各种模块、类和方法。

雪花 UDF 适用于可以直接写在函数体内的简单逻辑。当我们需要更复杂的逻辑,比如模块、类和方法时,我们会编写存储过程。有关 Snowpark 存储过程的更多信息,请参阅文档 此处

把 UDF 改成存储过程

用户定义函数

让我们将上一课中的这个UDF转换为存储过程。然后我们将它模块化。

我们定义和注册用户定义的函数(UDF)和存储过程的方式之间存在微妙的区别,简单总结如下:

  • UDF和存储过程(SP)的处理程序都是一个Python函数
  • 存储过程使用@sproc装饰器进行注册
  • 我们必须将session作为参数传递给存储过程(UDF使用默认的session对象,并不需要将其作为参数传递)
  • 存储过程使用CALL命令调用,而UDF则是通过SELECT语句来调用

要在Snowpark中注册名为_warmup2_的存储过程,请从上一课复制UDF代码到一个新文件中,并对注册和函数定义行中的以下内容进行修改(包括更改装饰器和添加会话变量),同时将存储过程和Python函数的名称从_warmup1_改为_warmup2_。

    # 注册一个永久存储过程
    @sproc(is_permanent=True, # 修改装饰器
        name="warmup2",   
        replace=True,   
        stage_location="@aoc_dev_stage",   
        packages=['snowflake-snowpark-python'])  
    def warmup2(session: Session, fileurl: str) -> str:

然后修改你调用存储过程的方法。改为通过执行CALL命令,而不是像在UDF中那样执行SELECT语句。(而是像这样直接调用CALL命令)

调用warmup2函数,该函数通过build_scoped_file_url构建文件URL,参数为@aoc_files_stage和'warmup1_example.txt'。之后,显示lines_df中的内容。

完成这些更改并运行代码后,你应该会在AOC2024_DB数据库的AOC模式(schema)中看到一个名为_warmup2_的存储过程。在Python控制台打印出的结果数据框应与来自UDF的结果一致。

将存储过程模块化:

虽然从UDF转换到存储过程时,我们看起来几乎没有做任何改动,但我们现在已经有了改进它的机会。

在上节课的UDF中,我们编写了谜题解决方案的逻辑代码。我们现在将这些代码移至单独的模块,并拆分为Part1和Part2,因为Advent of Code的谜题通常分为两个部分。

创建一个名为 AOC00_part1_part2.py 的新 Python 文件。定义一个名为 Part1Part2 的类,该类包含两个方法 part1()part2() 以及 __init__() 方法。该类接受一个参数 lines,它是输入数据文件的内容。代码如下:

# 假设的代码示例
class Part1Part2:
    def __init__(self, lines):
        self.lines = lines

    def part1(self):
        # 部分1的实现
        pass

    def part2(self):
        # 部分2的实现
        pass
    class Part1Part2:

        def __init__(self, lines):
            self.lines = lines

        def part1(self) -> int:
            # 先设个总和变量为0
            total = 0

            # 复制part1部分的解决方案代码...

            return total

        def part2(self) -> int:
            # 先设个总和变量为0
            total = 0

            # 在part2部分编写自己的解决方案...

            return total

然后回到你之前写的存储过程代码,在存储过程中添加之前创建的模块作为导入项。

from AOC00_part1_part2 import Part1Part2

从存储过程的主体中删除谜题部分1的解决方案代码。相反,,然后实例化 Part1Part2 类,并调用其 part1() 方法。

    # 实例化 Part1Part2 类
    _Part1Part2 = Part1Part2(lines)

    # 调用 part1() 方法来解决第一部分的问题,然后把结果保存在 part1_answer 变量中
    part1_answer = _Part1Part2.part1()

你也可以调用 part2() 方法,如果实现了它:

    # 调用 _Part1Part2.part2() 以解决第二部分
    part2_answer = _Part1Part2.part2()

你也可以让函数返回一个 f-string 格式的结果,并把返回类型改为字符串(之前是整数类型)。

返回(f"第1部分的答案为 {part1_answer}\n第2部分的答案为 {part2_answer}")

在你将这个存储过程注册到Snowflake时,必须将AOC00_part1_part2.py文件与存储过程代码一起上传到Snowflake的内部存储阶段,以便该文件可以被存储过程访问。你可以通过在@sproc装饰器中添加一个名为imports的参数来实现这一点,如下所示:

    # 创建一个永久存储过程
    @sproc(permanent=True,   
        name="warmup2",   
        replace=True,   
        stage_location="@aoc_dev_stage",   
        packages=['snowflake-snowpark-python'],  
        imports=['AOC00_part1_part2.py'])

然后运行代码。结果应该与之前相同,除了输出格式会有所不同,因为我们将输出格式从整数改成了 f-string(f-string 是一种字符串格式化方法),例如(注意我没有实现谜题的第二部分,因此第二部分的答案为零):

你可以在这里查看本课的完整代码,它在我的 GitHub 仓库中:这里

本地测试模块试试看

将代码模块化的好处之一是,在将存储过程注册到 Snowflake 之前,您可以单独测试各个模块。您可能已经注意到,注册存储过程需要几秒钟,在您测试和调试代码的过程中,您可能不希望每次修改代码时都经历这样的等待时间。

要本地测试你的模块,而无需将它注册到Snowflake,你可以对你的代码做一些修改。具体来说,你可以对你的代码做一些修改。

  • 取消 @sproc 装饰器的注释,这样 Python 函数将不会被注册到 Snowflake
  • 使用本地电脑上的文件,而不是使用 SnowflakeFile 类打开包含谜题数据的文件。我将 split() 函数的参数改为 \n,因为它适用于我的本地 Windows 系统
        # 打开文件并读取内容,将内容按行分割成列表

        # 若要进行本地测试,可以注释掉上面的代码,直接用 open() 函数打开文件
        with open(fileurl) as f:
            lines = f.read().split("\n")

直接使用Python中的函数,而不是调用那个存储过程

    #lines_df = session.sql("""call warmup2(build_scoped_file_url(@aoc_files_stage, 'warmup1_example.txt'))""")  
    #lines_df.show()  

    # 要本地测试,请注释掉前两行(如下)并调用该函数  
    print(warmup2(session, 'warmup1_example.txt'))

现在你可以运行代码,并且你会发现它运行得快多了,因为它是在本地运行。你也可以用你喜欢的调试工具,比如设置断点,或者在标准输出中打印信息。

一旦你完成测试,并且确信你的代码按预期运行,你可以撤销本地测试所做的更改。然后,将存储过程注册到Snowflake中。

你可以在我 GitHub 仓库 这里 查阅本节课程的完整代码。

另一种在当地测试你的Snowpark代码的方法是使用本地测试框架,这是一个允许你在不连接到Snowflake账户的情况下,本地操作Snowpark Python数据框的模拟环境。由于本课不使用数据框,我们在这节课里无法使用本地测试框架,但我们会在未来的一节课中提到它。

为什么要在Snowflake注册存储过程呢?

到这一步,你可能会想,为什么非要费劲在Snowflake注册存储过程,将文件上传到Snowflake内部存储阶段(stage),构建带范围的文件URL,然后调用存储过程,而我们本可以直接在本地用Python打开包含谜题数据文件并运行该模块来完成整个过程不是更简单吗?

简短的回答是:因为我们正在学习Snowpark。在这节课中,我们处理的是一个非常简单的谜题,不需要复杂的逻辑或大规模的数据处理。在未来的课程中,我们会看到谜题解决方案代码如何与Snowflake对象交互,并会思考当我们有大量的数据存储在对象存储或其他云源中的数据无法在本地计算机上处理时会发生什么。

雪花文档,涵盖本课内容

我是 Maja Ferle,Snowflake 数据超级英雄,同时还是 In516ht 的高级顾问。你可以在 LinkedIn 上找到我,或者看看我新出的书《Snowflake 数据工程》(Snowflake Data Engineering)。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消