【问题标题】:ijson kvitems unexpected behaviourijson kvitems 意外行为
【发布时间】:2021-12-12 17:11:30
【问题描述】:

我正在使用 ijson 来解析大型 JSON。我有这段代码,它应该给我一个dict 对应于相关 JSON 字段的值:

def parse_kvitems(kv_gen, key_list):
    results = {}
    for key in key_list:
        results[key] = (v for k, v in kv_gen if k == key)
    return results

with zipfile.ZipFile(fr'{directory}\{file}', 'r') as zipObj:
    # Get a list of all archived file names from the zip
    listOfFileNames = zipObj.namelist()
    # Iterate over the file names
    for fileName in listOfFileNames:
        # Check filename endswith csv.  dont extract, ijson wants bytes input and json.loads can run into memory issues with smash jsons.No documentation available 
        if fileName.endswith('.json'):
            # Extract a single file from zip
            with zipObj.open(fileName) as f:

                #HERE:
                records = ijson.kvitems(f, 'records.item')
                data_list = ['id', 'features', 'modules', 'dbxrefs', 'description']    
                parsed_records = parse_kvitems(records, data_list) --> give me a dict of dict values that fall under the json headings in data_list

我认为 kvitems 对象就像一个生成器,只通过一次运行(我得到了 'id' 的预期值,但 parsed_records 中的其他 data_list 键是空的)。

为了解决这个问题,我尝试列出重复的 kv_gen 的列表:

def parse_kvitems(kv_gen, key_list):
    kv_list = [kv_gen] * len(key_list) #this bit
    results = {}
    for key, kv_gen in zip(key_list, kv_list):
        results[key] = (v for k, v in kv_gen if k == key)
    return results

这给了我同样的错误。我认为可变性可能是这里的罪魁祸首,但我不能在 kvitems 对象上使用 copy 来查看这是否可以解决问题。

然后我尝试使用itertools.cycle(),但这似乎以我不理解的方式工作:

def parse_kvitems(kv_gen, key_list):
    infinite_kvitems = itertools.cycle(kv_gen)
    results = {}
    for key in key_list:
        results[key] = (v for k, v in infinite_kvitems if k == key)
    return results

此外,以下内容也有效(从某种意义上说,它给我的值与我在使用json.load() 加载 JSON 时看到的值相匹配):

records = ijson.kvitems(f, 'records.item')
ids = (v for k, v in records if k == 'id')
features = (v for k, v in records if k == 'features')
modules = (v for k, v in records if k == 'modules')

我只是对为什么我的函数不感兴趣,特别是当记录对象在上面多次运行时...


为罗德里戈编辑

但是,您并没有展示您是如何找到最终字典的 有 id 的值,但没有其他键的值。我假设它只是 因为您正在迭代 首先是 parse_records['id'] 值。当你这样做时,生成器 然后计算表达式,底层的 kvitems 生成器是 筋疲力尽。

是的,这是正确的 - 我将每个 val 转换为一个列表以检查每个键是否有一个包含相同数量项目的生成器,因为我担心下游 zip 操作可能会截断某些值,如果它们的对象多于最小的发电机。

我没有转换为函数中的列表,因为我认为生成器会是一个更好的返回对象(内存密集型等),然后我可以将其转换为函数外部所需的列表。

您说您的最后一段代码按预期工作。这是 只有一点让我感到惊讶,特别是如果你真的,真的检查过 (即评估)您之后的所有三个生成器表达式 创造了他们。如果你能澄清如果是这样的话 有趣的;否则,如果您创建了所有三个生成器表达式, 但是随后评估了其中一个,那么这里就没有惊喜了 (因为“关于结果集合”的解释)。

基本上,当我将项目作为生成器的压缩集合运行并将项目附加到列表时,它给了我所期望的值。但这可能需要更多调查,JSON 非常复杂,所以我可能遗漏了一些东西。

【问题讨论】:

    标签: python python-3.x generator ijson


    【解决方案1】:

    关于结果收集

    注意您是如何从kvitems 收集结果的。在上面的所有示例中,您都使用了生成器表达式,它们本身是惰性求值的,这可能会导致误解。但是,您没有显示 如何 您发现最终字典具有 id 的值,但没有其他键的值。我假设这只是因为您正在迭代parse_records['id']first 下的值。当你这样做的时候,生成器表达式会被计算并且底层的kvitems 生成器被耗尽。当您迭代其他生成器表达式的值时,提供它们的底层kvitems 生成器已用尽,因此它们不会产生任何结果。但是,如果您要迭代其他键之一的值first,您应该会看到该键的值,而不是其他键的值。

    生成器表达式本身很棒,但在这种情况下,它可能最终会增加混乱。 如果您想避免这种情况,您可能希望将这些序列合并为列表(例如,使用[... for k, v in kvitems ...] 而不是(... for k, v in kvitems ...))。

    关于 kvitems

    正如您所指出的,kvitems 是一个单通道生成器(或者当提供一个异步文件类对象时是一个单通道异步生成器),因此一旦您完全迭代它,进一步的迭代不会产生任何值。这就是为什么在您的原始代码中确实获得了 id 的值,但没有获得在已经迭代的 kvitems 对象的后续迭代中收集的其他键的值。

    试图复制kvitems 对象也是虚假的:正如您还发现的那样,您只是在创建一个所有位置都具有相同 对象的列表,而不是原始对象的副本。

    尝试copy kvitems 根本不可能。获得 N 个“副本”的唯一选择是实际构造 N 个不同的对象;然而,这意味着输入文件将被读取 N 次(并且还需要打开 N 次,因为kvitems 将推进给定文件,直到它没有更多输入)。可能,但不是很好。

    itertools.cycle 的结果是一个无限生成器。然后你以此为基础来构造不同的生成器表达式(因此,惰性求值)。您提到该解决方案以“您不了解”的方式起作用,但不要深入研究到底发生了什么。我的期望是,当尝试检查任何键的值时,您会遇到无限循环,因为您的生成器表达式正在迭代无限生成器或类似的东西。

    您说您的最后一段代码按预期工作。这是唯一让我感到惊讶的地方,特别是如果您在创建它们之后真的检查(即评估)所有三个生成器表达式。如果您能澄清是否是这种情况,那将很有趣;否则,如果您创建了所有三个生成器表达式,但随后评估了其中一个,那么这里没有任何意外(因为“关于结果集合”的解释)。

    如何解决您的问题

    这基本上都归结为对kvitems 进行一次迭代。例如,您可以尝试这样的事情:

    def parse_kvitems(kvitems, keys):
        results = collections.defaultdict(list)
        for k, v in kvitems:
            if k in keys:
                results[k].append(v)
        return results
    

    我认为应该这样做。

    【讨论】:

    • 很好的答案,非常感谢!我尝试回答了您提出的一些问题,但是是的,您解决了我的问题(也以非常简单的方式!)。
    猜你喜欢
    • 2021-08-21
    • 2017-03-10
    • 2019-05-01
    • 2011-07-15
    • 2011-04-27
    • 2019-12-07
    • 2021-12-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多