【问题标题】:Parallel and conditional: NoneType object has no attribute '__dict__'并行和条件:NoneType 对象没有属性“__dict__”
【发布时间】:2015-06-11 12:15:06
【问题描述】:

有关更多设置,请参阅this question。我想并行创建许多Toy 类的实例。然后我想把它们写到一个 xml 树中。

import itertools
import pandas as pd
import lxml.etree as et
import numpy as np
import sys
import multiprocessing as mp


def make_toys(df):
    l = []
    for index, row in df.iterrows():
        toys = [Toy(row) for _ in range(row['number'])]
        l += [x for x in toys if x is not None]
    return l


class Toy(object):
    def __new__(cls, *args, **kwargs):
        if np.random.uniform() <= 1:
            return super(Toy, cls).__new__(cls, *args, **kwargs)

    def __init__(self, row):
        self.id = None
        self.type = row['type']

    def set_id(self, x):
        self.id = x

    def write(self, tree):
        et.SubElement(tree, "toy", attrib={'id': str(self.id), 'type': self.type})


if __name__ == "__main__":
    table = pd.DataFrame({
        'type': ['a', 'b', 'c', 'd'],
        'number': [5, 4, 3, 10]})

    n_cores = 2
    split_df = np.array_split(table, n_cores)

    p = mp.Pool(n_cores)
    pool_results = p.map(make_toys, split_df)
    p.close()
    p.join()
    l = [a for L in pool_results for a in L]

    box = et.Element("box")
    box_file = et.ElementTree(box)

    for i, toy in itertools.izip(range(len(l)), l):
        Toy.set_id(toy, i)

    [Toy.write(x, box) for x in l]

    box_file.write(sys.stdout, pretty_print=True)

这段代码运行良好。但是我重新定义了__new__ 方法,使其只有随机机会实例化一个类。因此,如果我设置if np.random.uniform() &lt; 0.5,我想创建我要求的实例数量的一半,随机确定。这样做会返回以下错误:

Exception in thread Thread-3:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 763, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 380, in _handle_results
    task = get()
AttributeError: 'NoneType' object has no attribute '__dict__'

我什至不知道这意味着什么,或者如何避免它。如果我像l = make_toys(table) 那样单片地执行此过程,那么它对于任何随机机会都运行良好。

另一种解决方案

顺便说一句,我知道这可以通过单独保留 __new__ 方法来解决,而是将 make_toys() 重写为

def make_toys(df):
    l = []
    for index, row in df.iterrows():
        prob = np.random.binomial(row['number'], 0.1)
        toys = [Toy(row) for _ in range(prob)]
        l += [x for x in toys if x is not None]
    return l

但我正在尝试了解错误。

【问题讨论】:

  • 我不知道上一个答案发生了什么,但请注意,我正在从对象列表中删除 None 元素。

标签: python python-multiprocessing


【解决方案1】:

我想你已经发现了一个由Toy 引起的令人惊讶的“陷阱” 实例在通过多处理池时变为None 结果Queue。

multiprocessing.Pool 使用Queue.Queues 将结果从子进程传回主进程。

Per the docs:

当一个对象被放入队列时,对象被腌制并且背景 线程稍后将腌制数据刷新到底层管道。

虽然实际的序列化可能会有所不同,但在精神上 Toy 实例的酸洗变成了这样的字节流:

In [30]: import pickle

In [31]: pickle.dumps(Toy(table.iloc[0]))
Out[31]: "ccopy_reg\n_reconstructor\np0\n(c__main__\nToy\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS'type'\np6\nS'a'\np7\nsS'id'\np8\nNsb."

注意,对象的模块和类在流中提到 字节:__main__\nToy。

类本身没有被腌制。只有类名的引用。

当字节流在管道的另一端被解压时,Toy.__new__ 被调用来实例化Toy 的新实例。新对象的__dict__ 然后使用来自字节流的未腌制数据重构。当新对象为None时,它没有__dict__属性,因此会引发AttributeError。

因此,当Toy 实例通过Queue 时,它可能在另一侧变为None。

我相信这就是为什么使用

class Toy(object):
    def __new__(cls, *args, **kwargs):
        x = np.random.uniform() <= 0.5
        if x:
            return super(Toy, cls).__new__(cls, *args, **kwargs)
        logger.info('Returning None')

导致

AttributeError: 'NoneType' object has no attribute '__dict__'

如果您在脚本中添加日志记录,

import itertools
import pandas as pd
import lxml.etree as et
import numpy as np
import sys
import multiprocessing as mp
import logging
logger = mp.log_to_stderr(logging.INFO)

def make_toys(df):
    result = []
    for index, row in df.iterrows():
        toys = [Toy(row) for _ in range(row['number'])]
        result += [x for x in toys if x is not None]
    return result


class Toy(object):
    def __new__(cls, *args, **kwargs):
        x = np.random.uniform() <= 0.97
        if x:
            return super(Toy, cls).__new__(cls, *args, **kwargs)
        logger.info('Returning None')

    def __init__(self, row):
        self.id = None
        self.type = row['type']

    def set_id(self, x):
        self.id = x

    def write(self, tree):
        et.SubElement(tree, "toy", attrib={'id': str(self.id), 'type': self.type})


if __name__ == "__main__":
    table = pd.DataFrame({
        'type': ['a', 'b', 'c', 'd'],
        'number': [5, 4, 3, 10]})

    n_cores = 2
    split_df = np.array_split(table, n_cores)

    p = mp.Pool(n_cores)
    pool_results = p.map(make_toys, split_df)
    p.close()
    p.join()
    l = [a for L in pool_results for a in L]

    box = et.Element("box")
    box_file = et.ElementTree(box)

    for i, toy in itertools.izip(range(len(l)), l):
        toy.set_id(i)

    for x in l:
        x.write(box)

    box_file.write(sys.stdout, pretty_print=True)

你会发现只有AttributeError 在表单的日志消息之后发生

[INFO/MainProcess] Returning None

请注意,日志消息来自 MainProcess,而不是其中一个 PoolWorker 进程。由于Returning None 消息来自 Toy.__new__,这说明Toy.__new__被主进程调用了。 这证实了 unpickling 正在调用的说法 Toy.__new__ 并将Toy 的实例转换为None。


故事的寓意是Toy 实例要通过多处理池的队列,Toy.__new__ 必须始终返回一个实例 Toy。正如您所指出的,可以通过仅在 make_toys 中实例化所需数量的玩具来修复代码:

def make_toys(df):
    result = []
    for index, row in df.iterrows():
        prob = np.random.binomial(row['number'], 0.1)
        result.extend([Toy(row) for _ in range(prob)])
    return result

顺便说一句,用Toy.write(x, box)调用实例方法是不规范的 当x 是Toy 的一个实例时。首选方法是使用

x.write(box)

类似,使用toy.set_id(i) 而不是Toy.set_id(toy, i)。

【讨论】:

  • 感谢您的风格提示;我对 Python 很陌生。但我认为[x for x in toys if x is not None] 行删除了那些非对象。
  • 另外,将机会转移到for 循环将不是最理想的,特别是因为__init__ 在我的实际代码中做了很多事情。如果我只是想扔掉这个东西,我宁愿不经历这一切。
  • 天哪,我错过了。 Nones 一定来自其他地方。
  • 我认为您已经发现了一个令人惊讶的“陷阱”,这是由一些 Toy 实例在通过多处理池的结果队列时变为 None 引起的。
猜你喜欢
  • 2017-12-20
  • 2011-11-21
  • 2019-12-02
  • 1970-01-01
  • 1970-01-01
  • 2017-12-29
  • 1970-01-01
  • 2021-11-02
  • 2014-07-05
相关资源
最近更新 更多