关于oop:浅拷贝而不是Python中的新对象

Shallow copy instead of new object in Python

我试图在同一个类中创建一个类的新对象,但是它不是创建一个全新的对象,而是创建一个对我当前工作的同一个对象的新引用。

所以,如果我改变一个对象的值,它也会改变另一个对象的值——即使我可能有两个完全不同的对象。

使用copy.deepcopy()方法可以修复引用中的问题,但我想它也应该以不同的方式工作。

我的代码在这个特定的实现中是如何工作的?即使代码可能应该创建同一对象的新实例,它是否有理由创建该对象的浅副本?

这是一个稍微简化的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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__() + '
'
+ self.m_values[1].__str__() + '
'
+ self.m_values[2].__str__() + '
'
+ 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__() + '
'
+ self.trans_values[1].__str__() + '
'
+ self.trans_values[2].__str__() + '
'
+ self.trans_values[3].__str__()

    def mulM(self, m):

        print(self,"
"
)
        matrixResult = Matrix4()
        print(matrixResult,"
"
)
        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的输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.4.4


定义函数时,python计算默认参数。定义时:

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

您创建一个Vec4实例,现在每次调用该__init__方法时,该实例都将用作默认值。

第一次创建Matrix4实例时,将执行__init__实例,此实例的引用名称为row1

然后你有:

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

所以这个实例现在由self.m_值[0]引用。

稍后,在ScaleMatrix.update中,您将更新此Vec4实例:

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

下次不带参数调用Matrix4.__init__时,将使用默认值,这是刚才更新的Vec4

当使用空列表作为默认参数时,您有类似的行为,请参见"最小惊讶"和可变默认参数。.

避免这个问题的通常方法是避免使用可变对象作为默认参数。你可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
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()

其输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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


查看python构造函数和默认值以及"最小惊异"以及您的问题与上述链接问题中描述的问题基本相似的可变默认参数。

另外,在声明类时,使其成为object的子类:

1
2
class Vec4(object):
class Matrix4(object):

最后,要解决特定问题,请删除初始值设定项的默认值。例如,执行如下操作:

1
2
3
4
5
6
7
8
9
10
11
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的违约:

1
2
class ScaleMatrix(Matrix4):
    def __init__(self, m_scale = Vec4(1,1,1), *args, **kwargs):