如何在Python中深度比较嵌套类型

3
在Python中,很容易测试两个变量是否具有相同的顶级类型:
In [1]: s1 = 'bob'
In [2]: s2 = 'tom'
In [3]: type(s1) == type(s2)
Out[3]: True

但是在类型嵌套的情况下,这并不容易:
In [4]: strlist = ['bob', 'tom']
In [5]: intlist = [5, 6, 7]
In [6]: type(strlist) == type(intlist)
Out[6]: True

有没有一种“通用”的方法可以深入比较两个变量,使其具有以下特点:

deepcompare(['a', 'b'], [1, 2]) == False
deepcompare([42, 43], [1, 2]) == True

编辑:

为了更清晰地阐述问题,假设这包括列表长度和异质列表类型:

deepcompare([1, 2, 3], [1, 2]) == False
deepcompare([1, 3], [2, 'b']) == False
deepcompare([1, 'a'], [2, 'b']) == True

你是否也关心相等的大小?还是只是确保无论里面有多少个,它们都必须具有相同的结构类型? - idjaw
1
有一个“通用”的方法意味着这是一个明确定义的问题,但事实并非如此。列表的长度与其“类型”有关吗?对于非同质容器类型应该怎么处理? - tzaman
1
我们只讨论一层嵌套吗? - K. Menyah
你可以递归地创建两个仅包含每个位置处对象类型的结构,然后直接进行比较 - [str, str] != [int, int],例如。您可以假设这些结构包含同质类型,或者最终会查看例如 deepcompare(['a', 1], ['b', 2])(结果应该是什么)? - jonrsharpe
@tzaman 的观点很好,已经看到编辑。 - Alfredo Gimenez
2个回答

2

进一步解释,你可以递归创建我所谓的"类型映射"

def typemap(lst_or_obj):
    if not isinstance(lst_or_obj, list):
        return type(lst_or_obj)
    return [typemap(obj) for obj in lst_or_obj]

然后使用此方法获取您的结构体内部的类型:
a = [1, 2, ['three', 4]]
b = [5, 6, ['seven', 8]]
c = [9, 10, [11, 'twelve']]

ta = typemap(a)
tb = typemap(b)
tc = typemap(c)

print(ta)
print(tb)
print(tc)

print(ta == tb)
print(ta == tc)

输出:

[<class 'int'>, <class 'int'>, [<class 'str'>, <class 'int'>]]
[<class 'int'>, <class 'int'>, [<class 'str'>, <class 'int'>]] 
[<class 'int'>, <class 'int'>, [<class 'int'>, <class 'str'>]]
True
False

那么你的函数就是这样的:
def deepcompare(a, b):
    return typemap(a) == typemap(b)

如果你需要处理除了列表以外的东西,你可以很容易地将 isinstance 检查扩展到 (list, tuple),但是你可能会遇到一些问题,比如 str(递归迭代字符串是一个问题,因为单个字符或空字符串是它本身的可迭代对象,所以你的程序会崩溃),还有 dict(排序问题,比较键和/或值,...)。


不错!虽然为了更通用,typemap中的第一行应该检查list_or_obj是否可迭代,而不是它是否是一个列表? - Alfredo Gimenez
@spiffman 这是一个要求吗?可迭代性的问题在于例如字符串和字典是可迭代的,但您可能希望将它们与列表和元组区别对待。 - jonrsharpe
不一定,我只是想知道那是否能提供一个更通用的解决方案,但你说得对,在这种情况下字符串确实是特别棘手的。 - Alfredo Gimenez
@jonrsharpe 这是正确而清晰的方法。您还可以使用 collections.Iterable 进行类型检查,以使其更加全面。 - Mazdak
@Kasramvd 请阅读我的上面的评论,我在那里指出为什么仅仅检查可迭代对象并不是万能解决方法。 - jonrsharpe
@jonrsharpe 抱歉我错过了那个问题,但是你可以在isinstance中添加多个类型或者至少在问题中提到它。无论如何,做得好。 - Mazdak

1
我这样做的方式是使用这个函数:

def getDeepTypes(items):
    types = [type(x) for x in items]
    return (types[0] if all(x == types[0] for x in types) else None)

这个程序使用多种列表推导式来获取列表的深层类型。如果它们不完全相同,就会返回None

>>> getDeepTypes([1, 2, 3])
int
>>> getDeepTypes(["foo", "bar"])
str
>>> print(getDeepTypes([1, "foo"]))
None

所以你可以这样做:

getDeepTypes(['a', 'b']) == getDeepTypes([1, 2]) # False
getDeepTypes([42, 43]) == getDeepTypes([1, 2]) # True

1
我会在这里使用 any。它会在条件不满足时立即停止运行。此外,这仅处理单个级别。 - idjaw
1
如果您创建了所有类型的集合并仅检查集合长度是否为1,那么getDeepTypes(items)会更有效率吗? types = {type(x) for x in items}if len(types) == 1 return types.pop() - GeckStar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接