【问题标题】:Unable to pass an lxml etree object to a separate process无法将 lxml etree 对象传递给单独的进程
【发布时间】:2014-11-17 11:14:54
【问题描述】:

我正在开发一个项目,以使用 lxml 在 python 中同时解析多个 xml 文件。当我初始化进程时,我希望我的主类在将 etree 对象传递给进程之前对 XML 做一些工作,但我发现当 etree 对象到达新进程时,类仍然存在,但 XML 从在对象中,getroot() 返回 None。

我知道我只能使用队列传递可选择的数据,但我在“args”字段中传递给进程的内容也是如此吗?

这是我的代码:

import multiprocessing, multiprocessing.pool, time
from lxml import etree

def compute(tree):
    print("Start Process")
    print(type(tree)) # Returns <class 'lxml.etree._ElementTree'>
    print(id(tree)) # Returns new ID 44637320 as expected
    print(tree.getroot()) # Returns None

def pool_init(queue):
    # see http://stackoverflow.com/a/3843313/852994
    compute.queue = queue

class Main():
    def __init__(self):
        pass

    def main(self):
        tree = etree.parse('test.xml')
        print(id(tree)) # Returns object ID 43998536
        print(tree.getroot()) #Returns <Element SymCLI_ML at 0x29f5dc8>

        self.queue = multiprocessing.Queue()
        self.pool = multiprocessing.Pool(processes=1, initializer=pool_init, initargs=(self.queue,))
        self.pool.apply_async(func=compute, args=(tree,))
        time.sleep(10)

if __name__ == '__main__':
    Main().main()

非常感谢任何和所有帮助。

更新/回答

根据下一篇文章中的答案,我对其进行了一些修改,并设法使其在不使用 String IO 的情况下以更低的内存占用工作。 etree.tostring 方法返回一个字节数组,它可以被腌制,然后解除腌制,字节数组可以被 etree 解析。

import multiprocessing, multiprocessing.pool, time, copyreg
from lxml import etree

def compute(tree):
    print("Start Process")
    print(type(tree)) # Returns <class 'lxml.etree._ElementTree'>
    print(tree.getroot()) # Returns <Element SymCLI_ML at 0x29f5dc8>. Success!

def pool_init(queue):
    # see http://stackoverflow.com/a/3843313/852994
    compute.queue = queue

def elementtree_unpickler(data):
    return etree.parse(BytesIO(data))

def elementtree_pickler(tree):
    return elementtree_unpickler, (etree.tostring(tree),)

copyreg.pickle(etree._ElementTree, elementtree_pickler, elementtree_unpickler)

class Main():
    def __init__(self):
        pass

    def main(self):
        tree = etree.parse('test.xml')
        print(tree.getroot()) #Returns <Element SymCLI_ML at 0x29f5dc8>

        self.queue = multiprocessing.Queue()
        self.pool = multiprocessing.Pool(processes=1, initializer=pool_init, initargs=(self.queue,))
        self.pool.apply_async(func=compute, args=(tree,))
        time.sleep(10)

if __name__ == '__main__':
    Main().main()

更新 2

在对内存进行了一些基准测试后,我发现传递大对象会导致对象无法通过主进程的垃圾回收来清除。这可能不是小规模的问题,但 etree 对象在内存中的数量级为数百 MB。一旦在语句中使用 XML 对象调用异步任务,如果从主进程中删除该对象,即使我手动调用垃圾收集,也无法从内存中清除该对象。因此,我恢复为在主进程中关闭 XML 并将文件名传递给子进程。

【问题讨论】:

  • 是否可以将 etree 对象放在共享内存中并将对共享内存位置的引用传递给子进程?

标签: python lxml python-multiprocessing


【解决方案1】:

使用以下代码为 lxml Element/ElementTree 对象注册简单的picklers/unpicklers。我过去在 lxml 和 zmq 中使用过它。

import copy_reg
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO
from lxml import etree

def element_unpickler(data):
    return etree.fromstring(data)

def element_pickler(element):
    data = etree.tostring(element)
    return element_unpickler, (data,)

copy_reg.pickle(etree._Element, element_pickler, element_unpickler)

def elementtree_unpickler(data):
    data = StringIO(data)
    return etree.parse(data)

def elementtree_pickler(tree):
    data = StringIO()
    tree.write(data)
    return elementtree_unpickler, (data.getvalue(),)

copy_reg.pickle(etree._ElementTree, elementtree_pickler, elementtree_unpickler)

【讨论】:

  • 我已经添加了这个(python 3.4,所以 copy_reg 是 copyreg 并且 StringIO 导入是 'from is import StringIO'),但是在启动进程的行上我得到了 'I/O 错误:写错误'。修改初始问题的完整代码。
猜你喜欢
  • 2012-08-17
  • 1970-01-01
  • 2021-05-29
  • 2015-11-16
  • 1970-01-01
  • 2014-03-13
  • 2017-07-04
  • 2014-09-04
  • 1970-01-01
相关资源
最近更新 更多