多处理时如何使 Python 尊重可迭代字段?
How do I make Python respect iterable fields when multiprocessing?
如果这是一个愚蠢的问题,我深表歉意,但我还没有找到解决此问题的完美方法。基本上,当使用 concurent.futures 模块时,类 的非静态方法看起来应该可以正常工作,我在该模块的文档中没有看到任何暗示它们无法正常工作的内容,并且模块在 运行 时不会产生错误 - 在许多情况下甚至会产生预期的结果!
但是,我注意到该模块似乎不尊重父线程中对可迭代字段的更新,即使这些更新发生在启动任何子进程之前也是如此。这是我的意思的一个例子:
import concurrent.futures
class Thing:
data_list = [0, 0, 0]
data_number = 0
def foo(self, num):
return sum(self.data_list) * num
def bar(self, num):
return num * self.data_number
if __name__ == '__main__':
thing = Thing()
thing.data_list[0] = 1
thing.data_number = 1
with concurrent.futures.ProcessPoolExecutor() as executor:
results = executor.map(thing.foo, range(3))
print('result of changing list:')
for result in results:
print(result)
results = executor.map(thing.bar, range(3))
print('result of changing number:')
for result in results:
print(result)
我希望这里的结果是
result of changing list:
0
1
2
result of changing number:
0
1
2
但我得到
result of changing list:
0
0
0
result of changing number:
0
1
2
因此,出于某种原因,对于只是一个整数的字段,一切都按预期工作,但对于作为列表的字段,则完全不像预期的那样。这意味着在调用子进程时不会考虑对列表所做的更新,即使对更简单字段的更新也是如此。我已经用字典尝试过同样的问题,我怀疑这对所有可迭代对象都是一个问题。
有什么方法可以使它按预期工作,从而允许子进程尊重对可迭代字段的更新?非静态方法的多处理将像这样半实现似乎很奇怪,但我希望我只是遗漏了一些东西!
这个问题与“尊重可迭代字段”无关,但这是一个相当微妙的问题。在您的主要流程中,您有:
thing.data_list[0] = 1 # first assignment
thing.data_number = 1 # second assignmment
而不是:
Thing.data_list[0] = 1 # first assignment
Thing.data_number = 1 # second assignment
就第一个赋值而言,没有任何 material 区别,因为无论是哪个版本,您都不是在修改 class 属性,而是修改列表中恰好由 class 属性引用。换句话说,Thing.data_list
仍然指向同一个列表;此引用未更改。这是一个重要的区别。
但是在您的代码版本的第二次赋值中,您基本上通过实例的 self
引用修改了 class 属性。当您这样做时,您正在 创建一个具有相同名称 data_number.
的新实例属性
您的 class 成员函数 foo
和 bar
试图 通过 self
访问 class 属性. Thing
实例 thing
将被 pickled 跨越到新地址 space 但在新地址 space 中 Thing
是 un-pickled,默认情况下将创建新的 class 属性并将其初始化为其默认值,除非您添加特殊的 pickle 规则。但是实例属性应该是传输成功的,比如你新建的data_number
。这就是 'result of changing number:' 按预期打印的原因,即您实际上正在访问 bar
.
中的 instance 属性 data_number
将 bar
更改为以下内容,您将看到所有内容都将打印为 0
:
def bar(self, num):
return num * Thing.data_number
如果这是一个愚蠢的问题,我深表歉意,但我还没有找到解决此问题的完美方法。基本上,当使用 concurent.futures 模块时,类 的非静态方法看起来应该可以正常工作,我在该模块的文档中没有看到任何暗示它们无法正常工作的内容,并且模块在 运行 时不会产生错误 - 在许多情况下甚至会产生预期的结果!
但是,我注意到该模块似乎不尊重父线程中对可迭代字段的更新,即使这些更新发生在启动任何子进程之前也是如此。这是我的意思的一个例子:
import concurrent.futures
class Thing:
data_list = [0, 0, 0]
data_number = 0
def foo(self, num):
return sum(self.data_list) * num
def bar(self, num):
return num * self.data_number
if __name__ == '__main__':
thing = Thing()
thing.data_list[0] = 1
thing.data_number = 1
with concurrent.futures.ProcessPoolExecutor() as executor:
results = executor.map(thing.foo, range(3))
print('result of changing list:')
for result in results:
print(result)
results = executor.map(thing.bar, range(3))
print('result of changing number:')
for result in results:
print(result)
我希望这里的结果是
result of changing list:
0
1
2
result of changing number:
0
1
2
但我得到
result of changing list:
0
0
0
result of changing number:
0
1
2
因此,出于某种原因,对于只是一个整数的字段,一切都按预期工作,但对于作为列表的字段,则完全不像预期的那样。这意味着在调用子进程时不会考虑对列表所做的更新,即使对更简单字段的更新也是如此。我已经用字典尝试过同样的问题,我怀疑这对所有可迭代对象都是一个问题。
有什么方法可以使它按预期工作,从而允许子进程尊重对可迭代字段的更新?非静态方法的多处理将像这样半实现似乎很奇怪,但我希望我只是遗漏了一些东西!
这个问题与“尊重可迭代字段”无关,但这是一个相当微妙的问题。在您的主要流程中,您有:
thing.data_list[0] = 1 # first assignment
thing.data_number = 1 # second assignmment
而不是:
Thing.data_list[0] = 1 # first assignment
Thing.data_number = 1 # second assignment
就第一个赋值而言,没有任何 material 区别,因为无论是哪个版本,您都不是在修改 class 属性,而是修改列表中恰好由 class 属性引用。换句话说,Thing.data_list
仍然指向同一个列表;此引用未更改。这是一个重要的区别。
但是在您的代码版本的第二次赋值中,您基本上通过实例的 self
引用修改了 class 属性。当您这样做时,您正在 创建一个具有相同名称 data_number.
您的 class 成员函数 foo
和 bar
试图 通过 self
访问 class 属性. Thing
实例 thing
将被 pickled 跨越到新地址 space 但在新地址 space 中 Thing
是 un-pickled,默认情况下将创建新的 class 属性并将其初始化为其默认值,除非您添加特殊的 pickle 规则。但是实例属性应该是传输成功的,比如你新建的data_number
。这就是 'result of changing number:' 按预期打印的原因,即您实际上正在访问 bar
.
data_number
将 bar
更改为以下内容,您将看到所有内容都将打印为 0
:
def bar(self, num):
return num * Thing.data_number