Python:以函数式编程风格编写文件

标签 python python-3.x io functional-programming

我们应该如何在保持功能纯净的情况下用 Python 编写文件?通常我会做这样的事情

from typing import Iterable
from io import IOBase


def transform_input(input_lines: Iterable[str]) -> Iterable[str]: ...


def print_pack(input_lines: Iterable[str], output: IOBase) -> None:
    for line in input_lines:
        print(line, file=output)


def main(*args, **kwargs):
    # Somehow we get a bunch iterables with strings and a list of output streams
    packs_of_input = ... # Iterable[Iterable[str]]
    output_streams = ... # Iterable[IOBase]
    packs_to_print = map(transform_input, packs_of_input)
    for pack, output_stream in zip(packs_to_print, output_streams):
        print_pack(pack, output_stream)

我们可以用这样的东西替换 for 循环

list(map(lambda pack_stream: print_pack(*pack_stream), zip(packs_to_print, output_streams))

但这只会让打印看起来像是功能性完成的。问题是 print_pack 不是一个纯函数,它所有的努力都会产生副作用并且它什么都不返回。 我们应该如何编写文件并保持功能纯(或几乎纯)?

最佳答案

本质上,在 Python 中,您需要某处有一个不纯的函数,所以在这个应用程序中没有办法拥有 100% 的纯函数。最后需要做一些IO,而IO是不纯的。

但是,您可以尝试将应用程序中的特定抽象层表示为纯函数,并将产生实际副作用的部分隔离到另一个模块中。您可以以一种特别的方式非常轻松地做到这一点——例如,通过在您的主代码中累积您要作为纯不可变数据结构编写的文件的内容。然后您的副作用代码可以减少大小,因为它需要做的就是将字符串转储到文件中。

我们可以向 Haskell 寻求一种更严格的方法来纯粹地表示具有纯函数和数据结构的副作用操作的全部功能——使用 Monad 抽象。本质上,Monad 是您可以将回调绑定(bind)到的东西,以创建一系列基于纯函数的有效计算。对于 IO monad,一旦您从 main 函数返回 IO 值,Haskell 运行时就会实际执行副作用——因此您编写的所有代码在技术上都是纯函数,而运行时负责 IO。

Effect库(免责声明:我写的)基本上在 Python 中实现了某种风格的 Monad(或非常接近 monad 的东西)。这使您可以将任意 IO(和其他副作用)表示为纯对象和函数,并将这些效果的实际性能放在一边。所以你的应用程序代码可以是 100% 纯的,只要你有一种相对简单的副作用函数库。

因此,例如,要实现一个将行列表写入带有 Effects 的文件的函数,您需要执行如下操作:

@do
def write_lines_to_file(lines, filename):
    file_handle = yield open_file(filename)
    for line in lines:
        yield write_data(file_handle, line)
    # alternatively:
    # from effect.fold import sequence; from functools import partial
    # yield sequence(map(partial(write_data, file_handle), lines))
    yield close_file(file_handle)

Effect 库提供了这个特殊的 do 装饰器,让您可以使用命令式语法来描述纯粹的有效操作。上面的功能等同于这个:

def write_lines_to_file(lines, filename):
    file_handle_eff = open_file(filename).on(
        lambda file_handle:
            sequence(map(partial(write_data, file_handle), lines)).on(
                lambda _: close_file(file_handle)))

它们都假定存在三个函数:open_file、write_data 和 close_file。假定这些函数返回表示执行这些操作的意图的 Effect 对象。最后,Effect 本质上是一个意图(对请求的操作的一些透明描述),以及一个或多个回调,以便在该操作的结果完成时运行。有趣的区别是 write_lines_to_file 并不实际上将行写入文件;它只是返回将一些行写入文件的意图的一些表示。

要实际执行此效果,您需要使用 sync_perform 函数,例如 sync_perform(dispatcher, write_lines_to_file(lines, filename))。这是一个不纯的函数,它实际上运行执行者以获得有效计算的纯表​​示所使用的所有效果。

我可以深入了解需要如何实现 open_file、write_data 和 close_file 的细节,以及“dispatcher”参数的细节,但实际上文档位于 https://effect.readthedocs.org/。在这一点上可能是正确的引用。

我还在 Strange Loop 上发表了关于 Effect 及其实现的演讲,您可以在 YouTube 上观看:https://www.youtube.com/watch?v=D37dc9EoFus

值得注意的是,Effect 是一种非常笨拙的方法来保持代码的纯功能性。通过采用“函数式核心/命令式外壳”方法并尽最大努力将大部分代码编写为纯函数并尽量减少有效代码,您可以在代码可维护性方面取得长足进步。但如果您对更严格的方法感兴趣,我认为 Effect 很好。我的团队在生产中使用它,它帮了很多忙,尤其是在测试 API 方面。

关于Python:以函数式编程风格编写文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33107541/

相关文章:

c - 在c中更新文件记录的最佳方法是什么?

Rosetta Stone 上的 C 快速中值滤波器(C I/O 和图像)

Java:网络中的 EOFException

python - Python 中 __future__ 和 swagger_client 的问题

python - 为什么 str.count ('' ) 和 len(str) 给出不同的输出?

python - python中的子进程不产生输出

python - 如何像计算机科学家一样思考

python - 如何在python中合并多个coco json文件

python - 未找到数据源名称且无默认驱动程序错误

python - python中对象和实例的区别?