为何直接给numpy数组新增属性报错,通过子类化却可以实现?
为什么两行代码不会抛出属性错误
你疑惑的代码不会报错的核心原因是:调用.view(cls)后返回的对象已经不是原生np.ndarray的实例,而是你自定义的RealisticInfoArray子类的实例。view方法的作用是不修改底层存储的数组数据,仅换用指定的类类型来封装这部分数据,所以np.asarray(input_array).view(RealisticInfoArray)得到的对象类型就是你自己写的子类,而非原生numpy数组。
原生np.ndarray是C实现的类,为了尽可能优化内存占用,它的实例默认没有分配__dict__的存储空间。__dict__是Python用来存储实例动态属性的内置字典,没有这个结构的话,运行时给实例加未预先定义的属性就会抛出AttributeError,这就是普通numpy数组没法在运行时随意加属性的原因。
但你通过Python语法继承np.ndarray实现的自定义子类,默认是支持__dict__的,所以完全可以自由新增自定义的属性和方法,obj.info = info这步操作当然不会报错。
关于动态属性限制的补充说明
这不全是C实现的原因,Python判断一个类的实例能不能加动态属性,和它是Python实现还是C实现没有必然关系:
- 纯Python实现的类如果定义了
__slots__且没有把__dict__加入__slots__列表,一样不能随便加动态属性 - C实现的扩展类只要在实现时开放了
__dict__的存储空间,一样支持运行时新增属性
原生numpy数组只是出于性能考量在C实现阶段关闭了__dict__支持,才会有加属性的限制,和它本身是C实现的类没有绑定关系。
另外你示例中写的__array_finalize__是numpy约定的子类钩子方法:当你对自定义子类的数组做切片、运算等操作生成新的子类实例时,numpy会自动调用这个方法,把原有实例的info属性复制到新实例上,避免自定义属性在数组操作中意外丢失。
内容的提问来源于stack exchange,提问作者WiseKouichi

