Мелкая копия вместо нового объекта в Python - PullRequest
0 голосов
/ 20 мая 2018

Я пытаюсь создать новый объект класса внутри этого же класса, однако вместо создания нового объекта он просто создает новую ссылку на тот же объект, в котором я сейчас работаю.

Таким образом, если я изменяю значение одного объекта, он также меняет значение и в другом - хотя у меня, вероятно, должно быть 2 совершенно разных объекта.

Использование метода copy.deepcopy() устраняет проблему со ссылкой, но я думаю, что он должен работать по-другому.

Как мой код ведет себя в этой конкретной реализации?Есть ли причина для этого создавать поверхностную копию того же самого объекта, даже если код, вероятно, должен создать его новый экземпляр?

Это слегка уменьшенный фрагмент кода:

class Vec4():
    def __init__(self, x = 0, y = 0, z = 0, w = 0):
        self.values = [x,y,z,w]

    def __str__(self):
        return str(self.values[0]) + ' ' + str(self.values[1]) + ' ' + str(self.values[2]) + ' ' + str(self.values[3])

    def setValue(self, index, value):
        self.values[index] = value


    def scalar(self, vector):
        """returns the result of the scalar multiplication"""
        result = 0
        for u in range(4):
            result += self.values[u] * vector.values[u]
        return result




class Matrix4():
    def __init__(self, row1 = Vec4(), row2 = Vec4(), row3 = Vec4(), row4 = Vec4()):
        self.m_values = [row1,row2,row3,row4]
        self.trans_values = [Vec4(),Vec4(),Vec4(),Vec4()]
        self.set_transp_matrix()

    def __str__(self):
        return self.m_values[0].__str__() + '\n' + self.m_values[1].__str__() + '\n' + self.m_values[2].__str__() + '\n' + self.m_values[3].__str__()

    def setIdentity(self):

        identity = Matrix4(Vec4(1,0,0,0),
                       Vec4(0,1,0,0),
                       Vec4(0,0,1,0),
                       Vec4(0,0,0,1))
        for i in range(4):
            for j in range(4):
                self.m_values[i].values[j] = identity.m_values[i].values[j]

    def set_transp_matrix(self):
         for t in range(4):
            for s in range(4):
                self.trans_values[t].values[s] = self.m_values[s].values[t]

    def get_trans_matrix(self):
        return self.trans_values[0].__str__() + '\n' + self.trans_values[1].__str__() + '\n' + self.trans_values[2].__str__() + '\n' + self.trans_values[3].__str__()

    def mulM(self, m):

        print(self, "\n")
        matrixResult = Matrix4()
        print(matrixResult, "\n")
        for row in range(4):  # rows of self
            for element in range(4):
                value = self.m_values[row].scalar(m.trans_values[element])
                matrixResult.m_values[row].setValue(element, value)
        return matrixResult


class ScaleMatrix(Matrix4):

    def __init__(self, m_scale = Vec4(1,1,1), *args, **kwargs):
        super(ScaleMatrix, self).__init__(*args, **kwargs)
        self.m_scale = m_scale
        self.update()

    def getScale(self):
        """Returns the scale vector, only x, y and z are relevant"""
        return self.m_scale

    def setScale(self, v):
        """Sets the scale vector, only x, y and z are relevant"""
        self.m_scale = v
        self.update()

    def update(self):
        """Calculates the scale matrix"""

        self.setIdentity()

        for i in range(3):
            self.m_values[i].values[i] = self.getScale().values[i]

        return self


if __name__ == "__main__":
    #Simple Constructor and Print
    a = Vec4(1,2,3,4)
    b = Vec4(5,6,7,8)
    c = Vec4(9,10,11,12)
    d = Vec4(13,14,15,16)


    A = Matrix4(a, b, c, d)
    D = ScaleMatrix()
    D.setScale(Vec4(3, 4, 5, 1))

    print(D.mulM(A))

Проблема в классе Matrix4, метод mulM(), где matrixResult = Matrix4() должен создать новый экземпляр Matrix4() (где все значения Vec4() должны быть 0) вместо простого копирования self объект.Вывод print показывает следующее:

3 0 0 0
0 4 0 0
0 0 5 0
0 0 0 1 

3 0 0 0
0 4 0 0
0 0 5 0
0 0 0 1 

3 6 51 672
20 64 508 6688
45 140 1170 15340
13 40 334 4396

Таким образом, вторая матрица не должна быть равна первой.Однако проблема не возникает, если я создаю обычный объект Matrix4() вместо объекта ScaleMatrix(), который расширяет Matrix4() в самом конце фрагмента кода выше.

Python v.3.6.4

Ответы [ 2 ]

0 голосов
/ 20 мая 2018

Извлечение Конструктор Python и значение по умолчанию и "Наименьшее изумление" и изменяемый аргумент по умолчанию Ваша проблема в основном аналогична проблеме, описанной в вышеупомянутых связанных проблемах.

Кроме того, при объявлении класса сделайте его подклассом object:

class Vec4(object):
class Matrix4(object):

Наконец, для решения вашей конкретной проблемы удалите значения по умолчанию для инициализатора.Например, сделайте что-то вроде этого:

class Matrix4():
    def __init__(self, row1=None, row2=None, row3=None, row4=None):
       if row1 is None:
           row1 = Vec4()
       if row2 is None:
           row2 = Vec4()
       if row3 is None:
           row3 = Vec4()
       if row4 is None:
           row4 = Vec4()
       self.m_values = [row1,row2,row3,row4]

То же самое для значения по умолчанию ScaleMatrix аргумент m_scale:

class ScaleMatrix(Matrix4):
    def __init__(self, m_scale = Vec4(1,1,1), *args, **kwargs):
0 голосов
/ 20 мая 2018

Python оценивает параметры по умолчанию, когда функция определена.Когда вы определяете:

class Matrix4():
    def __init__(self, row1 = Vec4() ...

, вы создаете экземпляр Vec4, и этот экземпляр теперь будет использоваться в качестве значения по умолчанию каждый раз, когда вызывается этот метод __init__.

В первый развы создаете Matrix4 экземпляр, __init__ будет выполняться, и на этот экземпляр будет ссылаться имя row1

Тогда у вас есть:

    self.m_values = [row1,row2,row3,row4]

Итак, этот экземпляр теперьна которое ссылается self.m_values ​​[0].

Позже, в ScaleMatrix.update, вы обновите этот Vec4 экземпляр:

for i in range(3):
            self.m_values[i].values[i] = self.getScale().values[i]

В следующий раз, когда вы будете звонить Matrix4.__init__ безпараметров, будет использоваться значение по умолчанию, а это Vec4, который вы только что обновили.

У вас аналогичное поведение, когда вы используете пустой список в качестве параметра по умолчанию, см. «Наименьшее изумление»и изменяемый аргумент по умолчанию .

Обычный способ избежать этой проблемы - избегать использования изменяемых объектов в качестве параметров по умолчанию.Вы можете сделать:

class Matrix4():
    def __init__(self, row1 = None, row2 = None, row3 = None, row4 = None):
        if row1 is None:
            row1 = Vec4()
        if row2 is None:
            row2 = Vec4()
        if row3 is None:
            row3 = Vec4()
        if row4 is None:
            row4 = Vec4()
        self.m_values = [row1,row2,row3,row4]
        self.trans_values = [Vec4(),Vec4(),Vec4(),Vec4()]
        self.set_transp_matrix()

, что дает в качестве вывода:

3 0 0 0
0 4 0 0
0 0 5 0
0 0 0 1 

0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0 

3 6 9 12
20 24 28 32
45 50 55 60
13 14 15 16
...