numpy数组子类意外共享实例属性

10 投票
1 回答
2932 浏览
提问于 2025-04-16 15:18

我遇到了一个奇怪的关于子类 numpy.ndarray 的问题,感觉有点像
父类的实例变量在子类的实例之间持续存在
但我还没能完全理解或者让它在我的例子中正常工作。

我在阅读 稍微更现实的例子 - 向现有数组添加属性时,想要做的几乎就是这个。我想给数组添加一个 attrs 属性,用来存储像单位这样的信息,格式是字典。

这是我现在的代码:

import numpy
class dmarray(numpy.ndarray):
    def __new__(cls, input_array, attrs={}):
        obj = numpy.asarray(input_array).view(cls)
        obj.attrs = attrs
        return obj

    def __array_finalize__(self, obj):
        # see InfoArray.__array_finalize__ for comments
        if obj is None:
            return
        self.attrs = getattr(obj, 'attrs', {})

接下来我想用它来演示这个问题

a = dmarray([1,2,3,4])
b = dmarray([1,2,3,4])
a.attrs['foo'] = 'bar'
print(b.attrs)
#{'foo': 'bar'}
b.attrs is a.attrs
# True  # hmm....

所以 b 这个变量获取了我不想要的 attrs。令人烦恼的是,如果你这样做,它就能正常工作:

from datamodel import *
a = dmarray([1,2,3,4], attrs={'foo':'bar'})
b = dmarray([1,2,3,4])
b.attrs
# {}

那么,我到底该如何让这个 dmarray 按照我想要的方式工作呢?


编辑:
好的,这似乎解决了问题,但我不明白为什么。所以让我把问题改成:这到底在做什么,为什么它有效?

class dmarray(numpy.ndarray):
    def __new__(cls, input_array, attrs=None):
        obj = numpy.asarray(input_array).view(cls)
        return obj

    def __init__(self, input_array, attrs=None):
        if attrs == None:
            attrs = {}
        self.attrs = attrs

通过把 kwarg 从 __new__() 移到 __init__(),它就能正常工作。我只是试了一下,想看看“也许这样可以”。

a = dmarray([1,2,3,4])
b = dmarray([1,2,3,4])
a.attrs['foo'] = 'bar'
b.attrs
# {}

1 个回答

18

问题出在这里:

def __new__(cls, input_array, attrs={})

在函数的开头不要这样写 attrs={}。你可能会得到一个和你想的不一样的结果。这是一个常见的Python陷阱。可以参考这里 Python中的默认参数值

正确的做法是:

def __new__(cls, input_array, attrs=None):
    if attrs is None:
        attrs = {}

撰写回答