我正在尝试将一些代码从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版本之间不可靠?
答案 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