Python:使列表生成器JSON可序列化

时间:2014-02-09 19:25:58

标签: python json out-of-memory generator

如何将JSON文件列表连接成一个巨大的JSON数组?我有5000个文件和550 000个列表项。

我的第一次尝试是使用jq,但看起来jq -s并未针对大输入进行优化。

jq -s -r '[.[][]]' *.js 

这个命令有效,但需要太长时间才能完成,我真的想用Python解决这个问题。

这是我目前的代码:

def concatFiles(outName, inFileNames):
    def listGenerator():
        for inName in inFileNames:
            with open(inName, 'r') as f:
                for item in json.load(f):
                    yield item

    with open(outName, 'w') as f:
        json.dump(listGenerator(), f)

我得到了:

TypeError: <generator object listGenerator at 0x7f94dc2eb3c0> is not JSON serializable

任何将所有文件加载到ram中的尝试都会触发Linux的OOM杀手。你有什么想法吗?

4 个答案:

答案 0 :(得分:19)

从simplejson 3.8.0开始,您可以使用iterable_as_array选项将任何可迭代序列化为数组

# Since simplejson is backwards compatible, you should feel free to import
# it as `json`
import simplejson as json
json.dumps((i*i for i in range(10)), iterable_as_array=True)

结果为[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

答案 1 :(得分:17)

您应该从list派生并覆盖__iter__方法。

import json

def gen():
    yield 20
    yield 30
    yield 40

class StreamArray(list):
    def __iter__(self):
        return gen()

    # according to the comment below
    def __len__(self):
        return 1

a = [1,2,3]
b = StreamArray()

print(json.dumps([1,a,b]))

结果为[1, [1, 2, 3], [20, 30, 40]]

答案 2 :(得分:5)

一个完整的简单可读解决方案,可以从普通或空迭代序列化生成器,可以使用.encode()或.iterencode()。书面测试。使用Python 2.7,3.0,3.3,3.6进行测试

import itertools

class SerializableGenerator(list):
    """Generator that is serializable by JSON

    It is useful for serializing huge data by JSON
    >>> json.dumps(SerializableGenerator(iter([1, 2])))
    "[1, 2]"
    >>> json.dumps(SerializableGenerator(iter([])))
    "[]"

    It can be used in a generator of json chunks used e.g. for a stream
    >>> iter_json = ison.JSONEncoder().iterencode(SerializableGenerator(iter([])))
    >>> tuple(iter_json)
    ('[1', ']')
    # >>> for chunk in iter_json:
    # ...     stream.write(chunk)
    # >>> SerializableGenerator((x for x in range(3)))
    # [<generator object <genexpr> at 0x7f858b5180f8>]
    """

    def __init__(self, iterable):
        tmp_body = iter(iterable)
        try:
            self._head = iter([next(tmp_body)])
            self.append(tmp_body)
        except StopIteration:
            self._head = []

    def __iter__(self):
        return itertools.chain(self._head, *self[:1])


# -- test --

import unittest
import json


class Test(unittest.TestCase):

    def combined_dump_assert(self, iterable, expect):
        self.assertEqual(json.dumps(SerializableGenerator(iter(iterable))), expect)

    def combined_iterencode_assert(self, iterable, expect):
        encoder = json.JSONEncoder().iterencode
        self.assertEqual(tuple(encoder(SerializableGenerator(iter(iterable)))), expect)

    def test_dump_data(self):
        self.combined_dump_assert(iter([1, "a"]), '[1, "a"]')

    def test_dump_empty(self):
        self.combined_dump_assert(iter([]), '[]')

    def test_iterencode_data(self):
        self.combined_iterencode_assert(iter([1, "a"]), ('[1', ', "a"', ']'))

    def test_iterencode_empty(self):
        self.combined_iterencode_assert(iter([]), ('[]',))

    def test_that_all_data_are_consumed(self):
        gen = SerializableGenerator(iter([1, 2]))
        list(gen)
        self.assertEqual(list(gen), [])

使用的解决方案:Vadim Pushtaev(不完整),user1158559(不必要的复杂)和Claude(在另一个问题中,也很复杂)。

有用的简化是:

  • 没有必要懒惰地评估第一个项目,它可以在__init__中完成,因为我们可以预期可以在json.dumps之前立即调用SerializableGenerator。 (针对user1158559解决方案)
  • 没有必要通过NotImplementedError重写许多方法,因为这不是像__repr__这样的所有方法。最好将生成器也存储到列表中,以提供有意义的结果,如[<generator object ...>]。 (对克劳德)。默认方法__len____bool__现在可以正常识别空对象而非空对象。

此解决方案的一个优点是可以在没有参数的情况下使用标准JSON序列化程序。如果应该支持嵌套生成器,或者如果不希望SerializableGenerator(iterator)进行封装,那么我建议IterEncoder回答。

答案 3 :(得分:2)

根据接受的答案,这是我最终选择的StreamArray。它包含两个谎言:

  1. self.__tail__可能不可变的建议
  2. len(StreamArray(some_gen))为0或1
  3. class StreamArray(list):
    
        def __init__(self, gen):
            self.gen = gen
    
        def destructure(self):
            try:
                return self.__head__, self.__tail__, self.__len__
            except AttributeError:
                try:
                    self.__head__ = self.gen.__next__()
                    self.__tail__ = self.gen
                    self.__len__ = 1 # A lie
                except StopIteration:
                    self.__head__ = None
                    self.__tail__ = []
                    self.__len__ = 0
                return self.__head__, self.__tail__, self.__len__
    
        def rebuilt_gen(self):
            def rebuilt_gen_inner():
                head, tail, len_ = self.destructure()
                if len_ > 0:
                    yield head
                for elem in tail:
                    yield elem
            try:
                return self.__rebuilt_gen__
            except AttributeError:
                self.__rebuilt_gen__ = rebuilt_gen_inner()
                return self.__rebuilt_gen__
    
        def __iter__(self):
            return self.rebuilt_gen()
    
        def __next__(self):
            return self.rebuilt_gen()
    
        def __len__(self):
            return self.destructure()[2]
    

    仅限一次使用!