np.array, как на значения по умолчанию атрибутов в Python ссылаются несколько экземпляров?

У меня есть класс с некоторым полем атрибута, который является массивом NumPy. Класс обладает способностью порождать новые экземпляры того же класса. Теперь моя проблема в том, что все созданные таким образом экземпляры, похоже, указывают на один и тот же атрибут поля. Поэтому, когда ожидаемый результат данного кода:

[0, 0]

[1, 0]

[0, 2]

это на самом деле дает:

[1, 2]

[1, 2]

[1, 2]

и когда я изменяю запись в a.field впоследствии, запись изменяется в атрибуте поля всех экземпляров

Кто-нибудь получил представление о том, как обойти это или что я ошибся в понимании здесь?

import numpy as np

class A:
    def __init__(self, field=np.zeros(2)):
        self.field=field

    def setField(self, c, index):
        if index<len(self.field):
            self.field[index]=c


    def multiply(self):

        new_A=np.empty(2, dtype=object)

        for n in range(0, 2, 1):
            new_A[n]=A()
            new_A[n].setField(n+1, n)

        return new_A



a=A()

subs=a.multiply()

print a.field
print subs[0].field
print subs[1].field

2 ответа

Это распространенная "ошибка", с которой все сталкиваются при изучении Python. Это не имеет ничего общего с NumPy в частности. Например, обычный список даст вам такое же поведение.

>>> def f(x=[]):
...     x.append(1)
...     return x
... 
>>> f()
[1]
>>> f()
[1, 1]
>>> f()
[1, 1, 1]

Грубо говоря, этот код интерпретируется примерно так:

f_default_arg_x = []
def f(x=f_default_arg_x):
    x.append(1)
    return x

Как только вы узнаете, что эта проблема существует, вы можете найти множество страниц, объясняющих проблему и рассказывающих, что с этим делать. (Например: http://effbot.org/zone/default-values.htm)

Обычное решение - просто использовать None в качестве заполнителя для "укажите значение по умолчанию".

def f(x=None):
    if x is None:
        x = []
    x.append(1)
    return x

Вы не должны помещать массив в качестве аргумента по умолчанию, вместо этого вы должны определить его следующим образом:

class A:
    def __init__(self, field=None):
        if field is None:
            field = np.zeros(2)

        self.field=field

    def setField(self, c, index):
        if index<len(self.field):
            self.field[index]=c


    def multiply(self):

        new_A=np.empty(2, dtype=object)

        for n in range(0, 2, 1):
            new_A[n]=A()
            new_A[n].setField(n+1, n)

        return new_A

Проверьте "Наименьшее удивление" в Python: изменяемый аргумент по умолчанию для подробного объяснения того, почему вы не должны использовать список или массив в качестве аргумента по умолчанию.

Другие вопросы по тегам