在Python 3中使用Python 2 Dict比较

时间:2014-09-04 21:40:49

标签: python python-2.7 python-3.x dictionary comparison

我正在尝试将一些代码从Python 2移植到Python 3.这很丑陋,但我试图让Python 3的结果与Python 2的结果尽可能相同。我有类似的代码:

import json

# Read a list of json dictionaries by line from file.

objs = []
with open('data.txt') as fptr:
    for line in fptr:
        objs.append(json.loads(line))

# Give the dictionaries a reliable order.

objs = sorted(objs)

# Do something externally visible with each dictionary:

for obj in objs:
    do_stuff(obj)

当我将此代码从Python 2移植到Python 3时,我收到错误:

TypeError: unorderable types: dict() < dict()

所以我将排序的行改为:

objs = sorted(objs, key=id)

但是词典的顺序在Python 2和Python 3之间仍然有所改变。

有没有办法在Python 3中复制Python 2比较逻辑?是不是以前使用过id而在Python版本之间不可靠?

5 个答案:

答案 0 :(得分:3)

如果你想要在2.7(使用任意排序顺序)和3.x(拒绝排序dicts)的早期版本的Python 2.x中具有相同的行为,Ned Batchelder's answer to a question about how sorting dicts works会让你成为那里的方式,但不是所有的方式。


首先,它为您提供了旧式cmp函数,而不是新式key函数。幸运的是,2.7和3.x都有functools.cmp_to_key来解决这个问题。 (当然,您可以将代码重写为关键功能,但这可能会使发布的代码和代码之间的任何差异变得更加困难......)


更重要的是,它不仅在2.7和3.x中没有做同样的事情,它甚至在2.7和3.x中都没有工作。要了解原因,请查看代码:

def smallest_diff_key(A, B):
    """return the smallest key adiff in A such that A[adiff] != B[bdiff]"""
    diff_keys = [k for k in A if A.get(k) != B.get(k)]
    return min(diff_keys)

def dict_cmp(A, B):
    if len(A) != len(B):
        return cmp(len(A), len(B))
    adiff = smallest_diff_key(A, B)
    bdiff = smallest_diff_key(B, A)
    if adiff != bdiff:
        return cmp(adiff, bdiff)
    return cmp(A[adiff], b[bdiff])

请注意,它在不匹配的值上调用cmp

如果dicts可以包含其他dicts,那依赖于cmp(d1, d2)将最终调用此函数的事实......这在较新的Python中显然不正确。

最重要的是,在3.x cmp中甚至不再存在。

此外,这依赖于任何值都可以与任何其他值进行比较的事实 - 您可能会获得任意结果,但您不会得到异常。在2.x中这是真的(除少数情况外),但在3.x中却不是这样。如果您不想将dicts与不可比较的值进行比较(例如,{1: 2} < {1: 'b'}可以引发异常),那么对您来说这可能不是问题,但除此之外,它是。

当然,如果你不想在字典比较中使用任意结果,你真的想要价值比较的任意结果吗?

解决所有这三个问题的方法很简单:您必须替换cmp,而不是调用它。所以,像这样:

def mycmp(A, B):
    if isinstance(A, dict) and isinstance(B, dict):
        return dict_cmp(A, B)
    try:
        return A < B
    except TypeError:
        # what goes here depends on how far you want to go for consistency

如果您想要比较2.7使用的不同类型对象的确切规则they're documented,那么您可以实现它们。但是如果你不需要那么多细节,你可以在这里写一些更简单的东西(或者甚至可能只是陷阱TypeError,如果上面提到的例外是可以接受的)。

答案 1 :(得分:0)

  

有没有办法在Python 3中复制Python 2比较逻辑?是不是以前使用过id而在Python版本之间不可靠?

id永远不会“可靠”。您为任何给定对象获得的id是完全任意的值;即使在同一台机器和Python版本上,它也可能因运行而异。

Python 2.x实际上没有记录它按id排序。 All it says是:

  

除了平等以外的结果一致地得到解决,但没有另外定义。

但这只是更好的说法:订单明确定义为仲裁(除了在任何给定的运行期间保持一致)。通过在Python 3.x中使用key=id进行排序,无论其实际工作方式是否相同,这都是完全相同的保证。*

所以你在3.x中做同样的事情。两个任意顺序不同的事实只意味着任意是任意的。


如果你想根据它包含的内容对dict进行某种可重复的排序,你只需要决定那个顺序是什么,然后你就可以构建它。例如,您可以按顺序对项目进行排序,然后比较它们(在项目为或包含dicts的情况下递归传递相同的键函数)。**

并且,设计并实现了某种明智的,非任意的排序,它当然会在2.7和3.x中以相同的方式工作。


*请注意,对于身份比较,等效,仅用于排序比较。如果您仅将其用于sorted,则会导致您的排序不再稳定。但是,无论如何它都是任意顺序,这几乎不重要。

**请注意,Python 2.x过去常常使用与此类似的规则。从上面的脚注:“早期版本的Python使用排序(键,值)列表的词典比较,但这对于比较相等的常见情况来说非常昂贵。”所以,这告诉你这是一个合理的规则 - 只要它实际上是你想要的规则,而你不介意性能成本。

答案 2 :(得分:0)

CPython2.x中的逻辑有点复杂,因为行为由dict.__cmp__决定。可以找到python实现here

但是,如果您真的想要一个可靠的订购,那么您需要对比id更好的密钥进行排序。你可以使用functools.cmp_to_key将比较函数从链接的答案转换为关键函数,但实际上,它不是一个好的排序,因为它完全是任意的。

您最好的选择是按字段的值(或多个字段)对所有字典进行排序。 operator.itemgetter可以很好地用于此目的。使用它作为关键函数应该为任何有点现代的实现和python版本提供一致的结果。

答案 3 :(得分:0)

如果您只需要在可能不同的平台上执行多次Python运行的订单,但实际上并不关心实际订单,那么一个简单的解决方案是在对它们进行排序之前将其转储到JSON:

import json

def sort_as_json(dicts):
    return sorted(dicts, key=lambda d: json.dumps(d, sort_keys=True))

print(list(sort_as_json([{'foo': 'bar'}, {1: 2}])))
# Prints [{1: 2}, {'foo': 'bar'}]

显然这只有在你的dicts是JSON可表示的情况下才有效,但是因为你从JSON加载它们无论如何都应该没问题。在您的情况下,您可以通过简单地排序在反序列化JSON之前重新加载对象的文件来获得相同的结果。

答案 4 :(得分:0)

您可以比较.items()

d1 = {"key1": "value1"}
d2 = {"key1": "value1", "key2": "value2"}
d1.items() <= d2.items()
True

但这不是递归的

d1 = {"key1": "value1", "key2": {"key11": "value11"}}
d2 = {"key1": "value1", "key2": {"key11": "value11", "key12": "value12"}}
d1.items() <= d2.items()
False