Python: 如何判断一个变量是数组还是标量

406
我有一个接受参数NBins的函数。我想使用标量50或数组[0,10,20,30]调用此函数。如何在函数内部确定NBins的长度?或者换句话说,如何确定它是标量还是向量?

我尝试过这个:

>>> N=[2,3,5]
>>> P = 5
>>> len(N)
3
>>> len(P)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()
>>> 

你能看到,我不能对P应用len,因为它不是一个数组... 在Python中是否有类似于isarrayisscalar的东西?

谢谢。


3
你尝试过测试它的 type 吗? - Sukrit Kalra
16个回答

542
>>> import collections.abc
>>> isinstance([0, 10, 20, 30], collections.abc.Sequence)
True
>>> isinstance(50, collections.abc.Sequence)
False

注意: isinstance 还支持类的元组,应避免使用和是不必要的。

您也可以检查 not isinstance(x, (str, unicode))

正如@2080以及这里所指出的一样,这对于numpy数组是不起作用的。例如:

>>> import collections.abc
>>> import numpy as np
>>> isinstance((1, 2, 3), collections.abc.Sequence)
True
>>> isinstance(np.array([1, 2, 3]), collections.abc.Sequence)
False

如果您有需要,可以尝试来自@jpaddison3的答案:

>>> hasattr(np.array([1, 2, 3]), "__len__")
True
>>> hasattr([1, 2, 3], "__len__")
True
>>> hasattr((1, 2, 3), "__len__")
True

然而正如这里所指出的,这种方法也并非完美,会错误地(至少在我看来)将字典分类为序列,而使用collections.abc.Sequence类的isinstance可以正确地分类:

>>> hasattr({"a": 1}, "__len__")
True
>>> from numpy.distutils.misc_util import is_sequence
>>> is_sequence({"a": 1})
True
>>> isinstance({"a": 1}, collections.abc.Sequence)
False

您可以将解决方案定制为像这样,根据您的需求添加更多类型到isinstance

您可以根据需要将更多类型添加到isinstance中,以定制解决方案,例如:

>>> isinstance(np.array([1, 2, 3]), (collections.abc.Sequence, np.ndarray))
True
>>> isinstance([1, 2, 3], (collections.abc.Sequence, np.ndarray))
True

3
谢谢,我没有想到将“list”反转来得到标量的假值...谢谢。 - otmezger
8
虽然这是一个很好的回答,但是collections.Sequence也是字符串的抽象基类,因此应该考虑到这一点。我正在使用类似于if type(x) is not str and isinstance(x, collections.Sequence):的东西。这不是最好的解决方法,但是它是可靠的。 - bbenne10
3
@bbenne10 好的,但要避免使用 type,还要在 Python 2 上检查 not isinstance(x, (str, unicode)) - jamylak
1
collections.Sequence 可能需要在 Python 3.9 或 3.10 中更改为 collections.abc.Sequence - Bob Stein
2
不幸的是,isinstance(np.array(1), (collections.abc.Sequence, np.ndarray))(即numpy标量)返回True,但np.array(1)[0]却是一个IndexError - scott
显示剩余4条评论

172

之前的回答都是基于数组是Python标准列表的假设。作为一个经常使用NumPy的人,我建议使用以下非常Pythonic的测试:

if hasattr(N, "__len__")

27
字符串具有__len__属性(所以我猜,严格来说不是标量类型)。 - xofer
36
如果 N 具有 __len__ 属性并且不是字符串类型,则可以正确处理字符串。 - apdnu
2
同时在 Python 3 中考虑字典。 - Bruno Henrique
这对迭代器无效。len(iter([1,2,3]))会抛出“TypeError: object of type 'list_iterator' has no len()”错误。 - Him

78

将@jamylak和@jpaddison3的答案结合起来,如果你需要对numpy数组进行鲁棒性处理并以与列表相同的方式处理它们,那么应该使用

import numpy as np
isinstance(P, (list, tuple, np.ndarray))

这是针对列表、元组和NumPy数组子类的强韧性。

如果您还希望对所有其他序列子类(不仅仅是列表和元组)具有强韧性,请使用

import collections
import numpy as np
isinstance(P, (collections.Sequence, np.ndarray))

为什么你应该用isinstance这种方式而不是将type(P)与目标值进行比较?以下是一个示例,我们创建并研究NewList的行为,这是一个列表的微不足道的子类。

>>> class NewList(list):
...     isThisAList = '???'
... 
>>> x = NewList([0,1])
>>> y = list([0,1])
>>> print x
[0, 1]
>>> print y
[0, 1]
>>> x==y
True
>>> type(x)
<class '__main__.NewList'>
>>> type(x) is list
False
>>> type(y) is list
True
>>> type(x).__name__
'NewList'
>>> isinstance(x, list)
True

尽管xy比较相等,但使用type处理它们会导致不同的行为。然而,由于xlist的一个子类实例,使用isinstance(x,list)可以获得所需的行为,并以相同的方式处理xy


4
这是最适合我需求的答案。我只是添加了set。因为我不想针对字典进行鲁棒性测试。isinstance(P, (list, tuple, set, np.ndarray)) - Santiago

64

在numpy中是否有isscalar()的等价函数? 是的。

>>> np.isscalar(3.1)
True
>>> np.isscalar([3.1])
False
>>> np.isscalar(False)
True
>>> np.isscalar('abcd')
True

7
更好的写法及示例:>>> np.isscalar('abcd') 的返回值为 True - Syrtis Major
1
谢谢!这个示例比上面的任何一个都更加通用,应该优先考虑使用。它也是对OP问题的直接回答。 - Cristóbal Sifón
3
好的。虽然有一个需要注意的地方,就是isscalar(None)返回False。Numpy将其实现为return (isinstance(num, generic) or type(num) in ScalarType or isinstance(num, numbers.Number)) - Shital Shah
8
很遗憾, numpy.isscalar() 函数存在许多不可调和的设计缺陷,并且可能会在未来的修订中被弃用。引用 官方文档 的说法:“在几乎所有情况下,应该使用np.ndim(x) == 0而不是np.isscaler(x),因为前者还将正确地返回0d数组的真值。” 因此,一个强大且向前兼容的替代 numpy.isscalar() 的方法是简单包装 numpy.ndim(),例如:def is_scalar(obj): return np.ndim(obj) == 0 - Cecil Curry
实际上这个不应该被点赞,因为np.isscalar容易引起混淆。官方文档建议在任何情况下都使用np.array.ndim,即使np.isscalar(np.array(12))返回False,但由于np.array(12).ndim为0,所以应该被视为标量。 - knh190
它在映射上不起作用。 np.ndim({'a':'b', 'c':'d'}) == 0 - David Sauter

31

虽然@jamylak的方法更好,但这里是另一种方法

>>> N=[2,3,5]
>>> P = 5
>>> type(P) in (tuple, list)
False
>>> type(N) in (tuple, list)
True

4
如果给答案投反对票的人也能给一个理由就好了。 - Sukrit Kalra
我实际上已经点赞了,但后来意识到它在2.7中不起作用:>>> p=[]
type(p) in (list) Traceback (most recent call last): File "<stdin>", line 1, in <module>
- Oleg Gryb
尝试使用type(p) in (list, ) - Sukrit Kalra
啊,右边是一个元组而不是列表,懂了,谢谢,现在它可以工作了。我很抱歉,我不能点赞两次 - 这是迄今为止最好的解决方案 :) - Oleg Gryb

7
另一种替代方法(使用类名属性):

N = [2,3,5]
P = 5

type(N).__name__ == 'list'
True

type(P).__name__ == 'int'
True

type(N).__name__ in ('list', 'tuple')
True

不需要导入任何东西。


2
这样做与仅使用 type(N) is listtype(N) is int 相比没有任何优势。正如其他答案所提到的,进行严格的类型相等性检查通常不如使用 isinstance() 检查更可取,后者可以考虑子类。 - Aaron D

4
这是我发现的最佳方法:检查__len____getitem__是否存在。
你可能会问为什么?原因包括:
  1. 流行的方法isinstance(obj, abc.Sequence)在某些对象上失败,包括PyTorch的Tensor,因为它们没有实现__contains__
  2. 不幸的是,Python的collections.abc中没有只检查__len____getitem__的方法,而我认为这是类似数组的对象的最小方法。
  3. 它适用于列表、元组、ndarray、张量等。
所以,话不多说:
def is_array_like(obj, string_is_array=False, tuple_is_array=True):
    result = hasattr(obj, "__len__") and hasattr(obj, '__getitem__') 
    if result and not string_is_array and isinstance(obj, (str, abc.ByteString)):
        result = False
    if result and not tuple_is_array and isinstance(obj, tuple):
        result = False
    return result

请注意,我添加了默认参数,因为大多数情况下您可能希望将字符串视为值而不是数组。元组也是如此。

这在标量(TensorFlow)张量上效果不佳,因为它们具有__len__方法,但如果您尝试在标量张量上调用它,则会引发错误:TypeError:标量张量没有len()。 TensorFlow的行为有点令人恼火... - Ben Farmer
为了处理这个问题,我首先会做类似于 if hasattr(obj,"shape") and obj.shape==() 的操作来检查这些“标量数组”的情况。 - Ben Farmer

4
回答标题的问题,判断变量是否是标量的一种直接方法是尝试将其转换为浮点数。如果出现“TypeError”错误,则不是标量。
N = [1, 2, 3]
try:
    float(N)
except TypeError:
    print('it is not a scalar')
else:
    print('it is a scalar')

3
这个答案有什么问题吗?当执行isinstance(np.arange(10), collections.Sequence)时,所选答案会失败。 - Stefano

3
>>> N=[2,3,5]
>>> P = 5
>>> type(P)==type(0)
True
>>> type([1,2])==type(N)
True
>>> type(P)==type([1,2])
False

3

我很惊讶这样一个基础问题在Python中似乎没有即时的答案。据我所知,几乎所有提出的答案都使用某种类型检查,这通常不建议在Python中使用,并且它们似乎仅限于特定情况(它们无法处理不同的数字类型或不是元组或列表的通用可迭代对象)。

对我来说,更好的方法是导入numpy并使用array.size,例如:

>>> a=1
>>> np.array(a)
Out[1]: array(1)

>>> np.array(a).size
Out[2]: 1

>>> np.array([1,2]).size
Out[3]: 2

>>> np.array('125')
Out[4]: 1

注意:
>>> len(np.array([1,2]))

Out[5]: 2

但是:

>>> len(np.array(a))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-40-f5055b93f729> in <module>()
----> 1 len(np.array(a))

TypeError: len() of unsized object

1
我也很惊讶,他们似乎都没有涉及到生成器。 - RhysC
它也不能在映射上工作:>>> np.array({1:2, 3:4}).size == 1 - David Sauter
1
这是因为np.array函数创建了一个dtype为“object”的数组,其中包含一个包含字典(或生成器)的单个元素。使用np.array(list(a.items())).sizenp.array(list(a.keys())).size会得到不同的结果。 - Vincenzooo

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