ijson发现了意外行为



我使用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_record['id']值优先。当你这样做的时候,发电机然后计算表达式,并且底层kvitems生成器筋疲力尽的

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

我没有在函数中转换为列表,因为我认为生成器是一个更好的返回对象(内存占用较少等),然后我可以将其转换为函数外需要的列表。

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

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

关于结果收集

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

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

关于kvitems

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

尝试复制kvitems对象也是伪造的:正如您还发现的,您只需在所有位置创建一个带有相同对象的列表,而不是原始对象的副本。

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

CCD_ 25的结果是一个无限生成器。然后以此为基础构建不同的生成器表达式(因此,惰性求值)。你提到这个解决方案以";你不明白";,但不要深究到底发生了什么。我的期望是,当试图检查任何键的值时,您会遇到一个无限循环,因为您的生成器表达式正在无限生成器或类似的对象上迭代。

您说您的最后一段代码按预期工作。这是唯一让我感到惊讶的地方,特别是如果您在创建这三个生成器表达式后真的、真的检查(即评估)了它们。如果你能澄清一下,如果是这样的话,那会很有趣;否则,如果您创建了所有三个生成器表达式,但随后对其中一个或另一个进行了求值,那么这里就没有什么意外了(因为"关于结果集合"的解释)。

如何解决您的问题

基本上,这一切都可以归结为在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

我认为应该这样做。

最新更新