此通用解决方案对于非常大的数据也很有用”,如果结果字符串无法轻松放入内存,但仍可以轻松将其写入流来自 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
# from ?your_module? import SerializableGenerator
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 序列化程序。如果应该支持嵌套生成器,那么solution with simplejson 可能是最好的,它也有与iterencode(...) 类似的变体。
存根 *.pyi 用于强类型:
from typing import Any, Iterable, Iterator
class SerializableGenerator(list):
def __init__(self, iterable: Iterable[Any]) -> None: ...
def __iter__(self) -> Iterator: ...