含__getattr__方法的Pixel类存入np.array的问题及警告处理
问题概述
用户在学习类构建时编写了一个用于图像处理的Pixel类,并用二维NumPy数组存储由Pixel实例组成的完整图像。添加__getattr__方法后代码无法运行,原因是NumPy需要访问__array_struct__属性。在__getattr__中添加返回object.__array_struct__的逻辑后,代码可以运行,但触发了以下DeprecationWarning:
DeprecationWarning: An exception was ignored while fetching the attribute
__array__from an object of type 'Pixel'. With the exception ofAttributeErrorNumPy will always raise this exception in the future. Raise this deprecation warning to see the original exception. (Warning added NumPy 1.21)
触发警告的代码为:
I = np.array([Pixel()], dtype=Pixel)
用户提供的Pixel类核心代码如下:
class Pixel: def __init__(self,*args): # 确定RGB值的逻辑 self.R,self.G,self.B = RGB # R,G,B是0到255之间的浮点数 ... def __getattr__(self,name): if name == '__array_struct__': return object.__array_struct__ if name[0] in 'iI': inted = True name = name[1:] else: inted = False if len(name)==1: n = name[0] if n in 'rgba': value = min(1,self.__getattribute__(n.upper())/255) elif n in 'RGBA': value = min(255,self.__getattribute__(n)) assert 0<=value else: h,s,v = rgb_hsv(self.rgb) if n in 'h': value = h elif n == 's': value = s elif n == 'v': value = v elif n == 'S': value = s*100 elif n == 'V': value = v*100 elif n == 'H': value = int(h) if inted: return int(value) else: return value else: value = [] for n in name: try: v = self.__getattribute__(n) except AttributeError: v = self.__getattr__(n) if inted: value.append(int(v)) else: value.append(v) return value
问题根源
__getattr__拦截特殊属性:__getattr__会处理所有未被正常属性查找机制找到的属性请求,包括NumPy需要的__array__属性。现有逻辑未针对__array__做处理,导致解析该属性时抛出非AttributeError的异常,被NumPy捕获后触发警告。- 错误返回
object.__array_struct__:object类本身不存在__array_struct__属性,直接返回它会导致潜在错误,不符合NumPy对自定义对象的要求。 - 递归调用风险:多字符属性处理中手动调用
self.__getattr__(n),可能引发无限递归或未预期异常。
解决方案
1. 优先处理NumPy特殊属性,抛出正确的AttributeError
修改__getattr__方法,在开头对NumPy相关的特殊属性直接抛出AttributeError,让NumPy按默认逻辑处理:
def __getattr__(self, name): # 对NumPy需要的特殊属性直接抛出AttributeError,避免进入自定义逻辑 if name in ('__array__', '__array_struct__', '__array_interface__'): raise AttributeError(f"'Pixel' object has no attribute '{name}'") # 原有属性处理逻辑 if name[0] in 'iI': inted = True name = name[1:] else: inted = False # ... 剩余原有代码保持不变
2. 可选:实现__array__方法支持数组转换
如果希望Pixel实例可以直接转换为NumPy数组(比如返回RGB值数组),可以显式实现__array__方法,彻底消除警告:
def __array__(self, dtype=None): # 返回包含RGB值的NumPy数组,可指定数据类型 return np.array([self.R, self.G, self.B], dtype=dtype)
3. 修复递归调用问题
将多字符属性处理中的手动递归调用改为使用getattr,利用Python的属性查找机制自动处理,避免无限递归:
# 替换原有的try-except块 value = [] for n in name: # 使用getattr自动触发属性查找,包括调用__getattr__ v = getattr(self, n) if inted: value.append(int(v)) else: value.append(v) return value
验证效果
修改完成后,运行np.array([Pixel()], dtype=Pixel),DeprecationWarning会消失,同时Pixel类的原有属性访问逻辑(如pixel.r、pixel.H、pixel.rgb等)依然正常工作。
内容的提问来源于stack exchange,提问作者Gnathon

