모르고 살았어도 좋았을 내용
Introduction
비상식적인 append
-
아래의 코드를 관찰하자.
현재 a,b의 출력결과는?
-
이제 다시 아래의 코드를 관찰하자.
현재 a,b의 출력결과는?
append의 동작원리: 틀린상상
-
상자로서의 변수: 변수가 데이터를 저장하는 일종의 상자와 같다. <– 아주 흔한 오해 (Fluent Python)
흔히 비유하는 ‘상자로서의 변수’ 개념이 실제로는 객체지향적 언어에서 참조변수를 이해하는 데 방해가 된다.
-
“상자로서의 변수” 관점에서 아래의 코드를 해석하자. (일단 아래의 해석들이 틀린해석이라는 사실을 명심할 것)
a,b라는 변수들은 메모리에 어떻게 저장이 되어있을까?
상상력을 조금 발휘하면 아래와 같이 여길 수 있다.
메모리는 변수를 담을 방이 여러개 있는 호텔이라고 생각하자.
아래를 실행하였을 경우
- 메모리주소1에 존재하는 방을 a라고 하고, 그 방에 [1,2,3]을 넣는다.
- 아래를 실행하였을 경우
- 메모리주소2에 존재하는 방을 b라고 하고, 그 방에 a를 넣어야하는데, a는 [1,2,3]이니까 [1,2,3]을 넣는다.
- 아래를 실행하면
- 방 a로가서 [1,2,3]을 [1,2,3,4]로 바꾼다.
- 그리고 방 b에는 아무것도 하지 않는다.
-
R에서는 맞는 비유인데, 파이썬은 적절하지 않은 비유이다.
틀린이유
실제로는 a,b가 저장된 메모리 주소가 동일함
append의 동작원리: 올바른 상상
파이썬에서의 변수는 자바에서의 참조변수와 같으므로 변수는 객체에 붙은 레이블이라고 생각하는 것이 좋다.
-
파이썬에서는 아래가 더 적절한 비유이다.
메모리는 변수를 담을 방이 여러개 있는 호텔이라고 생각하자.
아래를 실행하였을 경우
- 메모리주소
139753545242336
에서 [1,2,3]을 생성 - 방
139753545242336
의 방문에a
라는 포스트잇을 붙인다. - 앞으로 [1,2,3]에 접근하기 위해서는 여러 메모리방중에서
a
라는 포스트잇이 붙은 방을 찾아가면 된다.
- 아래를 실행하였을 경우
a
라는 포스트잇이 지칭하는 객체를 가져옴. 그리고 그 객체에b
라는 포스트잇을 붙인다.- 쉽게말하면
b
라는 포스트잇을 방139753545242336
의 방문에 붙인다는 이야기. - 앞으로 [1,2,3]에 접근하기 위해서는 여러 메모리방중에서
a
라는 포스트잇이 붙어 있거나b
라는 포스트잇이 붙어있는 방을 찾아가면 된다.
- 아래를 실행하면
a
라는 포스트잇이 붙어있는 방으로 가서, 그 내용물에append
함수를 적용하여 4를 추가하라. 즉 내용물 [1,2,3]을 [1,2,3,4]로 바꾸라.- 같은방(
139753545242336
)에a
,b
라는 포스트잇이 모두 붙어있음. 따라서b
라는 포스트잇이 붙은 방을 찾아가서 내용물을 열어보면 [1,2,3,4]가 나온다.
할당문(=)의 이해
-
파이썬에서 할당문을 이해하기 위해서는 언제나 오른쪽을 먼저 읽어야 한다.
- 할당문의 오른쪽에서는 객체를 “생성”하거나 “가져옴”
- 그 후에 라벨을 붙이듯이 할당문 왼쪽의 변수가 할당문 오른쪽의 객체에 바인딩 된다. (참조)
-
b=a
는
나는 이미 a가 의미하는게 무엇인지 알고있어. 그런데 그 실체를 b라고도 부르고 싶어.
라는 것과 같다. 즉 이미 a
라고 부르고 있는것을 내가 b
라고도 부르고 싶다는 의미인데 이는 마치 별명과 같다. (b
는 a
의 별명, alias) 그리고 이처럼 하나의 오브젝트에 여러개의 이름을 붙이는 것을 에일리어싱이라고 부른다.
id, value
예제1: 같은 value, 다른 id
여기에서 a,b,c는 모두 같은 value를 가진다.
하지만 그 id까지 같은 것은 아니다.
예제2
(관찰)
a= [1, 2, 3, 4]
b= [1, 2, 3]
(해설)
- 포인트: [1,2,3]+[4] 가 실행되는 순간 새로운 오브젝트가 만들어지고 그 오브젝트를 a라는 이름으로 다시 할당되었음. (재할당)
인터닝
예제1
- 당연한결과임.
예제2: 이제 다 이해했다고 생각했는데..
- id(a)와 id(b)가 왜 똑같지..?
(해설) 파이썬의 경우 효율성을 위해서 -5~256까지의 정수를 미리 저장해둠.
- 3은 언제나 7394720에 지박령마냥 밖혀있음
.copy()
의 사용 (shallow copy의 사용)
예제1
(관찰) 아래의 예제를 살펴보자. 참조를 제대로 이해했다면 아래의 예제는 자연스럽게 이해가능할 것임.
l1 = [3, [66,55,44]]
l2 = l1
print('시점1')
print('l1=',l1)
print('l2=',l2)
l1[0]=4
print('시점2')
print('l1=',l1)
print('l2=',l2)
l2.append(5)
print('시점3')
print('l1=',l1)
print('l2=',l2)
시점1
l1= [3, [66, 55, 44]]
l2= [3, [66, 55, 44]]
시점2
l1= [4, [66, 55, 44]]
l2= [4, [66, 55, 44]]
시점3
l1= [4, [66, 55, 44], 5]
l2= [4, [66, 55, 44], 5]
(해설)
이해는 되지만 우리가 원한건 이런게 아니야
예제2: R과 같이 = 를 쓰고 싶다면?
(관찰)
l1 = [3, [66,55,44]]
l2 = l1.copy()
print('시점1')
print('l1=',l1)
print('l2=',l2)
l1[0]=4
print('시점2')
print('l1=',l1)
print('l2=',l2)
l2.append(5)
print('시점3')
print('l1=',l1)
print('l2=',l2)
시점1
l1= [3, [66, 55, 44]]
l2= [3, [66, 55, 44]]
시점2
l1= [4, [66, 55, 44]]
l2= [3, [66, 55, 44]]
시점3
l1= [4, [66, 55, 44]]
l2= [3, [66, 55, 44], 5]
(해설)
예제3: 이제 다 이해했다고 생각했는데..
(관찰)
l1= [3, [66, 55, 44, 33]]
l2= [3, [66, 55, 44, 33]]
(의문)
- l1이랑 l2의 주소도 다르게 나오는데 왜 또 참조한것마냥 l1과 l2가 같이 바뀌고 있지?
Shallow copy의 이해
-
방금 살펴본 예제3을 이해하기 위해서는 shallow copy를 이해해야 한다.
예제1
(관찰+해설)
메모리 상황
- 2222라는 오브젝트가 어떤공간(
139753545300880
)에 생성되고 그 공간에a
라는 라벨이 붙음 - 2222라는 오브젝트가 어떤공간(
139753545301808
)에 생성되고 그 공간에b
라는 라벨이 붙음
즉 -5~256 이외의 2개의 메모리 공간을 추가적으로 사용
예제2
(관찰)
a= [1, 2, 2222, 4]
b= [1, 2, 2222]
(해설)
(139753182327904, [7394656, 7394688, 139753178093776])
(139753173818656, [7394656, 7394688, 139753178095568])
메모리상황
- -5~256까지의 숫자는 미리 메모리에 저장되어 있다. 이중에서 1은
7394656
, 2는7394688
에 저장되어있음. - 2222가 공간
139753178093776
에서 만들어진다. - 어떠한 리스트오브젝트가 공간
139753182327904
에서 만들어지고 원소로 [1,2,2222]를 가진다. 이 공간에a
라는 포스트잇을 붙인다. - 2222가 공간
139753178095568
에서 만들어진다. - 어떠한 리스트오브젝트가 공간
139753173818656
에서 만들어지고 원소로 [1,2,2222]를 가진다. 이 공간에b
라는 포스트잇을 붙인다. a
라는 포스트잇이 붙은 공간으로 이동하여 원소에 4를 추가시킨다.
즉 -5~256이외에 4개의 메모리 공간을 추가사용 (a,b,a의 2222,b의 2222)
예제3
(관찰)
l1= [7777, [66, 55, 44]]
l2= [3, [66, 55, 44]]
(해설)
메모리상황
- -5~256까지의 숫자가 메모리에 저장되어 있다.
- 저장된 숫자중 66,55,44를 묶어서 리스트로 구성하고 이 리스트를 공간
139753183707216
에 저장. - 숫자 3과 공간
139753183707216
에 저장된 리스트 [66,55,44]를 하나로 묶어서 새로운 리스트를 구성하고 이를 공간139753183437040
에 저장. 공간139753183437040
에l1
이라는 포스트잇 생성. - 공간
139753182311120
에l1
의 원소들을 모아서 새로운 리스트를 구성함. 공간139753182311120
에l2
라는 포스트잇 생성.
- l1[0]은 원래 공간
7394720
와 binding 되어 있었음.
- 그런데 7777이라는 새로운 오브젝트가 공간
139753178092080
에 생성되고 l1[0]이 공간139753178092080
와 다시 binding 됨.
예제4
(관찰)
l1= [3, [66, 55, 44], 7777]
l2= [3, [66, 55, 44]]
(해설)
(139753183257056, [7394720, 139753184484240, 139753180268560])
예제3, 예제4를 통하여 리스트가 가변형객체라는 것을 확인할 수 있다. 예제3의 경우 l1이 저장되어있던 메모리공간의 내용물이 [3,[66,55,44]] 에서 [7777,[66,55,44]] 로 바뀌었다. 예제4의 경우 l1이 저장되어있던 메모리공간의 내용물이 [3,[66,55,44]] 에서 [3,[66,55,44],7777] 로 바뀌었다.
예제5: 우리를 힘들게 했던 그 예제.
(관찰)
l1= [3, [66, 55, 44, 7777]]
l2= [3, [66, 55, 44, 7777]]
(해설-시점1)
(해설-시점2)
해설: 사실 시점1에서 메모리 주소상황을 잘 이해했다면 신기한 일이 아니다. .copy()
는 l1과 l2의 주소만 다르게 만들 뿐 내용물인 l1[0]
,l1[1]
는 동일하니까.
예제6: 신임교수=[‘최규빈’,‘이영미’]
-
최규빈, 이영미는 신임교수임
-
신임교수를 누군가는 막내들이라고 부르기도 함.
“막내들”이라는 단어와 “신임교수”라는 단어는 사실 같은 말임
-
새로운 교수 “박혜원”이 뽑혔음.
-
전북대 통계학과에서 R특강팀을 구성하여 방학중 R교육을 실시하고자함. 특강팀은 우선 신임교수들로 구성.
-
R특강팀에 최혜미
교수님 추가. (그렇지만 최혜미교수님이 막내는 아니야.. // 참조와 shallow copy의 차이점)
-
R특강팀에서 양성준 교수를 추가하여 파이썬 특강팀을 구성 (R특강팀의 구분을 위해서 중첩리스트 구조로 만들자)
-
이영미교수는 다른 일이 많아서 R특강 팀에서 제외됨. (그럼 자연히 파이썬에서도 제외됨!!)
하지만 이영미교수는 여전히 신임교수이면서 막내들임
-
새로운 교수로 “손흥민”이 임용됨.
-
그렇다고 해서 손흥민 교수가 바로 R이나 파이썬 특강팀에 자동소속되는건 아님
Deep copy
예제1: Motivation example
-
아래의 상황을 다시 생각해보자.
이슈: 이영미교수가 파이썬특강에서 제외되면서 ADSP특강팀에서도 제외되었음. 그런데 사실 이영미교수가 파이썬특강팀에서만 제외되길 원한 것이지 ADSP특강팀에서 제외되길 원한게 아닐수도 있음.
해결: Deep copy의 사용
예제2
-
deepcopy
level 1
l1: 140346731797872
l2: 140346713502576
- 레벨1:
l1
,l2
의 메모리 주소가 다름을 확인
print('level 2')
print('l1:', id(l1), [id(l1[0]),id(l1[1])])
print('l2:', id(l2), [id(l2[0]),id(l2[1])])
level 2
l1: 140346731797872 [7394720, 140346713544496]
l2: 140346713502576 [7394720, 140346478134928]
- 레벨2:
l1안에 있는 [66,[55,44]]
와l2안에 있는 [66,[55,44]]
의 메모리 주소가 다름도 확인.
print('level 3')
print('l1:', id(l1), [id(l1[0]),[id(l1[1][0]),id(l1[1][1])]])
print('l2:', id(l2), [id(l2[0]),[id(l2[1][0]),id(l2[1][1])]])
level 3
l1: 140346731797872 [7394720, [7396736, 140346713594848]]
l2: 140346713502576 [7394720, [7396736, 140346477770704]]
- 레벨3:
l1안의 [66,[55,44]] 안의 [55,44]
와l2안의 [66,[55,44]] 안의 [55,44]
의 메모리 주소까지도 다름을 확인.
-
비교를 위한 shallow copy
level 1
l1: 140346478137008
l2: 140346477791984
- 레벨1:
l1
,l2
의 메모리 주소가 다름을 확인
print('level 2')
print('l1:', id(l1), [id(l1[0]),id(l1[1])])
print('l2:', id(l2), [id(l2[0]),id(l2[1])])
level 2
l1: 140346713603280 [7394720, 140346713602720]
l2: 140346713602880 [7394720, 140346713602720]
- 레벨2:
l1안에 있는 [66,[55,44]]
와l2안에 있는 [66,[55,44]]
의 메모리 주소는 같음!!
print('level 3')
print('l1:', id(l1), [id(l1[0]),[id(l1[1][0]),id(l1[1][1])]])
print('l2:', id(l2), [id(l2[0]),[id(l2[1][0]),id(l2[1][1])]])
level 3
l1: 140346713603280 [7394720, [7396736, 140346713556624]]
l2: 140346713602880 [7394720, [7396736, 140346713556624]]
- 레벨3:
l1안의 [66,[55,44]] 안의 [55,44]
와l2안의 [66,[55,44]] 안의 [55,44]
의 메모리 주소도 같음!!
-
비교를 위한 참조
level 1
l1: 140346478134288
l2: 140346478134288
- 레벨1:
l1
,l2
여기서부터 메모리 주소가 같다.
print('level 2')
print('l1:', id(l1), [id(l1[0]),id(l1[1])])
print('l2:', id(l2), [id(l2[0]),id(l2[1])])
level 2
l1: 140346478134288 [7394720, 140346713615648]
l2: 140346478134288 [7394720, 140346713615648]
print('level 3')
print('l1:', id(l1), [id(l1[0]),[id(l1[1][0]),id(l1[1][1])]])
print('l2:', id(l2), [id(l2[0]),[id(l2[1][0]),id(l2[1][1])]])
level 3
l1: 140346478134288 [7394720, [7396736, 140346713786480]]
l2: 140346478134288 [7394720, [7396736, 140346713786480]]
문헌에 따라서 shallow copy를 레벨1 deep copy라고 부르기도 한다.
Shallow copy 연습문제
예제1
-
아래의 코드결과를 예측하라. 결과가 나오는 이유를 설명하라.
- 포인트: shallow copy (=level 1 deep copy) 이므로
l1안의 [66,55,44]
와l2안의 [66,55,44]
는 같은 메모리 주소를 가짐
예제2
-
아래의 코드결과를 예측하라. 결과가 나오는 이유를 설명하라.
- 포인트:
l1[-1]+[33]
가 실행되는 순간 새로운 오브젝트가 생성되고 이 새로운 오브젝트가 l1의 마지막 원소에 새롭게 할당된다.
예제3
- 포인트: 이 상황에서
l1안의 [66,55,44]
와l2안의 [66,55,44]
는 서로 다른 메모리 주소를 가진다.
예제4
(잘못된 상상) 아래의 코드와 결과가 같을거야!!
(하지만 현실은)
- 포인트: 예제3을 이해했다면 그냥 이해되는것
예제5
- 포인트:
+=
연산자의 올바른 이해
??? 예제4랑 예제5는 같은코드가 아니었음!!!
a += [1]
는 새로운 오브젝트를 만드는게 아니고, 기존의 오브젝트를 변형하는 스타일의 코드였음! (마치 append 메소드처럼)
불변형 객체
Motivation example
-
우리는 이제 아래의 내용은 마스터함
-
아래의 결과를 한번 예측해볼까?
해설
(시점1)
level 1
l1: 139753183621520
l2: 139753181521472
print('level 2')
print('l1:', id(l1), [id(l1[0]),id(l1[1])])
print('l2:', id(l2), [id(l2[0]),id(l2[1])])
level 2
l1: 139753183621520 [7394720, 139753182280032]
l2: 139753181521472 [7394720, 139753182280032]
(시점2)
level 1
l1: 139753183621520
l2: 139753181521472
print('level 2')
print('l1:', id(l1), [id(l1[0]),id(l1[1])])
print('l2:', id(l2), [id(l2[0]),id(l2[1])])
level 2
l1: 139753183621520 [7394720, 139753182280032]
l2: 139753181521472 [7394720, 139753174874064]
주소 139753182280032
에 있는 값을 바꾸고 싶지만 불변형이라 못바꿈 \(\to\) 그냥 새로 만들자. 그래서 그걸 139753174874064
에 저장하자.
Shallow-copy vs Deep-copy
-
암기용문구: “shallow copy는 껍데기만 복사한다. deep copy는 내부까지 복사한다.”
-
일부교재에서는 경우에 따라 shallow copy가 선호될 수 있다는 식으로 설명되어있으나 솔직히 대부분 코드에서 shallow copy의 동작을 의도하고 코드를 사용하진 않는다. 대부분의 경우에서 shallow copy는 불필요한 side effect을 유발하는 쓸모없는 개념이라 생각한다. (차라리 참조를 쓰면 썼지..)
-
그럼 shallow copy의 장점은 무엇인가? shallow copy가 deep copy보다 메모리를 더 적게 사용한다.
## 예제1
lst1 = ['양성준',['최규빈','이영미','최혜미']]
lst2 = lst1.copy()
## 예제2
lst1 = ['양성준',['최규빈','이영미','최혜미']]
lst2 = copy.deepcopy(lst1)
- 예제1: 4+1+2 = 7개의 공간 사용
- 예제2: 4+2+2 = 8개의 공간 사용
요약
-
파이썬은 메모리를 아끼기 위해서 shallow copy라는 이상한 행동을 한다. (하지만 우리는 shallow copy 를 원하지 않아. 우리는 deep copy 만 쓰고 싶음!)
-
통찰1: 그런데 오묘하게도 [1,2,3,4,5,6]와 같이 1차원 리스트인 경우는 문제가 되지 않음.
- 1차원 리스트의 경우(= 중첩된 리스트가 아닐 경우)는 level 1 수준에서의 deep copy만 있으면 충분하므로 이때는 shallow copy = deep copy 임.
-
통찰2: 생각해보니까 모든 자료형이 불변형인 경우에도 문제가 되지 않음. (R은 모든 자료형이 불변형이다)
-
문제상황요약: [[1,2],[3,4]] 와 같이 리스트에 리스트가 포함된 형태라면 문제가 생긴다. \(\to\) 그런데 우리가 자주 쓰는 매트릭스가 사실 이러한 중첩된 리스트 구조 아니야?
- 해결책1: 깊은복사 (이미했음)
- 해결책2: 넘파이 (이걸로 뭘 어떻게 개선한다는거야?)
numpy
2차원의 실체
-
2차원 array a,b를 선언하자.
-
a,b,c,d 속성비교
((16, 8), (24, 8), (8, 8), (8,))
-
((16, 8), (24, 8), (8, 8), (8,)) 와 같은 저 숫자들이 도데체 무엇을 의미하는거야?!
- 사전지식: 컴퓨터는 하나의 숫자를 저장하는데 메모리를 8칸 쓴다.
- 가정: 만약에 컴퓨터가 1차원으로만 숫자를 저장한다면??
- strides의 의미: (다음 행으로 가기위해서 JUMP해야하는 메모리 공간수, 다음 열로 가기위해서 JUMP해야하는 메모리 공간수)
-
통찰: strides의 존재로 인해서 유추할 수 있는 것은 a,b,c,d 는 모두 1차원으로 저장되어있다는 사실이다. (중첩된 리스트꼴이 아니라)
-
그렇다면.. shallow copy = deep copy?!
-
잠깐 생각좀..
- A2를 바꿨는데 A1이 같이 바뀌는 것은 의도하지 않은 side effect임.
- 이러한 side effect가 생기는 이유는 파이썬이 메모리를 저장하기 위해서 shallow copy라는 희한한 짓을 하기 때문임.
- 이런 side effect을 방지하기 위해서는 deep copy를 써야함. 이 deep copy는 메모리를 더 많이 잡아먹는 단점이 있다.
- 요약하면 side effect 방지와 메모리사용은 trade off 관계에 있음.
- 그런데 생각해보니까 B2역시 B1의 shallow copy 임. 따라서 deep copy보다 메모리를 적게씀. 그런데 side effect도 발생하지 않음!?
-
B1에서 B2를 만드는 과정은 메모리를 적게 쓰지만 side effect과 가은 문제가 없음! (천재인데..?)
-
용어정리: (필요할까..?)
- numpy 한정
.copy()
는 copy모듈의 deepcopy와 동등한 효과를 준다. 하지만 실제로는 shallow copy 이다. 공식문서에는 “Note that np.copy is a shallow copy and will not copy object elements within arrays.” 라고 명시되어 있음. - 일부 블로그에서 deep copy라고 주장하기도 함. 블로그1, 블로그2, 블로그3 // 블로그2의 경우 참조와 shallow copy도 구분못함..
- 이따가 view라는 개념도 나올텐데
.copy()
를 deep copy라고 주장하는 블로거들 대부분.view()
를 shallow copy 혹은 참조라고 주장한다. 하지만 copy와 view를 설명하는 공식문서에서는 view가 shallow copy라는 말을 찾아볼 수 없음. - 사실 좀 애매한게 copy가 shallow copy 와 deep copy 둘만 있는건 아님. 사실
.view()
와.copy()
만 놓고 비교할 때.view()
가.copy()
보다 더 얕은 수준의 복사를 하는것도 사실임 (반대로.copy()
가.view()
보다 더 깊은 수준의 복사를 하는 것도 사실임)
참조
-
a를 선언, b는 a의 참조
-
a의 shape을 바꾸어보자 \(\to\) b도 같이 바뀐다
view
-
a를 선언, b는 a의 view
-
그런데..
-
출생의 비밀
- ? 이거 바뀐 a아니야?
-
View
- b가 a의 뷰라는 의미는, b가 a를 소스로하여 만들어진 오브젝트란 의미이다.
- 따라서 이때 b.base는 a가 된다.
- b는 자체적으로 데이터를 가지고 있지 않으며 a와 공유한다.
note1
원본 ndarray의 일 경우는 .base가 None으로 나온다.
note2
b.base의 shpae과 b의 shape은 아무 관련없다.
-
numpy에서 view를 사용하는 예시 (transpose)
X.T
는X
의 view 이다.
copy
-
a를 선언, b는 a의 copy
-
a의 shape을 바꿔도 b에는 적용되지 않음
-
그리고 a[0]의 값을 바꿔도 b에는 적용되지 않음.
-
b의 출생을 조사해보니..
출생의 비밀은 없었다. 둘다 원본.
-
.view()
는 껍데기만 새로생성 // .copy()
는 껍데기와 데이터를 모두 새로 생성
Appendix: .copy의 한계(?)
(관찰)
a=np.array([1,[1,2]],dtype='O')
b=a.copy()
print('시점1')
print('a=',a)
print('b=',b)
a[0]=222
print('시점2')
print('a=',a)
print('b=',b)
a[1][0]=333
print('시점2')
print('a=',a)
print('b=',b)
시점1
a= [1 list([1, 2])]
b= [1 list([1, 2])]
시점2
a= [222 list([1, 2])]
b= [1 list([1, 2])]
시점2
a= [222 list([333, 2])]
b= [1 list([333, 2])]
- 왜 또 시점2에서는 a와 b가 같이 움직여?
해결책: 더 깊은 복사
a=np.array([1,[1,2]],dtype='O')
b=copy.deepcopy(a)
print('시점1')
print('a=',a)
print('b=',b)
a[0]=222
print('시점2')
print('a=',a)
print('b=',b)
a[1][0]=333
print('시점2')
print('a=',a)
print('b=',b)
시점1
a= [1 list([1, 2])]
b= [1 list([1, 2])]
시점2
a= [222 list([1, 2])]
b= [1 list([1, 2])]
시점2
a= [222 list([333, 2])]
b= [1 list([1, 2])]
-
중간요약
- 사실
b=a.copy()
는 에서.copy()
는 사실 온전한 deep-copy가 아니다. - 그래서
a
의 데이터가 중첩구조를 가지는 경우는 온전한 deep-copy가 수행되지 않는다. - 그런데 일반적으로 넘파이를 이용할때 자주 사용하는 데이터 구조인 행렬, 텐서등은 데이터가 중첩구조를 가지지 않는다. (1차원 array로만 저장되어 있음)
- 따라서 행렬, 텐서에 한정하면
.copy()
는 온전한 deep-copy라고 이해해도 무방하다. <– 이것만 기억해!
요약
아래를 구분할 수 있으면 잘 이해한 것!!
별명, 뷰, 카피
-
test 함수 작성
-
잘 동작하나?
(테스트1)
(테스트2)
(테스트3)
(테스트4)
결론
-
참조, 뷰, 카피의 개념을 잘 알고 있고 때에 따라 이들을 적절하게 사용하며 효율적으로 메모리를 쓰고 싶을것 같음. 하지만 이건 불가능한 소망임.
-
우리가 사용했던 어떠한 것들이 뷰가 나올지 카피가 나올지 잘 모른다. (그래서 원리를 이해해도 대응할 방법이 사실없음)
예시1
예시2
-
심지어 copy인줄 알았던것이 사실 view라서 원치않는 side effect이 생길수 있음. \(\to\) 그냥 방어적 프로그래밍이 최선인듯