如果一个结果字符串不容易放入内存,但仍然可以从JSON迭代器轻松地写入流中,那么这个通用解决方案对于非常大的数据也非常有用。 (这比“import simplejson…”更好,它可以提供帮助,但不太多)。已在Python 2.7、3.0、3.3、3.6、3.10.0a7中进行了测试。比simplejson快两倍。内存占用小。编写单元测试。
import itertools
class SerializableGenerator(list):
"""Generator that is serializable by JSON"""
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])
普通用法(输入内存占用小,但整个输出字符串仍在内存中):
>>> json.dumps(SerializableGenerator(iter([1, 2])))
"[1, 2]"
>>> json.dumps(SerializableGenerator(iter([])))
"[]"
对于非常庞大的数据,可以将其用作Python 3中JSON块的生成器,并且仍然使用非常少的内存:
>>> iter_json = json.JSONEncoder().iterencode(SerializableGenerator(iter(range(1000000))))
>>> for chunk in iter_json:
... stream.write(chunk)
# or a naive examle
>>> tuple(iter_json)
('[1', ', 2', ... ', 1000000', ']')
这个类通常被普通的 JSONEncoder().encode(...)
内部使用,也可以被显式地调用 json.dumps(...)
或 JSONEncoder().iterencode(...)
来获取 JSON 块的生成器。
(示例中的 iter()
函数并不是必需的,只是为了演示一个长度未知的非平凡输入。)
测试:
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(在另一个问题中,也很复杂)。
与这些解决方案的重要区别包括:
- 重要方法
__len__
、__bool__
等从有意义初始化的list
类继承一致。
- 输入的第一项立即由
__init__
评估(不是由许多其他方法懒惰触发),list
类可以立即知道迭代器是否为空。非空的list
包含具有生成器的一个项或列表为空,如果迭代器为空。
- 对于空迭代器的正确长度实现对于
JSONEncoder.iterencode(...)
方法很重要。
- 所有其他方法都给出有意义的输出,例如
__repr__
:
>>> SerializableGenerator((x for x in range(3)))
[<generator object <genexpr> at 0x........>]
这种解决方案的优点是可以使用标准的JSON序列化器。如果需要支持嵌套生成器,则
使用simplejson的解决方案可能是最好的选择,它还有类似的变体
iterencode(...)
。
*.pyi
桩文件用于强类型:
from typing import Any, Iterable, Iterator
class SerializableGenerator(list):
def __init__(self, iterable: Iterable[Any]) -> None: ...
def __iter__(self) -> Iterator: ...
[]
包围即可。 - Joel Cornett