【问题标题】:Python: confusion between types and dtypesPython:类型和数据类型之间的混淆
【发布时间】:2015-03-03 01:41:26
【问题描述】:

假设我输入:

a = uint8(200)
a*2

那么结果是400,重铸为uint16类型。

但是:

a = array([200],dtype=uint8)
a*2

结果是

array([144], dtype=uint8)

乘法以 256 为模,以确保结果保持在一个字节中。

我对“类型”和“dtypes”以及其中一个优先于另一个使用感到困惑。如您所见,类型可能会对输出产生重大影响。

例如,我可以创建一个 dtype uint8 的单个数字,以便对该数字执行模 256 的操作吗?或者,我可以创建一个类型(不是 dtype)uint8 的数组,以便对其进行的操作将产生 0-255 范围之外的值吗?

【问题讨论】:

    标签: python numpy types unsigned-integer


    【解决方案1】:

    简单而高级的答案是,NumPy 在 Python 的类型系统之上添加了第二个类型系统。

    当您请求 NumPy 对象的 type 时,您会得到 容器 的类型——类似于 numpy.ndarray。但是当你请求dtype 时,你会得到elements 的(numpy-managed)类型。

    >>> from numpy import *
    >>> arr = array([1.0, 4.0, 3.14])
    >>> type(arr)
    <type 'numpy.ndarray'>
    >>> arr.dtype
    dtype('float64')
    

    有时,当使用默认的 float 类型时,元素数据类型 (dtype) 等效于 Python 类型。但这是等价的,不完全相同:

    >>> arr.dtype == float
    True
    >>> arr.dtype is float
    False
    

    在其他情况下,没有等效的 Python 类型。例如,当您指定 uint8 时。此类数据值/类型可以由 Python 管理,但与 C、Rust 和其他“系统语言”不同,管理直接与机器数据类型对齐的值(如 uint8 与“无符号字节”计算紧密对齐)不是Python 的常见用例。

    因此,重要的是 NumPy 提供了像数组和矩阵这样的容器,它们在自己的类型系统下运行。它提供了一堆非常有用、优化良好的例程来操作这些容器(及其元素)。如果你小心的话,你可以混合搭配 NumPy 和普通的 Python 计算。

    没有 Python 类型 uint8。有一个名为 uint8 的构造函数,调用时返回一个 NumPy 类型:

    >>> u = uint8(44)
    >>> u
    44
    >>> u.dtype
    dtype('uint8')
    >>> type(u)
    <type 'numpy.uint8'>
    

    那么“我可以创建一个类型为(不是 dtype)uint8 的数组吗...?”不,你不能。没有这样的动物。 你可以 在不使用 NumPy arrays(又名 NumPy 标量值)的情况下进行受限于 uint8 规则的计算。例如:

    >>> uint8(44 + 1000)
    20
    >>> uint8(44) + uint8(1000)
    20
    

    但是如果你想计算 mod 256 的值,使用 Python 的 mod 运算符可能更容易:

    >> (44 + 1000) % 256
    20
    

    将大于 255 的数据值驱动为 uint8 数据类型,然后进行算术运算是获得 mod-256 算术的一种相当后门的方法。如果您不小心,您将导致 Python 将您的值“升级”为完整整数(杀死您的 mod-256 方案),或触发溢出异常(因为在 C 和机器语言中运行良好的技巧通常被标记为高级语言)。

    【讨论】:

      【解决方案2】:

      NumPy 数组的typenumpy.ndarray;这只是 Python 对象的类型(例如,类似于 type("hello")str 的方式)。

      dtype 只定义了内存中的字节如何被标量(即单个数字)或数组解释以及处理字节的方式(例如int/float)。因此,您无需更改数组或标量的type,只需更改其dtype

      如您所见,如果您将两个标量相乘,则生成的数据类型是最小的“安全”类型,两个值都可以转换为该类型。但是,将数组和标量相乘只会返回相同数据类型的数组。函数np.inspect_typesdocumentation 清楚地表明特定标量或数组对象的dtype 何时更改:

      NumPy 中的类型提升与 C++ 等语言中的规则类似,但略有不同。当同时使用标量和数组时,数组的类型优先,并考虑标量的实际值。

      文档继续:

      如果只有标量或标量的最大类别高于数组的最大类别,则将数据类型与promote_types组合以产生返回值。

      所以对于np.uint8(200) * 2,两个标量,结果数据类型将是np.promote_types返回的类型:

      >>> np.promote_types(np.uint8, int)
      dtype('int32')
      

      对于np.array([200], dtype=np.uint8) * 2,数组的数据类型优先于标量int,并返回np.uint8 数据类型。

      要解决关于在操作期间保留标量 dtype 的最后一个问题,您必须限制您使用的任何其他标量的数据类型,以避免 NumPy 的自动 dtype 提升:

      >>> np.array([200], dtype=np.uint8) * np.uint8(2)
      144
      

      当然,另一种方法是简单地将单个值包装在 NumPy 数组中(然后 NumPy 不会将其转换为具有不同 dtype 标量的操作)。

      要在操作期间提升数组的类型,您可以先将任何标量包装在数组中:

      >>> np.array([200], dtype=np.uint8) * np.array([2])
      array([400])
      

      【讨论】:

      • "dtype 只定义了内存中的字节将如何被标量解释" → 它还定义了它们被解释的方式(例如int32 vs float32)。
      • 谢谢 Veedrac,我把这句话写得更准确了。
      【解决方案3】:

      numpy 数组包含相同类型的元素,因此np.array([200],dtype=uint8)具有一个值的数组,类型为uint8。当您执行np.uint8(200) 时,您没有数组,只有单个值。这有很大的不同。

      当对数组执行某些操作时,类型保持不变,与单个值是否溢出无关。数组中的自动向上转换是禁止的,因为整个数组的大小必须改变。仅当用户明确需要时才这样做。对单个值执行操作时,可以轻松向上转换,不会影响其他值。

      【讨论】:

        猜你喜欢
        • 2011-02-01
        • 1970-01-01
        • 2012-06-09
        • 2017-08-14
        • 2017-12-24
        • 1970-01-01
        • 1970-01-01
        • 2013-12-31
        • 1970-01-01
        相关资源
        最近更新 更多