【python】如何包装 numpy 的数组

news/2025/3/14 5:34:23/

一、说明

        Numpy的数组是强大的对象,通常用作更复杂的对象(如pandas或xarray)的基本数据结构。话虽如此,您当然也可以在自己的类中使用numpy的强大数组 - 为此,您基本上有2种方法:

  • 子类方法:创建从 Numpy.ndarray 继承的类
  • 容器方法:创建将数组作为属性的类

在本文中,我们将看到如何使用容器方法来包装 numpy 的数组来正确创建自己的自定义类。

二、 物理格式的项目

        让我们举一个示例项目:我们想创建一个简单的项目来处理物理单位和尺寸,创建长度或重量的数组,然后使用这些数组来计算平均身高或[身体质量指数](https://en.wikipedia.org/wiki/Body_mass_index)。我们希望依靠 numpy 来完成繁重的数值计算(如加法、减法、幂),但我们也希望能够处理像 numpy 数组这样的实例,比如 or .[1, 2, 3] meter [55 65 8] kilogramnp.sort(weights)np.min(heights)

        为此,我们将创建一个使用容器方法来包装 numpy 数组的新类。数值将存储为普通 numpy 数组,物理维度存储为字符串:

import numpy as npclass Physical():def __init__(self, value, unit=""):self.value = value # store the numerical value as a plain numpy arrayself.unit = unitdef __repr__(self):return f"<Physical:({self.value}, {self.unit})"def __str__(self):return f"{self.value} {self.unit}"weights = Physical(np.array([55.6, 45.7, 80.3]), "kilogram")
print(weights) # [55.6 45.7 80.3] kilogram

物理阵列的第一个实现

这将简单地打印:。同样,此字符串后面的数字列表是存储在 中的实际 numpy 数组。[55.6 45.7 80.3] kilogramself.value

现在这是毫无用处的:我们不能让这个对象与其他任何东西交互,所以我们添加了基本操作,如加法或乘法与其他实例:Physical

import numpy as npclass Physical():def __init__(self, value, unit=""):self.value = valueself.unit = unitdef __repr__(self):return f"<Physical:({self.value}, {self.unit})"def __str__(self):return f"{self.value} {self.unit}"def __add__(self, other):if self.unit == other.unit:return Physical(self.value + other.value, self.unit)else:raise ValueError("Physical objects must have same unit to be added.")def __sub__(self, other):if self.unit == other.unit:return Physical(self.value - other.value, self.unit)else:raise ValueError("Physical objects must have same unit to be subtracted.")def __mul__(self, other):return Physical(self.value * other.value, f"{self.unit}*{other.unit}")def __truediv__(self, other):return Physical(self.value / other.value, f"{self.unit}/{other.unit}")def __pow__(self, powfac):return Physical(self.value**powfac, f"{self.unit}^{powfac}")weights = Physical(np.array([55.6, 45.7, 80.3]), "kilogram")
heights = Physical(np.array([1.64, 1.85, 1.77]), "meter")
print(weights) # [55.6 45.7 80.3] kilogram
print(heights) # [1.64 1.85 1.77] meter
print(heights + heights) # [3.28 3.7  3.54] meter
# print(height + weights) # raises ValueError
print(heights**2) # [2.6896 3.4225 3.1329] meter^2

现在可以将物理阵列与其他物理阵列相加或相乘。

请注意,在添加或减去物理之前,我们首先检查它们是否具有相同的单位:您不能用重量添加长度(或土豆加胡萝卜,或马用驴)。

这很好,我们现在可以计算一个身体质量指数 (BMI) 数组,给定一个以米为单位的高度数组和一个以公斤为单位的重量数组。BMI 只是通过将重量除以平方高度来给出的,即:

BMI =体重(公斤)/身高(米)^2

weights = Physical(np.array([55.6, 45.7, 80.3]), "kilogram")
heights = Physical(np.array([1.64, 1.85, 1.77]), "meter")
bmi = weights/heights**2
print(bmi) # [20.67221892 13.35281227 25.63120432] kilogram/meter^2

        万岁!我们使用身高数组和身高数组计算了一个身体质量指数数组,使用后面的numpy数组来执行实际的数值计算。但是numpy的数组还有很多东西可以提供,这就是它变得非常有趣的地方。

三、实现 numpy 函数支持

Numpy 提供了许多用于数组的有用函数。仅举几例:

  • np.sinnp.cosnp.tan
  • np.expnp.lognp.log10
  • np.addnp.multiplynp.divide
  • np.minnp.maxnp.argminnp.argmax
  • np.floornp.ceilnp.trunc
  • np.concatenatenp.vstack

等等。您可以在他们的网站上找到numpy提供的所有内容:Routines — NumPy v1.25 Manual。

让我们尝试在我们的类中使用其中之一:

np.mean(bmi)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
...
<ipython-input-10-538e4626f6f7> in __truediv__(self, other)31 32     def __truediv__(self, other):
---> 33         return Physical(self._value / other._value, f"{self._unit}/{other._unit}")34 35     def __pow__(self, powfac):AttributeError: 'numpy.int64' object has no attribute '_value'

尝试调用我们的物理实例会引发一个 因为 numpy 依赖于整数的加法和除法,而我们的类没有正确实现这种操作。所以我们必须在某个地方告诉 numpy 我们想要如何表现。np.meanbmiAttributeErrornp.mean(bmi)

这就是界面发挥作用的地方。__array_function__

该接口只是一个规范化过程,用于重载(某些)numpy 函数如何处理类中的参数。__array_function__

让我们看一个轻量级的例子来处理我们的调用:np.mean(bmi)

import numpy as np# FIRST
HANDLED_FUNCTIONS = {}class Physical():def __init__(self, value, unit=""):self._value = valueself._unit = unit# ... other methods here, see above# SECONDdef __array_function__(self, func, types, args, kwargs):if func not in HANDLED_FUNCTIONS:return NotImplemented# Note: this allows subclasses that don't override# __array_function__ to handle MyArray objectsif not all(issubclass(t, Physical) for t in types):return NotImplementedreturn HANDLED_FUNCTIONS[func](*args, **kwargs)# THIRD
def implements(numpy_function):"""Register an __array_function__ implementation for Physical objects."""def decorator(func):HANDLED_FUNCTIONS[numpy_function] = funcreturn funcreturn decorator# FOURTH
@implements(np.mean)
def np_mean_for_physical(x, *args, **kwargs):# first compute the numerical value, with no notion of unitmean_value = np.mean(x._value, *args, **kwargs)# construct a Physical instance with the result, using the same unitreturn Physical(mean_value, x._unit)weights = Physical(np.array([55.6, 45.7, 80.3]), "kilogram")
heights = Physical(np.array([1.64, 1.85, 1.77]), "meter")
bmi = weights/heights**2print(np.mean(bmi)) # 19.885411834844252 kilogram/meter^2

使用 __array_function__ 接口实现 np.mean 支持

再次欢呼,返回我们物理数组的“平均值”,这确实是一个物理量,单位为“千克/米^2”。np.mean(bmi)

让我们回顾一下我们添加到代码中以实现此目的的内容。有 4 点需要注意:

  1. 首先,我们在类定义之上创建一个空字典,称为 。HANDLED_FUNCTION = {}
  2. 其次,我们在类中添加一个调用的方法,该方法采用一个名为 .我们将在一分钟内回到此方法的内容。__array_function__func
  3. 第三,我们创建一个装饰器构造函数:这是一个返回装饰器的函数(即,另一个将函数作为参数的函数)。我们的装饰器只是在我们的字典中创建 numpy 函数和 之间的对应关系,后者是我们版本的 numpy 函数。implementsHANDLED_FUNCTIONfunc
  4. 第四,我们实现了 numpy 处理物理实例的平均值,当调用时是物理实例。它具有与 大致相同的签名,并执行以下操作:np.mean(x)xnp.mean
  • 使用 x 的值计算数值平均值,这是一个普通数组。x._value
  • 然后使用平均值作为值和输入的单位作为单位创建新的物理实例。
  • 最后,我们在该函数上使用装饰器。implements

那么当我们打电话时会发生什么?np.mean(bmi)

好吧,由于 numpy 无法计算平均值,正如我们上面看到的,它检查是否有方法,并使用在 上使用的函数调用它,即 : 。bmi__array_function__bminp.meanbmi.__array_function__(np.mean, *args, **kwargs)

由于已经在 中注册,我们用它来调用我们的版本:这里相当于 。np.meanHANDELED_FUNCTIONSnp.meanHANDLED_FUNCTIONS[np.mean](*args, **kwargs)np_mean_for_physical(*args, **kwargs)

这就是如何使 numpy 的函数与您的自定义类一起工作。

不幸的是,这并不完全正确。此接口仅适用于某些 numpy 函数,但不适用于全部。

还记得上面的功能列表吗?好吧,我们可以将它们分为 2 个子列表:常规 numpy 函数和 numpy 通用函数 — 或简称“ufuncs”:

  • Numpy 函数 : , , , , ,np.minnp.maxnp.argminnp.argmaxnp.concatenatenp.vstack.
  • Numpy ufuncs : , , ,, , ,, , , , ,np.sinnp.cosnp.tannp.expnp.lognp.log10np.addnp.multiplynp.dividenp.floornp.ceilnp.trunc

我们看到了如何使用 实现 numpy 函数支持。在下一篇文章中,我们现在将看到如何使用该接口添加对“ufuncs”的支持。__array_function____array_ufunc__

四、总结一下 

  • 使用 numpy 数组的容器方法包括在自定义类实例中将数组设置为属性(与子类化数组相反)。
  • 要使类使用 numpy 函数调用(如 ),必须在类中实现接口。np.mean(my_array_like_instance)__array_function__
  • 这基本上是通过向类添加一个方法,编写自己的包装器(就像我们对 ) 所做的那样,并将它们链接在一起(就像我们对查找字典所做的那样)。__array_function__np_mean_for_physicalHANDLED_FUNCTIONS
  • 请注意,这仅适用于“常规”numpy 函数。对于numpy的“通用”函数,您还需要实现该接口。__array_ufunc__

这个主题相当广泛,所以这里有几个链接,你应该阅读,以更好地掌握利害关系:

  • 容器方法:Writing custom array containers — NumPy v1.25 Manual
  • __array_function__参考资料: Standard array subclasses — NumPy v1.25 Manual
  • 参考资料: Universal functions (ufunc) — NumPy v1.25 Manual

以下是我们在本文中编写的完整代码:

import numpy as npHANDLED_FUNCTIONS = {}class Physical():def __init__(self, value, unit=""):self._value = valueself._unit = unitdef __repr__(self):return f"<Physical:({self._value}, {self._unit})"def __str__(self):return f"{self._value} {self._unit}"def __add__(self, other):if self._unit == other._unit:return Physical(self._value + other._value, self._unit)else:raise ValueError("Physical objects must have same unit to be added.")def __sub__(self, other):if self._unit == other._unit:return Physical(self._value - other._value, self._unit)else:raise ValueError("Physical objects must have same unit to be subtracted.")def __mul__(self, other):return Physical(self._value * other._value, f"{self._unit}*{other._unit}")def __truediv__(self, other):return Physical(self._value / other._value, f"{self._unit}/{other._unit}")def __pow__(self, powfac):return Physical(self._value**powfac, f"{self._unit}^{powfac}")def __array_function__(self, func, types, args, kwargs):if func not in HANDLED_FUNCTIONS:return NotImplemented# Note: this allows subclasses that don't override# __array_function__ to handle Physical objectsif not all(issubclass(t, Physical) for t in types):return NotImplementedreturn HANDLED_FUNCTIONS[func](*args, **kwargs)def implements(numpy_function):"""Register an __array_function__ implementation for Physical objects."""def decorator(func):HANDLED_FUNCTIONS[numpy_function] = funcreturn funcreturn decorator@implements(np.mean)
def np_mean_for_physical(x, *args, **kwargs):# first compute the numerical value, with no notion of unitmean_value = np.mean(x._value, *args, **kwargs)# construct a Physical instance with the result, using the same unitreturn Physical(mean_value, x._unit)weights = Physical(np.array([55.6, 45.7, 80.3]), "kilogram")
heights = Physical(np.array([1.64, 1.85, 1.77]), "meter")
print(weights)
print(heights)
print(heights + heights)
print(heights**2)
ratio = weights/heights
print(ratio)
bmi = weights/heights**2
print(bmi)
print(np.mean(bmi))

干杯!


http://www.ppmy.cn/news/974426.html

相关文章

(数据结构)哈夫曼编码实现(C语言)

(数据结构)哈夫曼编码实现&#xff08;C语言&#xff09; 哈夫曼的编码:从一堆数组当中取出来最小的两个值&#xff0c;按照左下右大的进行绘制&#xff0c;将两个权值之和&#xff0c;放入队列当中&#xff0c;然后再进行取出两个小的&#xff0c;以此类推&#xff0c;直到全部…

opencv 之 外接多边形(矩形、圆、三角形、椭圆、多边形)使用详解

opencv 之 外接多边形&#xff08;矩形、圆、三角形、椭圆、多边形&#xff09;使用详解 本文主要讲述opencv中的外接多边形的使用&#xff1a; 多边形近似外接矩形、最小外接矩形最小外接圆外接三角形椭圆拟合凸包 将重点讲述最小外接矩形的使用 1. API介绍 #多边形近似 v…

视频的行为识别

1. 概述 使用DL方法解决视频中行为识别/动作识别的问题解决思路有三个分支&#xff1a;分别是two-stream(双流)方法&#xff0c;C3D方法以及CNN-LSTM方法。本文将从算法介绍、算法架构、参数配置、训练集预处理、算法优势及原因、运行结果六个方面对每种算法进行阐释&#xff…

人脸图片及视频识别

import cv2 cap cv2.VideoCapture(0) cap.set(3,640)#改变高度 cap.set(4,480)#改变宽度 cap.set(10,100)#改变亮度 def Face_Detect_Pic(image):gray cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)cv2.imshow("gray", gray)face_detector cv2.CascadeClassifier(&quo…

ChatGPT在舆情分析和情感监测中的应用如何?

ChatGPT在舆情分析和情感监测领域的应用具有广泛的潜力。舆情分析是指通过对社交媒体、新闻、论坛等渠道中的文本数据进行分析&#xff0c;了解公众对特定事件、产品或品牌的态度和情感。情感监测是一种文本情感分析任务&#xff0c;旨在识别文本中所表达的情感&#xff0c;如喜…

视频理解多模态大模型(大模型基础、微调、视频理解基础)

转眼就要博0了&#xff0c;导师开始让我看视频理解多模态方向的内容&#xff0c;重新一遍打基础吧&#xff0c;从Python&#xff0c;到NLP&#xff0c;再到视频理解&#xff0c;最后加上凸优化&#xff0c;一步一步来&#xff0c;疯学一个暑假。写这个博客作为我的笔记以及好文…

Vue数据劫持源码分析

Vue.js 3.0 中的数据劫持是通过 Proxy 实现的&#xff0c;而不是使用 Vue.js 2.x 中的 Object.defineProperty。Proxy 是 ES6 中引入的一个新特性&#xff0c;它提供了更强大的拦截和自定义操作对象的能力。 以下是 Vue.js 3.0 中数据劫持的简化版源码分析&#xff1a; 创建 …

进程信号的理解

进程信号 1. 信号的概念2. 信号的产生3. 信号的保存1. 信号其他相关常见概念2. 在内核中的表示3.信号集操作函数 4. 信号的处理&#xff08;捕捉&#xff09; 1. 信号的概念 信号的一生&#xff0c;进程信号从产生到被处理所经历的过程一共分成了三步&#xff1a;信号产生、信…