web-dev-qa-db-ja.com

Python:リストジェネレーターJSONをシリアル化可能にする

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キラーがトリガーされます。あなたはなにか考えはありますか?

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]]です。

19
Vadim Pushtaev

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]です

23
Nick Babcock

通常または空の反復可能オブジェクトからジェネレーターをシリアル化できる完全でシンプルな読み取り可能なソリューションは、.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ソリューションに対して)
  • すべてのメソッドが___repr___のようなものではないため、NotImplementedErrorによって多くのメソッドを書き換える必要はありません。 _[<generator object ...>]_のような意味のある結果を提供するには、ジェネレータもリストに格納することをお勧めします。 (クロードに対して)。デフォルトのメソッド___len___および___bool___は、空のオブジェクトと空でないオブジェクトを正しく認識するようになりました。

このソリューションの利点は、標準のJSONシリアライザーをパラメーターなしで使用できることです。ネストされたジェネレーターをサポートする必要がある場合、またはSerializableGenerator(iterator)によるカプセル化が望ましくない場合は、 IterEncoder の回答をお勧めします。

7
hynekcer

受け入れられた答えに基づいて、ここに私が最終的に行ったStreamArrayがあります。 2つの嘘が含まれています。

  1. _self.__tail___は不変である可能性があるという提案
  2. len(StreamArray(some_gen))は0または1です

_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]
_

使い捨てのみ!

2
user1158559