Python 为什么我可以将实例方法传递给multiprocessing.Process,而不是multiprocessing.Pool?

Python 为什么我可以将实例方法传递给multiprocessing.Process,而不是multiprocessing.Pool?,python,python-2.7,multiprocessing,pickle,Python,Python 2.7,Multiprocessing,Pickle,我正在尝试编写一个应用程序,它与多处理.Pool同时应用函数。我希望这个函数是一个实例方法(因此我可以在不同的子类中对它进行不同的定义)。这似乎是不可能的;显然,正如我在其他地方学到的那样。那么,为什么以绑定方法作为目标启动多处理.Process?以下代码: import multiprocessing def test1(): print "Hello, world 1" def increment(x): return x + 1 class testClass():

我正在尝试编写一个应用程序,它与
多处理.Pool
同时应用函数。我希望这个函数是一个实例方法(因此我可以在不同的子类中对它进行不同的定义)。这似乎是不可能的;显然,正如我在其他地方学到的那样。那么,为什么以绑定方法作为目标启动
多处理.Process
?以下代码:

import multiprocessing

def test1():
    print "Hello, world 1"

def increment(x):
    return x + 1

class testClass():
    def process(self):
        process1 = multiprocessing.Process(target=test1)
        process1.start()
        process1.join()
        process2 = multiprocessing.Process(target=self.test2)
        process2.start()
        process2.join()

    def pool(self):
        pool = multiprocessing.Pool(1)
        for answer in pool.imap(increment, range(10)):
            print answer
        print
        for answer in pool.imap(self.square, range(10)):
            print answer

    def test2(self):
        print "Hello, world 2"

    def square(self, x):
        return x * x

def main():
    c = testClass()
    c.process()
    c.pool()

if __name__ == "__main__":
    main()
生成此输出:

Hello, world 1
Hello, world 2
1
2
3
4
5
6
7
8
9
10

Exception in thread Thread-2:
Traceback (most recent call last):
  File "C:\Python27\Lib\threading.py", line 551, in __bootstrap_inner
    self.run()
  File "C:\Python27\Lib\threading.py", line 504, in run
    self.__target(*self.__args, **self.__kwargs)
  File "C:\Python27\Lib\multiprocessing\pool.py", line 319, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup __builtin__.instancemethod failed
你好,世界1
你好,世界2
1.
2.
3.
4.
5.
6.
7.
8.
9
10
线程2中的异常:
回溯(最近一次呼叫最后一次):
文件“C:\Python27\Lib\threading.py”,第551行,在引导程序内部
self.run()
文件“C:\Python27\Lib\threading.py”,第504行,正在运行
自我目标(*自我参数,**自我参数)
文件“C:\Python27\Lib\multiprocessing\pool.py”,第319行,在任务处理中
放置(任务)
PicklingError:无法pickle:属性查找\内置\实例方法失败

为什么进程可以处理绑定的方法,但不能处理池?

pickle模块通常不能pickle实例方法:

>>> import pickle
>>> class A(object):
...  def z(self): print "hi"
... 
>>> a = A()
>>> pickle.dumps(a.z)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/pickle.py", line 1374, in dumps
    Pickler(file, protocol).dump(obj)
  File "/usr/local/lib/python2.7/pickle.py", line 224, in dump
    self.save(obj)
  File "/usr/local/lib/python2.7/pickle.py", line 306, in save
    rv = reduce(self.proto)
  File "/usr/local/lib/python2.7/copy_reg.py", line 70, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle instancemethod objects
您可以使用模块复制此功能,以查看其是否适合自己:

>>> import copy_reg
>>> def _reduce_method(m):
...     if m.im_self is None:
...         return getattr, (m.im_class, m.im_func.func_name)
...     else:
...         return getattr, (m.im_self, m.im_func.func_name)
... 
>>> copy_reg.pickle(type(a.z), _reduce_method)
>>> pickle.dumps(a.z)
"c__builtin__\ngetattr\np0\n(ccopy_reg\n_reconstructor\np1\n(c__main__\nA\np2\nc__builtin__\nobject\np3\nNtp4\nRp5\nS'z'\np6\ntp7\nRp8\n."
使用
Process.start
在Windows上生成新进程时:

请注意“向孩子发送信息”部分。它使用
dump
函数,该函数使用
ForkingPickler
来pickle数据,这意味着您的实例方法可以被pickle

现在,当您使用
multiprocessing.Pool
上的方法向子进程发送方法时,它使用
multiprocessing.Pipe
来pickle数据。在Python2.7中,
multiprocessing.Pipe
是用C实现的,因此它没有利用
ForkingPickler
。这意味着酸洗实例方法不起作用

但是,如果使用
copy_reg
注册
实例方法
类型,而不是自定义
酸洗器
,则所有酸洗尝试都将受到影响。因此,您可以使用它来启用pickle实例方法,甚至可以通过

import multiprocessing
import copy_reg
import types

def _reduce_method(m):
    if m.im_self is None:
        return getattr, (m.im_class, m.im_func.func_name)
    else:
        return getattr, (m.im_self, m.im_func.func_name)
copy_reg.pickle(types.MethodType, _reduce_method)

def test1():
    print("Hello, world 1")

def increment(x):
    return x + 1

class testClass():
    def process(self):
        process1 = multiprocessing.Process(target=test1)
        process1.start()
        process1.join()
        process2 = multiprocessing.Process(target=self.test2)
        process2.start()
        process2.join()

    def pool(self):
        pool = multiprocessing.Pool(1)
        for answer in pool.imap(increment, range(10)):
            print(answer)
        print
        for answer in pool.imap(self.square, range(10)):
            print(answer)

    def test2(self):
        print("Hello, world 2")

    def square(self, x):
        return x * x

def main():
    c = testClass()
    c.process()
    c.pool()

if __name__ == "__main__":
    main()
输出:

Hello, world 1
Hello, world 2
GOT (0, 0, (True, 1))
GOT (0, 1, (True, 2))
GOT (0, 2, (True, 3))
GOT (0, 3, (True, 4))
GOT (0, 4, (True, 5))
 1GOT (0, 5, (True, 6))

GOT (0, 6, (True, 7))
2
GOT (0, 7, (True, 8))
3
 GOT (0, 8, (True, 9))
GOT (0, 9, (True, 10))
4
5
6
7
8
9
10

GOT (1, 0, (True, 0))
0
GOT (1, 1, (True, 1))
1
GOT (1, 2, (True, 4))
4
GOT (1, 3, (True, 9))
9
 GOT (1, 4, (True, 16))
16
GOT (1, 5, (True, 25))
25
 GOT (1, 6, (True, 36))
36
 GOT (1, 7, (True, 49))
49
 GOT (1, 8, (True, 64))
64
GOT (1, 9, (True, 81))
81
GOT None

还要注意的是,在Python3.x中,
pickle
可以本地pickle实例方法类型,因此这些内容不再重要了

这里有一个我有时使用的替代方案,它在Python2.x中工作:

您可以创建一个顶级“别名”,对实例方法进行排序,该别名接受要在池中运行其实例方法的对象,并让它为您调用实例方法:

import functools
import multiprocessing

def _instance_method_alias(obj, arg):
    """
    Alias for instance method that allows the method to be called in a 
    multiprocessing pool
    """
    obj.instance_method(arg)
    return

class MyClass(object):
    """
    Our custom class whose instance methods we want to be able to use in a 
    multiprocessing pool
    """

    def __init__(self):
        self.my_string = "From MyClass: {}"

    def instance_method(self, arg):
        """
        Some arbitrary instance method
        """

        print(self.my_string.format(arg))
        return

# create an object of MyClass
obj = MyClass()

# use functools.partial to create a new method that always has the 
# MyClass object passed as its first argument
_bound_instance_method_alias = functools.partial(_instance_method_alias, obj)

# create our list of things we will use the pool to map
l = [1,2,3]

# create the pool of workers
pool = multiprocessing.Pool()

# call pool.map, passing it the newly created function
pool.map(_bound_instance_method_alias, l)

# cleanup
pool.close()
pool.join()
此代码生成以下输出:

Hello, world 1
Hello, world 2
1
2
3
4
5
6
7
8
9
10

Exception in thread Thread-2:
Traceback (most recent call last):
  File "C:\Python27\Lib\threading.py", line 551, in __bootstrap_inner
    self.run()
  File "C:\Python27\Lib\threading.py", line 504, in run
    self.__target(*self.__args, **self.__kwargs)
  File "C:\Python27\Lib\multiprocessing\pool.py", line 319, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup __builtin__.instancemethod failed
来自MyClass:1
来自MyClass:2
来自我的班级:3


一个限制是您不能将其用于修改对象的方法。每个进程都会获得它调用方法的对象的副本,因此更改不会传播回主进程。如果您不需要修改正在调用的方法中的对象,那么这是一个简单的解决方案。

在Python 2中,有一种更简单的方法,只需包装原始的实例方法。经测试,Python2.7在macOS和Linux上运行良好,在Windows上不起作用

from multiprocessing import Pool

class Person(object):
    def __init__(self):
        self.name = 'Weizhong Tu'

    def calc(self, x):
        print self.name
        return x ** 5


def func(x, p=Person()):
    return p.calc(x)


pool = Pool()
print pool.map(func, range(10))

这是因为它们不能被
pickle
序列化。如果您需要使用python2.7,并且需要让代码按原样工作……您应该使用
多处理
分支,它可以pickle实例方法,也可以pickle
。查看
pathos.multiprocessing
,您可以在上面文章中引用的stackoverflow链接中找到它。更具体地说,该链接显示了如何在
池中轻松序列化2.x中的实例方法:它必须是实例方法吗?你能使用classmethod吗?我试过了,效果很好;我很惊讶多处理模块还没有实现这一点。您的精确解决方案对我不起作用,因为它涉及到对方法绑定的实例进行酸洗,这会导致其他问题,但它为我指明了正确的方向。相反,我定义了在模块顶层的多处理过程中要运行的方法,以避免这两个问题并获得我想要的行为。感谢这篇文章,在深入研究酸洗等问题后对我有意义,这对我来说很有用。Python3将(最终)弥合这一差距。干杯
from multiprocessing import Pool

class Person(object):
    def __init__(self):
        self.name = 'Weizhong Tu'

    def calc(self, x):
        print self.name
        return x ** 5


def func(x, p=Person()):
    return p.calc(x)


pool = Pool()
print pool.map(func, range(10))