【问题标题】:Strange behavior in python when comparing lists of dictionaries比较字典列表时python中的奇怪行为
【发布时间】:2015-07-15 10:11:33
【问题描述】:

我正在从事一个处理解析电子邮件并将其保存到数据库的项目。解析电子邮件并将解析的电子邮件保存到日志文件中没有问题,但是当我尝试从日志文件中读取时,我遇到了一个奇怪的问题。代码如下:

def main():
global email_batch, temp_email_batch

read_logfile = True
while read_logfile:
    try:
        with open('a-1-test.json', 'r') as outfile:
            temp_email_batch = json.load(outfile)
            outfile.close()
    except IOError as err:
        print "I/O error({0}): {1}".format(err.errno, err.strerror)
    except:
        print "Unexpected error:", sys.exc_info()[0]

    if temp_email_batch != email_batch:
        email_batch = temp_email_batch
        print "not equal log files"
        # saveData()
        for parsedMail in email_batch:
            collection.insert(parsedMail)
    else:
        print "equal log files"
        time.sleep(10)

我可以读取日志文件(这里只是一个 json 格式的测试文件)并保存数据。检查:

if temp_email_batch != email_batch:

当我尝试比较两个批次时有一个奇怪的行为。这个想法是检查我是否已经从日志文件中获取数据,或者是否已将新数据写入日志文件。如果我注释掉迭代:

for parsedMail in email_batch:
            # http_client.addRawData(sourceName, parsedMail)
            collection.insert(parsedMail)

检查工作正常,并查看何时没有新数据添加到日志文件,但如果我取消注释,即使 temp_email_batch 与 email_batch 相等,检查似乎总是为真,因此它会不断将相同的电子邮件保存到集合。

我很震惊。迭代器是否以某种方式更改列表?我所有的编程直觉都说它没有,但是如果只是注释它,代码可以正常工作并且如果 temp 等于前一批,则跳转到 else 子句肯定有问题。

提前谢谢你, 乔治

编辑:

因此,我找到了一种解决方法,即不直接通过 pymongo 库将我的数据保存到数据库,而是使用 json 远程进程调用并设置一个脚本,该脚本定义了几种用于数据库操作的方法。通过使用 RPC,我没有任何问题,并且代码工作正常。

【问题讨论】:

  • 我想说python正在检查temp_email_batchemail_batch是否是不同的实体,而不是它们的内容是否不同。您可能需要实现此比较才能使其正常工作。
  • 你可以试试if all(email in email_batch for email in temp_email_batch):
  • @cnluzon,恐怕这是不正确的。在 Python 中进行身份测试的是 is 运算符。这仍然只是一个平等测试。
  • 对了,不应该是for parsedMail in temp_email_batch还是我理解你的问题有误?
  • 老实说,我敢打赌这是因为您使用的是 Mongo,它会在您保存后为每个字典添加一个 _id 字段。这将使生成的变异字典列表无法通过相等性测试。

标签: python list dictionary iterator


【解决方案1】:

这不是 Python 中如何实现 dict 或 list 相等的问题。

Python 中的列表相等性基于大小、顺序和所包含值的相等性。这意味着,在建立两个相同大小的列表后,解释器基本上会压缩这两个列表,并在每一对上依次调用 ==。

字典相等是相似的,只是它们是无序的,所以顺序不考虑在内。从概念上讲,它可以被认为是从每个字典创建一组键值对并询问这些集合是否不相等。

我要说的是,两个 dicts 列表必须真的不相等,我 95% 确定这是因为您忘记了 dicts 是可变的,而 PyMongo 在插入或保存一个dict to a collection 将一个 _id 字段添加到 dict 中。请记住,dict 是可变的——运行时中对该 dict 的所有引用现在也具有 _id 字段。

不过,JSON 文件当然在运行时之外。当您再次加载字典时,它们没有该字段。对您而言,对于大多数意图和目的而言,这两个 dicts 列表可能看起来相同,但由于旧列表的每个 dicts 中的 _id 字段,它们现在不相等。

如果你想保持这条路线,你需要努力避免比较中的 _id 字段(hacky)。或者,您可以为集合设置一个唯一键并处理将引发的重复键错误(如果您实际上担心重复,则可能是最佳选择)。或者,您可以在将每个 dict 写入数据库之前复制它,然后写入副本,而不是原始的,这样旧的引用就不会得到新的字段(也是 hacky)。确实,这需要您更仔细地考虑如何定义数据结构的相等性。

欢迎来到可变状态。糟透了。

【讨论】:

  • 我明白你的意思。我尝试将 email_batch 的内容复制到第二个临时列表并使用该列表添加到 Mongo 数据库,但遇到了同样的问题。我能做的是逐一检查列表中的项目并比较它们的唯一消息 ID。但我不确定在 python 中最好的方法是什么(换句话说,最有效的)
  • @GeorgiNikolov,复制包含列表是不够的。您必须复制每个包含的字典。同样,我不推荐这条路线——这是一种糟糕的编码模式——但要说明我的观点:导入复制模块,并将插入行更改为collection. insert(copy.deepcopy(parsedMail))。再次重申,为了真正发挥作用,请考虑在您的集合上建立唯一索引并处理重复键错误。
  • 每个字典都包含一个键:值对 Message-Id,每封电子邮件都是唯一的。我可以将其用作唯一标识符。如果 Message-Id 对应,那么在两个列表中检查每封电子邮件的最佳方法是什么?
  • 试试:lists_are_equal = len(list_1) == len(list_2) and all(a["message-id"] == b["message-id“] for a, b in zip(list_1, list_2))。同样,我认为更好的解决方案是在 Mongo 中在消息 id 上创建一个唯一索引,并捕获并记录重复的键错误,否则忽略它们。这实际上会简化您的代码。
  • 我不明白您所说的“在 Mongo 中在消息 id 上创建唯一索引并捕获和记录重复键错误”是什么意思。能举个例子吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-26
  • 2018-12-25
  • 2017-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多