【问题标题】:numpy: formal definition of "array_like" objects?numpy:“array_like”对象的正式定义?
【发布时间】:2017-03-15 16:13:06
【问题描述】:

在 numpy 中,许多对象的构造函数接受“array_like”作为第一个参数。是否有这样的对象的定义,无论是作为抽象元类,还是应该包含方法的文档??

【问题讨论】:

    标签: python numpy


    【解决方案1】:

    我将此作为评论发布,但我想我也会将其作为答案。

    事实证明,“类数组”的概念与其说是抽象基类或协议,不如说是关于如何处理各种对象的类数组。也就是说,类数组是关于当对象作为类数组参数提供时将如何处理该对象的声明。但几乎任何对象都可以作为类似数组的参数提供。

    对于许多对象,类似数组的处理方式与可迭代对象大致相同。序列对象也是如此:

    >>> x=[1,2,3]
    >>> a = np.array(x)
    >>> a[0]
    1
    

    但是,numpy 通常不会以相同的方式处理可迭代对象(当作为类似数组的参数提供时)。相反,它将类数组对象视为嵌套对象或原子对象,具体取决于类型。

    以下是一些可迭代对象的示例(这个想法听起来与类数组密切相关,但完全不同),但 numpy 将其视为原子对象。

    • 字符串(str 对象)
    • 字典/映射
    • 迭代器
    • 缓冲区/文件处理程序

    array() 工厂将所有这些都视为原子(即非嵌套)值。简而言之:类数组绝不是typing.Iterable 的同义词。

    【讨论】:

      【解决方案2】:

      NumPy 1.21 引入了numpy.typing.ArrayLike


      原来在这个commit中定义如下:

      class _SupportsArray(Protocol):
          @overload
          def __array__(self, __dtype: DtypeLike = ...) -> ndarray: ...
          @overload
          def __array__(self, dtype: DtypeLike = ...) -> ndarray: ...
      
      ArrayLike = Union[bool, int, float, complex, _SupportsArray, Sequence]
      

      不过,ArrayLike 的更新定义可以在 numpy/typing/_array_like.py 中找到:

      _ArrayLike = Union[
          _NestedSequence[_SupportsArray[_DType]],
          _NestedSequence[_T],
      ]
      
      ArrayLike = Union[
          _RecursiveSequence,
          _ArrayLike[
              "dtype[Any]",
              Union[bool, int, float, complex, str, bytes]
          ],
      ]
      

      【讨论】:

        【解决方案3】:

        事实证明,从技术上讲,几乎所有东西都是类似数组的。 “类数组”更像是对如何解释输入的陈述,而不是对输入可以是什么的限制;如果参数被记录为类数组,NumPy 将尝试将其解释为数组。

        除了the nearly tautological one 之外,没有关于类数组的正式定义——类数组是np.array 可以转换为ndarray 的任何Python 对象。要超越这一点,您需要研究source code

        NPY_NO_EXPORT PyObject *
        PyArray_FromAny(PyObject *op, PyArray_Descr *newtype, int min_depth,
                        int max_depth, int flags, PyObject *context)
        {
            /*
             * This is the main code to make a NumPy array from a Python
             * Object.  It is called from many different places.
             */
            PyArrayObject *arr = NULL, *ret;
            PyArray_Descr *dtype = NULL;
            int ndim = 0;
            npy_intp dims[NPY_MAXDIMS];
        
            /* Get either the array or its parameters if it isn't an array */
            if (PyArray_GetArrayParamsFromObject(op, newtype,
                                0, &dtype,
                                &ndim, dims, &arr, context) < 0) {
                Py_XDECREF(newtype);
                return NULL;
            }
            ...
        

        特别有趣的是PyArray_GetArrayParamsFromObject,它的cmets枚举了np.array期望的对象类型:

        NPY_NO_EXPORT int
        PyArray_GetArrayParamsFromObject(PyObject *op,
                                PyArray_Descr *requested_dtype,
                                npy_bool writeable,
                                PyArray_Descr **out_dtype,
                                int *out_ndim, npy_intp *out_dims,
                                PyArrayObject **out_arr, PyObject *context)
        {
            PyObject *tmp;
        
            /* If op is an array */
        
            /* If op is a NumPy scalar */
        
            /* If op is a Python scalar */
        
            /* If op supports the PEP 3118 buffer interface */
        
            /* If op supports the __array_struct__ or __array_interface__ interface */
        
            /*
             * If op supplies the __array__ function.
             * The documentation says this should produce a copy, so
             * we skip this method if writeable is true, because the intent
             * of writeable is to modify the operand.
             * XXX: If the implementation is wrong, and/or if actual
             *      usage requires this behave differently,
             *      this should be changed!
             */
        
            /* Try to treat op as a list of lists */
        
            /* Anything can be viewed as an object, unless it needs to be writeable */
        
        }
        

        所以通过研究源代码我们可以得出一个类似数组的is

        【讨论】:

        • 之前的 SO 问题涉及为什么 np.array 将遍历 list(或元组),而不是字典。以及如何使自定义对象的行为更像字典。 np.array([{1:2},{3:4}]) 生成一维对象数组,即使 {1:2} 是可迭代的。
        • 值得注意的是,“尝试将 op 视为列表列表”之后的下一行是通过检查 PySequence_Check 来实现的,其中 generally checks to see if the object implements __getitem__ and __len__。换句话说:实现这两个函数的任何东西通常都会作为“类数组”传递。
        • FWIW,看起来 Tensorflow 采用 __array__ 路线,为 EagerTensors 实现它,因此它们可以输入到 NumPy 方法中,c.f. github.com/tensorflow/tensorflow/blob/master/tensorflow/python/…
        • 这个答案没有提到的一个非常重要的微妙之处是如何处理类似数组的对象的类似数组。最大的例子是字符串(str 对象)。在 numpy 和 scipy 中,字符串被视为原子,而不是熟悉 python 的人可能期望的序列。其他示例是迭代器和缓冲区/文件处理程序。 array() 工厂将所有这些都视为原子(即非嵌套)值。简而言之:类数组绝不是typing.Iterable 的同义词。
        • 源代码链接已过期。这是一个固定链接github.com/numpy/numpy/blob/…
        【解决方案4】:

        这只是一个概念,除了其他答案中提到的User Guide part中的解释之外,还有一个official statement (in Numpy Glossary)

        array_like

        任何可以解释为 ndarray 的序列。这包括 嵌套列表、元组、标量和现有数组。

        所以即使是标量也可以考虑在内,就像np.array(1024)

        【讨论】:

          【解决方案5】:

          "array-like" 一词在 NumPy 中使用,指的是可以作为第一个参数传递给 numpy.array() 以创建数组 () 的任何内容。

          根据Numpy document

          一般来说,在 Python 中以类数组结构排列的数值数据可以通过使用 array() 函数转换为数组。最明显的例子是列表和元组。有关其使用的详细信息,请参阅 array() 的文档。一些对象可能支持数组协议并允许以这种方式转换为数组。找出是否可以使用 array() 将对象转换为 numpy 数组的一种简单方法是简单地以交互方式尝试它,看看它是否有效! (Python 方式)。

          欲了解更多信息,请阅读:

          【讨论】:

          • 是的,很明显,但是什么定义了 array_like 对象?它似乎适用于所有可迭代对象,但支持哪些其他方法。我在寻找一个正式的定义
          • @blue_note 需要明确的是,最新版本的 numpy.array 直接转换可迭代对象,但旧版本(例如 CentOS 7 附带的 1.7)并非总是如此。我有一个将 iter 暴露给底层数值数据的对象,但 np.array 会生成一个 dtype=object 的 1x1 数组
          猜你喜欢
          • 1970-01-01
          • 2023-01-07
          • 1970-01-01
          • 2021-05-22
          • 1970-01-01
          • 1970-01-01
          • 2013-03-12
          • 1970-01-01
          • 2016-08-13
          相关资源
          最近更新 更多