为什么直接向 numpy 数组添加新属性不起作用,但通过子类化可以呢?
Why does directly adding a new attribute to numpy arrays not work but doing so by subclassing does?
我想创建一个 class,它的行为类似于 numpy 数组,但具有额外的 methods/attributes 并且一直在阅读但不完全理解 numpy 的 guide on subclassing ndarray。在那个网页上有一个例子,上面写着
import numpy as np
class RealisticInfoArray(np.ndarray):
def __new__(cls, input_array, info=None):
# Input array is an already formed ndarray instance
# We first cast to be our class type
obj = np.asarray(input_array).view(cls)
# add the new attribute to the created instance
obj.info = info
# Finally, we must return the newly created object:
return obj
def __array_finalize__(self, obj):
# see InfoArray.__array_finalize__ for comments
if obj is None: return
self.info = getattr(obj, 'info', None)
我很困惑为什么这些行
obj = np.asarray(input_array).view(cls)
# add the new attribute to the created instance
obj.info = info
不加注
AttributeError: 'numpy.ndarray' object has no attribute 'info'
我在 Add an attribute to a Numpy array in runtime 中读到它与用 C 实现的 numpy 数组有关。故事就这样结束了吗? Python 如何“知道”np.array 是在 C 中实现的,而不是 Python class,您可以轻松地向其添加新属性?
C 实现的 classes 必须不遗余力地拥有一个 __dict__
(这是存储动态定义的属性的地方);他们可以做到,但他们通常不会这样做,除非他们试图模拟其他允许它的类型(例如 functools.partial
允许您分配任意属性,因为常规函数允许它,并且它试图保持兼容) ,因为它们有更有效的方法来存储其预定义的属性集(通常作为 PyObject
header 中的原始值或指针)。
省略 __dict__
可以节省每个实例指针的内存开销(4-8 字节),加上实际 dict
本身的成本(104 字节,即使是空的 __dict__
在 64 位 CPython 3.9.5)。对于您创建许多实例的简单类型,包括几乎从不使用的 __dict__
会大量增加开销。例如,CPython 3.9.5 x64 float
消耗 24 个字节来存储 8 个字节的“真实”数据,这意味着 16 个字节是开销;如果它允许任意属性分配,即使 __dict__
是延迟创建的,开销也会从 16 字节跳到 24 字节,如果它不是延迟创建的(通过删除对“允许 [=10] 的检查来加速其他代码=] 但它可能尚未初始化”,每次访问都必须执行)开销将从 24 字节跳到 128 字节(加上分配器开销浪费未严格分配但丢失的字节的机会的两倍round-off 和碎片问题),所有这些都只是 8 个字节的“真实”数据。存储 500 万 float
s 将使 40 MB 的原始 C 成本变为 __dict__
-less CPython 120 MB 的成本(忽略实际容纳它们的容器;这将增加至少 40 MB) 到 680 MB,全部在 off-chance 上您可能想在其中 一个 上定义任意属性。
User-defined classes 在默认情况下有 __dict__
(这是 只有 默认情况下它们存储属性的地方,无论在 __init__
中定义或由 class 的使用者手动添加),并且仅在 class 及其所有 parent class 时省略它,定义一个class-level __slots__
(并且只有当他们都从 __slots__
中省略 '__dict__'
时)。
回答您的具体问题“Python 如何知道”np.array 是用 C 实现的,而不是 Python class可以轻松地向? 添加新属性,至少对于 CPython,它 测试 tp_dictoffset
on the instance's class 是否为 non-zero;如果它是零,那么 class 的实例缺少 __dict__
并且添加任意属性是不合法的,如果它是 non-zero,它告诉解释器从开始(或结束, PyObject
header 的负数)需要查找 __dict__
指针。 tp_dictoffset
在定义 class 时被初始化,在 C 实现的 classes 的情况下手动初始化想要支持任意属性,并由 [=55= 的解释器机器代表您] classes.
我想创建一个 class,它的行为类似于 numpy 数组,但具有额外的 methods/attributes 并且一直在阅读但不完全理解 numpy 的 guide on subclassing ndarray。在那个网页上有一个例子,上面写着
import numpy as np
class RealisticInfoArray(np.ndarray):
def __new__(cls, input_array, info=None):
# Input array is an already formed ndarray instance
# We first cast to be our class type
obj = np.asarray(input_array).view(cls)
# add the new attribute to the created instance
obj.info = info
# Finally, we must return the newly created object:
return obj
def __array_finalize__(self, obj):
# see InfoArray.__array_finalize__ for comments
if obj is None: return
self.info = getattr(obj, 'info', None)
我很困惑为什么这些行
obj = np.asarray(input_array).view(cls)
# add the new attribute to the created instance
obj.info = info
不加注
AttributeError: 'numpy.ndarray' object has no attribute 'info'
我在 Add an attribute to a Numpy array in runtime 中读到它与用 C 实现的 numpy 数组有关。故事就这样结束了吗? Python 如何“知道”np.array 是在 C 中实现的,而不是 Python class,您可以轻松地向其添加新属性?
C 实现的 classes 必须不遗余力地拥有一个 __dict__
(这是存储动态定义的属性的地方);他们可以做到,但他们通常不会这样做,除非他们试图模拟其他允许它的类型(例如 functools.partial
允许您分配任意属性,因为常规函数允许它,并且它试图保持兼容) ,因为它们有更有效的方法来存储其预定义的属性集(通常作为 PyObject
header 中的原始值或指针)。
省略 __dict__
可以节省每个实例指针的内存开销(4-8 字节),加上实际 dict
本身的成本(104 字节,即使是空的 __dict__
在 64 位 CPython 3.9.5)。对于您创建许多实例的简单类型,包括几乎从不使用的 __dict__
会大量增加开销。例如,CPython 3.9.5 x64 float
消耗 24 个字节来存储 8 个字节的“真实”数据,这意味着 16 个字节是开销;如果它允许任意属性分配,即使 __dict__
是延迟创建的,开销也会从 16 字节跳到 24 字节,如果它不是延迟创建的(通过删除对“允许 [=10] 的检查来加速其他代码=] 但它可能尚未初始化”,每次访问都必须执行)开销将从 24 字节跳到 128 字节(加上分配器开销浪费未严格分配但丢失的字节的机会的两倍round-off 和碎片问题),所有这些都只是 8 个字节的“真实”数据。存储 500 万 float
s 将使 40 MB 的原始 C 成本变为 __dict__
-less CPython 120 MB 的成本(忽略实际容纳它们的容器;这将增加至少 40 MB) 到 680 MB,全部在 off-chance 上您可能想在其中 一个 上定义任意属性。
User-defined classes 在默认情况下有 __dict__
(这是 只有 默认情况下它们存储属性的地方,无论在 __init__
中定义或由 class 的使用者手动添加),并且仅在 class 及其所有 parent class 时省略它,定义一个class-level __slots__
(并且只有当他们都从 __slots__
中省略 '__dict__'
时)。
回答您的具体问题“Python 如何知道”np.array 是用 C 实现的,而不是 Python class可以轻松地向? 添加新属性,至少对于 CPython,它 测试 tp_dictoffset
on the instance's class 是否为 non-zero;如果它是零,那么 class 的实例缺少 __dict__
并且添加任意属性是不合法的,如果它是 non-zero,它告诉解释器从开始(或结束, PyObject
header 的负数)需要查找 __dict__
指针。 tp_dictoffset
在定义 class 时被初始化,在 C 实现的 classes 的情况下手动初始化想要支持任意属性,并由 [=55= 的解释器机器代表您] classes.