检查列表中的所有元素是否属于同一类型

112

如何检查列表中的元素是否为相同类型,如果可能的话,而不必逐个检查每个元素?

例如,我想要一个函数来检查这个列表的每个元素是否为整数(显然是错误的):

x = [1, 2.5, 'a']

def checkIntegers(x):
    # return True if all elements are integers, False otherwise

3
你如何在不检查每个元素的情况下完成它呢?你无法获得任何有关未检查元素的信息。 - Daniel Roseman
6
@DanielRoseman -- 一旦你找到了一个不好的,你就可以立即中断。 - mgilson
似乎使用 all 是正确的方法... - linello
@mgilson,是的...我认为OP请求的解决方案根本不涉及迭代。 - Daniel Roseman
“所有元素都是相同类型(彼此之间)”与“所有元素都是特定类型(在序列外指定)”是不同的命题。 - Karl Knechtel
11个回答

206
尝试结合使用 allisinstance
all(isinstance(x, int) for x in lst)

如果需要,您甚至可以使用 isinstance 检查多个类型:

all(isinstance(x, (int, long)) for x in lst)

并不会自动提取继承类,例如:
class MyInt(int):
     pass

print(isinstance(MyInt('3'),int)) #True

如果您需要限制自己仅使用整数,您可以使用all(type(x) is int for x in lst)。但这是一种非常罕见的情况。


您可以编写一个有趣的函数,如果所有其他元素都是相同类型,则该函数将返回序列中第一个元素的类型:

def homogeneous_type(seq):
    iseq = iter(seq)
    first_type = type(next(iseq))
    return first_type if all( (type(x) is first_type) for x in iseq ) else False

这将适用于任何任意的可迭代对象,但这将在处理过程中使用"迭代器"。
同样有趣的函数是返回共同基数的集合:
import inspect
def common_bases(seq):
    iseq = iter(seq)
    bases = set(inspect.getmro(type(next(iseq))))
    for item in iseq:
        bases = bases.intersection(inspect.getmro(type(item)))
        if not bases:
           break
    return bases


1
请注意,homogeneous_type()(我猜这就是函数的名称)对于空迭代器会出错。此外,结果取决于可迭代对象中项目的顺序(我猜这就是你所说的“迭代器”),这与我对此名称的函数所期望的不同。(例如,[0,False][False,0]。) - Sven Marnach
1
@SvenMarnach -- 我想这是一个合理的观点。我认为在空迭代器上出错是可以接受的(实际上我在编写函数时考虑过这一点)...在空可迭代对象的情况下,结果应该是什么?至于传递[0,False][False,0]列表,我可以通过执行all(type(x) is first_type for x in iseq)来修复它,但我认为如果函数也检查子类,则该函数将更有用。 - mgilson
@SvenMarnach -- 最终,是的,我确实是指“可迭代对象”。从语义上讲,您会用什么术语来描述可以被消耗(iter(...)或生成器)的可迭代对象,而不是不能被消耗的可迭代对象(listtuple,...)?(感谢您对此进行了批判性的思考。希望这会带来更好的答案 :)) - mgilson
all() 还有一个优点,就是当它遇到生成器表达式中的 False 值时,它会短路并停止。 - dansalmo
根据问题的代码示例,x 不应该是列表对象吗? - Stevoisiak
刚刚意识到all对于空的可迭代对象返回True,这就是为什么homogeneous_type适用于长度为1的序列。聪明! - Nathaniel Jones

9
使用 any() 函数,无需遍历整个列表。只要找到不是 intlong 类型的对象就可以立即停止:
>>> not any(not isinstance(y,(int,long)) for y in [1,2,3])
True
>>> not any(not isinstance(y,(int,long)) for y in [1,'a',2,3])
False

4
使用“all”更具逻辑性,但我认为“not any(not condition ...)”也可以。我想对你的努力表示赞赏(同时也是为了展示“all”的相对应物,它同样非常有用)。 - mgilson
@mgilson,当我看到你的评论时,我想到了这个问题:),否则我对基于all()的解决方案感到满意。 - Ashwini Chaudhary
16
all也会进行短路处理,因此这种方法与@mgilson的解决方案实际上没有什么区别。它更多的是一个逻辑练习... - mata
通常来说,我认为你需要做的是检查是否有任何元素与第一个元素的类型不同。即 any(not isinstance(e,type(seq[0])) for e in seq[1:]) - martineau
@martineau -- 假设您当然可以切片seq。否则,您需要使用iternext--这样可以节省复制的开销,但对于列表来说,这通常是非常便宜的。 - mgilson
5
%timeit all(isinstance(x, basestring) for x in list_w_int_or_str) actually takes less time than %timeit not any(not isinstance(x, basestring) for x in list_w_int_or_str) - Mark Mikofski

5

结合之前给出的一些答案,使用map()、type()和set()的组合提供了一个我认为相当易读的答案。假设不检查类型多态性的限制是可以接受的。这也不是最高效的答案,但它允许轻松地检查所有元素是否属于同一类型。

# To check whether all elements in a list are integers
set(map(type, [1,2,3])) == {int}
# To check whether all elements are of the same type
len(set(map(type, [1,2,3]))) == 1

3

检查列表是否由同类元素组成的最简单方法是使用itertools模块的groupby函数:

from itertools import groupby
len(list(groupby(yourlist,lambda i:type(i)))) == 1

如果len与1不同,那么它意味着在列表中找到了不同种类的类型。

这个问题是需要遍历整个序列。

如果你想要一个懒惰版本,可以编写一个函数:

def same(iterable):
    iterable = iter(iterable)
    try:
        first = type(next(iterable))
        return all(isinstance(i,first) for i in iterable)
    except StopIteration:
        return True

这个函数会存储第一个元素的类型,并且一旦在列表中的元素中发现不同类型,就停止执行。

这两种方法都对类型非常敏感,所以它们将把int和float视为不同的类型,但这应该是您能够接近您请求的结果的最佳选择。

编辑:

根据mgilson的建议,用all函数的调用替换了for循环。

如果是空迭代器,则返回True,以保持与内置的all函数的行为一致。


1
OP的第一个请求不是检查所有元素是否都是特定类型,而是检查所有元素是否都是相同类型,与第一个元素是哪种类型无关。这是解决一个略微不同的问题的方法。 - EnricoGiampieri
@EnricoGiampieri -- 但你仍然可以在3行代码中使用短路版本。 a = iter(obj); t = type(next(a)); all(isinstance(x, t) for x in a)。对于一个列表来说,甚至更容易,这就是问题所在 -- t = type(lst[0]); return all(instance(x,t) for x in lst[1:])。不错。 - mgilson
是的,您可以使用短路计算,但它无法处理空迭代器并且会引发异常。这只是一个选择问题,在我的情况下,我更喜欢处理它而不是引发异常。 - EnricoGiampieri
你可以直接使用 type,而不是 lambda i: type(i) - Sven Marnach

2
>>> def checkInt(l):
    return all(isinstance(i, (int, long)) for i in l)

>>> checkInt([1,2,3])
True
>>> checkInt(['a',1,2,3])
False
>>> checkInt([1,2,3,238762384762364892364])
True

3
使用isinstance()type()更好。 - Ashwini Chaudhary
这两个函数中,哪一个更快,isinstance() 还是 type() - linello
isinstance(i, (int, long)) 很简短。它不是关于速度的问题,而是 type() 会对 int 的子类失效。 - Ashwini Chaudhary
@mgilson 是的,我也刚注意到你已经回答了。 :) - Inbar Rose

2
我喜欢由EnricoGiampieri以上)创建的功能,但是有一个更简单的版本,使用itertools文档中"Itertools recipes"部分的all_equal函数:
from itertools import groupby

def all_equal(iterable):
    "Returns True if all the elements are equal to each other"
    g = groupby(iterable)
    return next(g, True) and not next(g, False)

所有这些配方都被“打包”在more_itertools中:
引用: 大部分这些配方和许多其他配方都可以从Python软件包索引中找到的more-itertools项目中安装: pip install more-itertools 扩展工具提供与基础工具集相同的高性能。通过一次处理一个元素而不是一次将整个可迭代对象带入内存,保持了卓越的内存性能。通过以函数式样式将工具链接在一起来消除临时变量,代码量保持较小。通过优先选择“矢量化”的构建块而不是使用for循环和生成器来避免解释器开销,保留了高速度。
from more_itertools import all_equal

all_equal(map(type, iterable))

或者使用 isinstance 和已知类型 int(如原始问题中所述)。
all_equal(map(lambda x: isinstance(x, int), iterable))

这两种方法比Enrico的建议更简洁,并且与Enrico的函数一样处理“空迭代器”(例如range(0))。
all_equal(map(type, range(0))) # True
all_equal(map(type, range(1))) # True
all_equal(map(type, range(2))) # True

all_equal(map(lambda x: isinstance(x, int), range(0))) # True
all_equal(map(lambda x: isinstance(x, int), range(1))) # True
all_equal(map(lambda x: isinstance(x, int), range(2))) # True

1

如果您想要排除子类,也可以使用 type()。请参阅isinstance()type() 的区别

>>> not any(not type(y) is int for y in [1, 2, 3])
True
>>> not any(not type(y) == int for y in [1, 'a', 2.3])
False

尽管可能不希望这样做,因为这将更加脆弱。如果 y 将其类型更改为 int 的子类,则此代码将中断,而 isinstance() 仍将正常工作。
使用 is 是可以的,因为内存中只有一个 ,所以如果它们是相同类型,则应返回相同的标识。

1
我建议您使用这种方法,遍历列表中的每个项目并检查它们是否都是相同的数据类型,如果是,则返回True,否则返回False。
def checkIntegers(x):
    # return True if all elements are integers, False otherwise
    return all(isinstance(i, type(x[0])) for i in x[1:])

x = [1, 2.5, 'a']
checkIntegers(x)

False

0
这里有一个关于此的简洁函数,目前它检查(在一个列表中)所有项目是否为整数或所有项目是否为字符串,或者是否为混合数据类型
def check_item_dtype_in_list(item_range):
    if all(map(lambda x: str(x).isdigit(), item_range)):
        item_range = list(map(int, item_range))
        print('all are integer')
        print(item_range)
        return 
    elif all(isinstance(item, str) for item in item_range):
        print('all are string')
        print(item_range)
        return 
    elif any(map(lambda x: str(x), item_range)):
        print('mixed dtype')
        print(item_range)
        return 

check_item_dtype_in_list(['2', 2, 3])
check_item_dtype_in_list(["2", 2, 'Two'])
check_item_dtype_in_list(['Two', 'Two', 'Two'])
check_item_dtype_in_list([2, 2., 'Two'])

all are integer
[2, 2, 3]
mixed dtype
['2', 2, 'Two']
all are string
['Two', 'Two', 'Two']
mixed dtype
[2, 2.0, 'Two']

-1
    def c(x):
         for i in x:
             if isinstance(i,str):
                   return False
             if isinstance(i,float):
                   return False

          return True

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