Python中使用本地函数进行多处理的变通方法? 具有本地定义函数的多处理?

Python中使用本地函数进行多处理的变通方法? 具有本地定义函数的多处理?,python,multiprocessing,Python,Multiprocessing,我正在为一个对外部依赖性非常挑剔的客户移植一个库 ProcessPool模块支持此库中的大多数多处理。主要原因是它可以很容易地处理本地定义的函数 我试图在不强制依赖或重写库的大块内容的情况下恢复一些功能。我理解以下代码之所以有效,是因为函数是在顶层定义的: import multiprocessing as mp def f(x): return x * x def main(): with mp.Pool(5) as p: print(p.map(f,

我正在为一个对外部依赖性非常挑剔的客户移植一个库

ProcessPool模块支持此库中的大多数多处理。主要原因是它可以很容易地处理本地定义的函数

我试图在不强制依赖或重写库的大块内容的情况下恢复一些功能。我理解以下代码之所以有效,是因为函数是在顶层定义的:

import multiprocessing as mp


def f(x):
    return x * x


def main():
    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))


if __name__ == "__main__":
    main()
我需要的以下代码无法正常工作,因为函数仅在本地范围内定义:

import multiprocessing as mp


def main():
    def f(x):
        return x * x

    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))


if __name__ == "__main__":
    main()
有谁知道这个不需要外部依赖性的特定用例的良好解决方案?谢谢你的阅读

更新: 有一个使用fork的解决方案,但这对Mac和Windows不安全,谢谢@Monica和@user2357112。 @Blop提供了一个非常好的建议,对许多人都适用。在我的例子中,不是上面的玩具示例,我的生成器中的对象是无法识别的。 @amsh提供了一个似乎适用于任何函数+生成器的解决方案。虽然这是一个很好的选择,但缺点是它需要在全局范围内定义函数。 原始答案

免责声明:如果您希望在本地定义函数以更好地管理代码,但对其全局范围没有问题,则此答案适用

在定义函数之前,可以使用全局关键字。它将解决函数的pickle问题,因为它现在是一个全局函数,同时在局部范围内定义它

import multiprocessing as mp

def main():
    global f
    def f(x):
        return x * x

    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))

if __name__ == "__main__":
    main()
    print(f(4)) #Inner function is available here as well.
输出:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
16
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
64
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Traceback (most recent call last):
  File "<file>.py", line 25, in <module>
    print(f(4))
NameError: name 'f' is not defined
添加具有相同名称的多个函数的另一个示例,每个后续函数将覆盖前一个函数

import multiprocessing as mp

def main():
    global f
    def f(x):
        return x * x

    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))

def main2():
    global f
    def f(x):
        return x * x * x

    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))

if __name__ == "__main__":
    main()
    main2()
    print(f(4))

输出:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
16
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
64
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Traceback (most recent call last):
  File "<file>.py", line 25, in <module>
    print(f(4))
NameError: name 'f' is not defined
最新答案

调用映射后,撤消全局状态。感谢@KCQs在评论中的提示

为了确保全局函数不会对代码的其余部分造成任何问题,您可以简单地为全局函数添加del语句来撤销它们的全局状态

import multiprocessing as mp

def main():
    global f
    def f(x):
        return x * x

    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))
    del f

if __name__ == "__main__":
    main()
    print(f(4)) #Inner function is not available.
输出:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
16
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
64
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Traceback (most recent call last):
  File "<file>.py", line 25, in <module>
    print(f(4))
NameError: name 'f' is not defined
虽然python会自动收集垃圾,但您也可以。

原始答案

免责声明:如果您希望在本地定义函数以更好地管理代码,但对其全局范围没有问题,则此答案适用

在定义函数之前,可以使用全局关键字。它将解决函数的pickle问题,因为它现在是一个全局函数,同时在局部范围内定义它

import multiprocessing as mp

def main():
    global f
    def f(x):
        return x * x

    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))

if __name__ == "__main__":
    main()
    print(f(4)) #Inner function is available here as well.
输出:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
16
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
64
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Traceback (most recent call last):
  File "<file>.py", line 25, in <module>
    print(f(4))
NameError: name 'f' is not defined
添加具有相同名称的多个函数的另一个示例,每个后续函数将覆盖前一个函数

import multiprocessing as mp

def main():
    global f
    def f(x):
        return x * x

    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))

def main2():
    global f
    def f(x):
        return x * x * x

    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))

if __name__ == "__main__":
    main()
    main2()
    print(f(4))

输出:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
16
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
64
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Traceback (most recent call last):
  File "<file>.py", line 25, in <module>
    print(f(4))
NameError: name 'f' is not defined
最新答案

调用映射后,撤消全局状态。感谢@KCQs在评论中的提示

为了确保全局函数不会对代码的其余部分造成任何问题,您可以简单地为全局函数添加del语句来撤销它们的全局状态

import multiprocessing as mp

def main():
    global f
    def f(x):
        return x * x

    with mp.Pool(5) as p:
        print(p.map(f, [i for i in range(10)]))
    del f

if __name__ == "__main__":
    main()
    print(f(4)) #Inner function is not available.
输出:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
16
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
64
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Traceback (most recent call last):
  File "<file>.py", line 25, in <module>
    print(f(4))
NameError: name 'f' is not defined

尽管python会自动收集垃圾,但您也可能会发现。

主要问题是闭包变量

如果您没有这些,可以这样做:

导入封送员 导入多处理 导入类型 从functools导入部分 def主: def内部功能: 返回c*c 使用multiprocessing.Pool5作为池: printinternal_func_mappool,internal_func,[i代表范围10中的i] def内部功能映射池,f,gen: 封送=封送.转储SF.\uu代码__ return pool.mappartialrun_func,封送=封送,gen def run_func*参数,**kwargs: 已编组=kwargs.popmarched func=marshal.loadsmarshaled 已还原\u f=types.FunctionTypefunc,全局 返回已恢复的参数,**kwargs 如果uuuu name uuuuu==\uuuuuuuu main\uuuuuuuu: 主要的 其思想是函数代码拥有在新进程中运行所需的一切。请注意,不需要外部依赖项,只需要常规的python库

如果确实需要闭包,那么这个解决方案最困难的部分就是创建闭包。 在闭包中,有一种称为单元的东西,它不太容易通过代码创建

下面是有点复杂的工作代码:

导入封送员 导入多处理 进口泡菜 导入类型 从functools导入部分 A类: 定义初始自我,a: self.a=a def主: x=A1 def内部功能: 返回x.a+c 使用multiprocessing.Pool5作为池: printinternal_func_mappool,internal_func,[i代表范围10中的i] def内部功能映射池,f,gen: 闭包=f.\u闭包__ 封送\u func=封送.dumpsf.\u代码__ pickled_closure=closure中x的pickle.dumpstuplex.cell_内容 return pool.mappartialrun\u func,封送\u func=marshaled\u func,pickled\u closure=pickled\u closure,gen def run_func*参数,**kwargs: marshaled_func=kwargs.popmarshaled_func func=marshal.loadsmarshaled_func 腌制闭包=kwargs.poppickled\u closu 重新 closure=pickle.loadspickled\u闭包 已还原\u f=types.FunctionTypefunc,全局,闭包=创建\u closurefunc,闭包 返回已恢复的参数,**kwargs def create_closurefunc,原始_闭包: 缩进=*4 func.co_freevars中name的closure_vars_def=f\n{indent}.joinf{name}=None 闭包变量引用=,.joinfunc.co\u freevars 动态闭包=创建动态闭包 s=f def{dynamic_closure}: {closure_vars_def} def内部: {closure_vars_ref} 返回内部。\u\u关闭__ 执行官 创建的\u闭包=局部变量[动态\u闭包] 对于闭包_var,zipcreated_闭包中的值,原始_闭包: 闭包变量单元格内容=值 返回创建的U闭包 如果uuuu name uuuuu==\uuuuuuuu main\uuuuuuuu: 主要的
希望这有助于或至少给你一些如何解决这个问题的想法

主要问题是闭包变量

如果您没有这些,可以这样做:

导入封送员 导入多处理 导入类型 从functools导入部分 def主: def内部功能: 返回c*c 使用multiprocessing.Pool5作为池: printinternal_func_mappool,internal_func,[i代表范围10中的i] def内部功能映射池,f,gen: 封送=封送.转储SF.\uu代码__ return pool.mappartialrun_func,封送=封送,gen def run_func*参数,**kwargs: 已编组=kwargs.popmarched func=marshal.loadsmarshaled 已还原\u f=types.FunctionTypefunc,全局 返回已恢复的参数,**kwargs 如果uuuu name uuuuu==\uuuuuuuu main\uuuuuuuu: 主要的 其思想是函数代码拥有在新进程中运行所需的一切。请注意,不需要外部依赖项,只需要常规的python库

如果确实需要闭包,那么这个解决方案最困难的部分就是创建闭包。 在闭包中,有一种称为单元的东西,它不太容易通过代码创建

下面是有点复杂的工作代码:

导入封送员 导入多处理 进口泡菜 导入类型 从functools导入部分 A类: 定义初始自我,a: self.a=a def主: x=A1 def内部功能: 返回x.a+c 使用multiprocessing.Pool5作为池: printinternal_func_mappool,internal_func,[i代表范围10中的i] def内部功能映射池,f,gen: 闭包=f.\u闭包__ 封送\u func=封送.dumpsf.\u代码__ pickled_closure=closure中x的pickle.dumpstuplex.cell_内容 return pool.mappartialrun\u func,封送\u func=marshaled\u func,pickled\u closure=pickled\u closure,gen def run_func*参数,**kwargs: marshaled_func=kwargs.popmarshaled_func func=marshal.loadsmarshaled_func pickled_closure=kwargs.poppickled_closure closure=pickle.loadspickled\u闭包 已还原\u f=types.FunctionTypefunc,全局,闭包=创建\u closurefunc,闭包 返回已恢复的参数,**kwargs def create_closurefunc,原始_闭包: 缩进=*4 func.co_freevars中name的closure_vars_def=f\n{indent}.joinf{name}=None 闭包变量引用=,.joinfunc.co\u freevars 动态闭包=创建动态闭包 s=f def{dynamic_closure}: {closure_vars_def} def内部: {closure_vars_ref} 返回内部。\u\u关闭__ 执行官 创建的\u闭包=局部变量[动态\u闭包] 对于闭包_var,zipcreated_闭包中的值,原始_闭包: 闭包变量单元格内容=值 返回创建的U闭包 如果uuuu name uuuuu==\uuuuuuuu main\uuuuuuuu: 主要的
希望这有助于或至少给你一些如何解决这个问题的想法

这不是一个3.8特定的问题。在此之前,非繁殖startmethods在Mac上是不安全的。3.8只是更改默认值的版本。现在,Fork-without-exec通常不是一个好主意-使Fork成为Mac上不安全的startmethod的线程安全问题不是Mac特有的。它们在Mac上更为普遍。我添加了一个答案,同时假设将函数保留在本地范围仅用于代码管理目的,如果这些本地定义的函数具有全局范围,则可以接受。您可以确认此假设是否成立。谢谢这不是一个3.8特定的问题。在此之前,非繁殖startmethods在Mac上是不安全的。3.8只是更改默认值的版本。现在,Fork-without-exec通常不是一个好主意-使Fork成为Mac上不安全的startmethod的线程安全问题不是Mac特有的。它们在Mac上更为普遍。我添加了一个答案,同时假设将函数保留在本地范围仅用于代码管理目的,如果这些本地定义的函数具有全局范围,则可以接受。您可以确认此假设是否成立。谢谢你的回答,这是我自己永远不会想到的解决办法!在我自己的问题上尝试了这个之后,我得到了一个错误,即包中的自定义对象与我收到的错误封送不兼容
is ValueError:不可编组的对象。经过更多的挖掘,似乎唯一的解决办法是外包装莳萝?也许你知道解决这个错误的方法。嘿@KCQs,我已经编辑了代码以支持更广泛的对象。。。你现在能检查一下吗?谢谢@Blop,这大约有一半的时间有效,太棒了!看起来失败的情况是由于内部函数引用了顶级函数,我本以为顶级函数可以被封送的函数访问,但显然不能。看起来我需要为内部函数中引用的函数创建一个闭包。谢谢你的回答,这是一个我自己永远不会想到的解决方案!在我自己的问题上尝试了这个之后,我得到了一个错误,我们包中的自定义对象与封送不兼容。我收到的错误是ValueError:unmarshallable object。经过更多的挖掘,似乎唯一的解决办法是外包装莳萝?也许你知道解决这个错误的方法。嘿@KCQs,我已经编辑了代码以支持更广泛的对象。。。你现在能检查一下吗?谢谢@Blop,这大约有一半的时间有效,太棒了!看起来失败的情况是由于内部函数引用了顶级函数,我本以为顶级函数可以被封送的函数访问,但显然不能。看起来我需要为内部函数中引用的函数创建一个闭包。谢谢你的回答!我希望避免将函数放入全局范围,因为这是一个相当大的包,我不想引入可能破坏我不知道的东西的代码。但也许我对全局函数过于悲观。是否有处理全局功能的最佳实践?我是否应该将函数名设置为一长串随机字符以避免重叠?调用地图后我可以释放内存吗?etc@KCQs,谢谢你在评论中的提示。事实上,我们可以确保在调用map之后,我们的全局函数不会保持全局。我已经更新了我的答案,添加了相关示例。谢谢当我尝试此操作时,它仅在全局范围中已经定义了一个f并且它使用该定义的情况下才起作用,即您的确切代码加上缺少的导入失败,属性错误:无法获取属性“f”,谢谢您的回答!我希望避免将函数放入全局范围,因为这是一个相当大的包,我不想引入可能破坏我不知道的东西的代码。但也许我对全局函数过于悲观。是否有处理全局功能的最佳实践?我是否应该将函数名设置为一长串随机字符以避免重叠?调用地图后我可以释放内存吗?etc@KCQs,谢谢你在评论中的提示。事实上,我们可以确保在调用map之后,我们的全局函数不会保持全局。我已经更新了我的答案,添加了相关示例。谢谢当我尝试此操作时,它仅在全局范围中已经定义了一个f并且它使用该定义的情况下才起作用,即您的确切代码加上缺少的导入会因AttributeError而失败:无法获取属性“f”