Python与Golang的赋值与拷贝
今天用Go写代码时,遇到map的拷贝问题,想到Python中拷贝是引用,但对这里的概念有些模糊。这里做个归纳整理。
数据类型
Python
在python中,系统默认提供六个标准数据类型:
可变数据类型为如下三种
不可变类型为
Number (int、float、bool、complex(复数))
String
Tuple
Golang
在Golang中,Map与Slice也是可变类型
不可变类型包括:
Byte
Rune
String
数字类型
Bool
变量赋值
对变量赋值分为简单赋值、浅拷贝、深拷贝这几种“拷贝”方式。
对于简单类型的数据来说,赋值就是深拷贝。对于复杂类型的数据(对象)来说,有浅拷贝和深拷贝之分。
浅拷贝对于值类型的话是完全拷贝一份,而对于引用类型是拷贝其地址。也就是拷贝的对象修改引用类型的变量同样会影响到源对象。
Python中的变量都是指针 ,因为变量是指针,所以变量没有类型限制,可以指向任意对象。指针的内存空间大小是与类型无关的,其内存空间只是保存了所指向数据的内存地址。所以在Python与Go中有些细微的区别。
Python与Go赋值区别
python中的切片赋值可以生成新的地址空间,是深拷贝,而Go语言中则是引用。
这里对Go与Python中的值类型与切片赋值举例
Go中的情形如下所示:
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 func Change1 () { a := []string {"1" ,"2" ,"3" ,"4" } b := a b[2 ] = "1" fmt.Println("a= " ,a, &a) fmt.Println("b= " ,b, &b) c := []string {"1" ,"2" ,"3" ,"4" } d := c[:] d[2 ] = "1" fmt.Println("c= " ,&c) fmt.Println("d= " ,&d) e := 1 f := e fmt.Println("e= " ,e, &e) fmt.Println("f= " ,f, &f) } 输出: a= [1 2 1 4 ] &[1 2 1 4 ] b= [1 2 1 4 ] &[1 2 1 4 ] c= &[1 2 1 4 ] d= &[1 2 1 4 ] e= 1 0xc00009c470 f= 1 0xc00009c478
**分析:**可以看出,在Go中,可变类型赋值及切片形式赋值并不会生成新的空间。对于不可变类型,Go会直接生成新的空间。
Python中的情形如下所示:
1 2 3 4 5 a = [1 ,2 ,3 ,4 ] b = a b[2 ] = 1 print("a = " ,a,id(a)) print("b = " ,b,id(b))
1 2 a = [1, 2, 1, 4] 140183767803616 b = [1, 2, 1, 4] 140183767803616
1 2 3 4 5 a = [1 ,2 ,3 ,4 ] b = a[:] b[2 ] = 1 print("a = " ,a,id(a)) print("b = " ,b,id(b))
1 2 a = [1, 2, 3, 4] 140183767804176 b = [1, 2, 1, 4] 140183761986576
1 2 3 4 5 a = 1 b = a b = 2 print("a = " ,a,id(a)) print("b = " ,b,id(b))
1 2 a = 1 94734743720704 b = 2 94734743720736
可以看出,如果要生成一份新的空间时,在Python中可以对列表使用切片来进行,Go中不可行。
这里需要注意的是,切片拷贝无法拷贝嵌套结构 ,示例如下:
1 2 3 4 5 6 a = [1 ,2 ,3 ,[4 ,5 ,6 ]] b = a[:] b[2 ] = 0 b[3 ][0 ] = 1 print("a = " ,a,id(a),id(a[3 ]),id(a[2 ])) print("b = " ,b,id(b),id(b[3 ]),id(b[2 ]))
1 2 a = [1, 2, 3, [1, 5, 6]] 140183759486800 140183767686672 94734743720768 b = [1, 2, 0, [1, 5, 6]] 140183759484240 140183767686672 94734743720672
可以看出,嵌套结构a[3]并没有被重新赋予新的空间。若想赋予新的空间,可以使用 copy 标准库内的 deepcopy 方法,来达到嵌套深拷贝的目的 。
1 2 3 4 5 6 7 import copya = [1 ,2 ,3 ,[4 ,5 ,6 ]] b = copy.deepcopy(a) b[2 ] = 0 b[3 ][0 ] = 1 print("a = " ,a,id(a),id(a[3 ]),id(a[2 ])) print("b = " ,b,id(b),id(b[3 ]),id(b[2 ]))
1 2 a = [1, 2, 3, [4, 5, 6]] 140183767648208 140183769006480 94734743720768 b = [1, 2, 0, [1, 5, 6]] 140183767646768 140183762524512 94734743720672
另外,在python中如果只是变量赋值的话,不可变类型也不会生成新的空间,这与Go中不同 ,如下所示:
1 2 3 4 a = (1 ,2 ,3 ,4 ) b = a print("输出结果a = " ,a,id(a)) print("输出结果b = " ,b,id(b))
1 2 输出结果a = (1, 2, 3, 4) 140183762321488 输出结果b = (1, 2, 3, 4) 140183762321488
1 2 3 4 5 a = (1 ,2 ,3 ,4 ) b = a b = (1 ,2 ) print("输出结果a = " ,a,id(a)) print("输出结果b = " ,b,id(b))
1 2 输出结果a = (1, 2, 3, 4) 140183766821008 输出结果b = (1, 2) 140183769335600
对可变类型Map的拷贝,在Python和Go中是相同的,都是引用
Go中的拷贝举例
下面在Go中使用一个复杂的例子来弄清楚上述情形,及深拷贝。Python中的深拷贝可以使用copy包,在Go中可以使用反射。代码如下:
我们使用句子信息来构建一个大的结构体,其中包含一些可变与不可变类型。
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 package mainimport ( "bytes" "encoding/gob" "fmt" ) type WordInfo struct { Word string Tag string Strokes []int } type Cut struct { Count int WordCut []WordInfo } type Sentence struct { Text string IsNegative bool Cut Cut Idf map [string ]float64 }
使用两种方式进行直接赋值
方式1:
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 func SentenceTest1 () { St1 := Sentence{ Text: "去北京玩" , IsNegative: false , Cut: Cut{ Count: 3 , WordCut: []WordInfo{ {"去" , "v" , []int {5 }}, {"北京" , "n" , []int {5 , 8 }}, {"玩" , "v" , []int {8 }}}}, Idf: map [string ]float64 {"去" : 1.0 , "玩" : 2.0 }, } St2 := St1 fmt.Printf("Before St1:%v\n" , St1) fmt.Printf("Before St2:%v\n" , St2) St2.Text = "不去上海玩" St2.IsNegative = true St2.Cut.Count = 4 St2.Cut.WordCut[0 ] = WordInfo{"不" , "v" , []int {4 }} St2.Cut.WordCut[1 ] = WordInfo{"去" , "v" , []int {5 }} St2.Cut.WordCut[2 ] = WordInfo{"上海" , "n" , []int {3 ,10 }} St2.Cut.WordCut = append (St2.Cut.WordCut,WordInfo{"玩" , "v" , []int {8 }}) St2.Idf["去" ] = 1.1 St2.Idf["玩" ] = 2.2 fmt.Printf("After St1:%v\n" , St1) fmt.Printf("After St2:%v\n" , St2) }
1 2 3 4 Before St1:{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Before St2:{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} After St1:{去北京玩 false {3 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]}]} map [去:1.1 玩:2.2 ]} After St2:{不去上海玩 true {4 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]} {玩 v [8 ]}]} map [去:1.1 玩:2.2 ]}
方式2:
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 func SentenceTest2 () { St1 := Sentence{ Text: "去北京玩" , IsNegative: false , Cut: Cut{ Count: 3 , WordCut: []WordInfo{ {"去" , "v" , []int {5 }}, {"北京" , "n" , []int {5 , 8 }}, {"玩" , "v" , []int {8 }}}}, Idf: map [string ]float64 {"去" : 1.0 , "玩" : 2.0 }, } St2 := St1 fmt.Printf("Before St1:%v\n" , St1) fmt.Printf("Before St2:%v\n" , St2) St2.Text = "不去上海玩" St2.IsNegative = true St2.Cut.Count = 4 St2.Cut.WordCut = []WordInfo{ {"不" , "v" , []int {4 }}, {"去" , "v" , []int {5 }}, {"上海" , "n" , []int {3 ,10 }}, {"玩" , "v" , []int {8 }}} St2.Idf = map [string ]float64 {"去" : 1.1 , "玩" : 2.2 } fmt.Printf("After St1:%v\n" , St1) fmt.Printf("After St2:%v\n" , St2) }
1 2 3 4 Before St1:{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Before St2:{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} After St1:{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} After St2:{不去上海玩 true {4 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]} {玩 v [8 ]}]} map [去:1.1 玩:2.2 ]}
分析:
可以看到只有Slice 和 Map会受到影响,我们这里重新生成一个Slice和Map就可以不受影响。
通过函数赋值(测试Go中的参数传递)
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 func SentenceTest1 () { St1 := Sentence{ Text: "去北京玩" , IsNegative: false , Cut: Cut{ Count: 3 , WordCut: []WordInfo{ {"去" , "v" , []int {5 }}, {"北京" , "n" , []int {5 , 8 }}, {"玩" , "v" , []int {8 }}}}, Idf: map [string ]float64 {"去" : 1.0 , "玩" : 2.0 }, } St2 := St1 fmt.Printf("Before St:%v\n" , St1) Modify(St2) fmt.Printf("After St1:%v\n" , St1) fmt.Printf("After St2:%v\n" , St2) } func Modify (query Sentence) { fmt.Printf("Modify Before St1:%v\n" , query) query.Text = "不去上海玩" query.IsNegative = true query.Cut.Count = 4 query.Cut.WordCut[0 ] = WordInfo{"不" , "v" , []int {4 }} query.Cut.WordCut[1 ] = WordInfo{"去" , "v" , []int {5 }} query.Cut.WordCut[2 ] = WordInfo{"上海" , "n" , []int {3 ,10 }} query.Cut.WordCut = append (query.Cut.WordCut,WordInfo{"玩" , "v" , []int {8 }}) query.Idf["去" ] = 1.1 query.Idf["玩" ] = 2.2 fmt.Printf("Modify After St1:%v\n" , query) }
输出结果如下所示:
1 2 3 4 5 6 7 Before St:{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Modify Before St1:{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Modify After St1:{不去上海玩 true {4 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]} {玩 v [8 ]}]} map [去:1.1 玩:2.2 ]} After St1:{去北京玩 false {3 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]}]} map [去:1.1 玩:2.2 ]} After St2:{不去上海玩 true {4 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]} {玩 v [8 ]}]} map [去:1.1 玩:2.2 ]}
分析结果:
可以看到,在函数中St1的值已经随着St2的值改变了,但函数外确没变。因为在Golang方法中传递的参数被拷贝了一份,修改的Slice 和 Map都是同一份地址,但是值类型是重新赋值的(这里IsNegative,count,Text在St1中都未改变)。
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 func deepCopy (dst, src interface {}) error { var buf bytes.Buffer if err := gob.NewEncoder(&buf).Encode(src); err != nil { return err } return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst) } func SentenceTest2 () { St1 := &Sentence{ Text: "去北京玩" , IsNegative: false , Cut: Cut{ Count: 3 , WordCut: []WordInfo{ {"去" , "v" , []int {5 }}, {"北京" , "n" , []int {5 , 8 }}, {"玩" , "v" , []int {8 }}}}, Idf: map [string ]float64 {"去" : 1.0 , "玩" : 2.0 }, } St2 := new (Sentence) if err := deepCopy(St2, St1); err != nil { panic (err.Error()) } fmt.Printf("SentenceInfo St1:%v, St2:%v\n" , St1, St2) Modify(*St2) fmt.Printf("SentenceInfo St1:%v, St2:%v\n" , St1, St2) }
结果如下:
1 2 3 4 5 6 Before St1:&{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Before St2:&{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Modify Before St1:{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Modify After St1:{不去上海玩 true {4 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]} {玩 v [8 ]}]} map [去:1.1 玩:2.2 ]} After St1:&{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} After St2:&{去北京玩 false {3 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]}]} map [去:1.1 玩:2.2 ]}
分析:
可以看到St2经过参数传递(浅拷贝)在修改Slice和Map受到影响的也只有St2和St2的浅拷贝对象。对于St1没有任何影响。
若想改变St2的值可以通过指针实现。如下所示:
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 func ModifyPoint (query *Sentence) { fmt.Printf("Modify Before St2:%v\n" , query) query.Text = "不去上海玩" query.IsNegative = true query.Cut.Count = 4 query.Cut.WordCut = []WordInfo{ {"不" , "v" , []int {4 }}, {"去" , "v" , []int {5 }}, {"上海" , "n" , []int {3 ,10 }}, {"玩" , "v" , []int {8 }}} query.Idf = map [string ]float64 {"去" : 1.1 , "玩" : 2.2 } fmt.Printf("Modify After St2:%v\n" , query) } func SentenceTest3 () { St1 := &Sentence{ Text: "去北京玩" , IsNegative: false , Cut: Cut{ Count: 3 , WordCut: []WordInfo{ {"去" , "v" , []int {5 }}, {"北京" , "n" , []int {5 , 8 }}, {"玩" , "v" , []int {8 }}}}, Idf: map [string ]float64 {"去" : 1.0 , "玩" : 2.0 }, } St2 := new (Sentence) if err := deepCopy(St2, St1); err != nil { panic (err.Error()) } fmt.Printf("Before St1:%v\n" , St1) fmt.Printf("Before St2:%v\n" , St2) ModifyPoint(&*St2) fmt.Printf("After St1:%v\n" , St1) fmt.Printf("After St2:%v\n" , St2) }
1 2 3 4 5 6 Before St1:&{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Before St2:&{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Modify Before St2:&{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} Modify After St2:&{不去上海玩 true {4 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]} {玩 v [8 ]}]} map [去:1.1 玩:2.2 ]} After St1:&{去北京玩 false {3 [{去 v [5 ]} {北京 n [5 8 ]} {玩 v [8 ]}]} map [去:1 玩:2 ]} After St2:&{不去上海玩 true {4 [{不 v [4 ]} {去 v [5 ]} {上海 n [3 10 ]} {玩 v [8 ]}]} map [去:1.1 玩:2.2 ]}
St2的改变对St1不造成影响
总结
关于Go与Python中的变量赋值应该视情况使用不同的方式,通过函数进行赋值时需要特别注意
参考:
https://blog.csdn.net/weixin_40165163/article/details/90680466