=[1,2,3]
a=a
b=a+[4] a
copy
Introduction
비상식적인 append
-
아래의 코드를 관찰하자.
현재 a,b의 출력결과는?
a,b
([1, 2, 3, 4], [1, 2, 3])
-
이제 다시 아래의 코드를 관찰하자.
=[1,2,3]
a=a
b4) a.append(
a,b
([1, 2, 3, 4], [1, 2, 3, 4])
append의 동작원리: 틀린상상
-
아래의 코드를 다시 살펴보자.
=[1,2,3]
a=a
b4) a.append(
a,b라는 변수들은 메모리에 어떻게 저장이 되어있을까?
상상력을 조금 발휘하면 아래와 같이 여길 수 있다.
메모리는 변수를 담을 방이 여러개 있는 호텔이라고 생각하자.
아래를 실행하였을 경우
=[1,2,3] a
- 메모리주소1에 존재하는 방을 a라고 하고, 그 방에 [1,2,3]을 넣는다.
- 아래를 실행하였을 경우
=a b
- 메모리주소2에 존재하는 방을 b라고 하고, 그 방에 a를 넣어야하는데, a는 [1,2,3]이니까 [1,2,3]을 넣는다.
- 아래를 실행하면
4) a.append(
방 a로가서 [1,2,3]을 [1,2,3,4]로 바꾼다.
그리고 방 b에는 아무것도 하지 않는다.
-
R에서는 맞는 비유인데, 파이썬은 적절하지 않은 비유이다.
틀린이유
id(a)
139660748276272
id(b)
139660748276272
실제로는 a,b가 저장된 메모리 주소가 동일함
append의 동작원리: 올바른 상상
-
파이썬에서는 아래가 더 적절한 비유이다.
메모리는 변수를 담을 방이 여러개 있는 호텔이라고 생각하자.
아래를 실행하였을 경우
=[1,2,3] a
- 메모리주소
139753545242336
에서 [1,2,3]을 생성 - 방
139753545242336
의 방문에 a라는 포스트잇을 붙인다. - 앞으로 [1,2,3]에 접근하기 위해서는 여러 메모리방중에서 a라는 포스트잇이 붙은 방을 찾아가면 된다.
- 아래를 실행하였을 경우
=a b
- a라는 포스트잇이 있는데,
a
라는 포스트잇이랑 b라는 포스트잇과 같은 효과를 주도록 한다. - 쉽게말하면 b라는 포스트잇을 방
139753545242336
의 방문에 붙인다는 이야기. - 앞으로 [1,2,3]에 접근하기 위해서는 여러 메모리방중에서 a라는 포스트잇이 붙어 있거나 b라는 포스트잇이 붙어있는 방을 찾아가면 된다.
- 아래를 실행하면
4) a.append(
a
라는 포스트잇이 붙어있는 방으로 가서, 그 내용물에 append함수를 적용하여 4를 추가하라. 즉 내용물 [1,2,3]을 [1,2,3,4]로 바꾸라.- 같은방(
139753545242336
)에 a,b라는 포스트잇이 모두 붙어있음. 따라서 b라는 포스트잇이 붙은 방을 찾아가서 내용물을 열어보면 [1,2,3,4]가 나온다.
할당문(=)의 이해
-
파이썬에서 할당문을 이해하기 위해서는 언제나 오른쪽을 먼저 읽어야 한다.
-
할당문의 오른쪽에서는 객체를 생성하거나 가져옴 - 그 후에 라벨을 붙이듯이 할당문 왼쪽의 변수가 할당문 오른쪽의 객체에 바인딩 된다. (참조)
-
b=a
는
나는 이미 a가 의미하는게 무엇인지 알고있어. 그런데 그 실체를 b라고도 부르고 싶어. \(\to\) 참조
라는 것과 같다. 즉 이미 a라고 부르고 있는것을 내가 b라고도 부르고 싶다는 의미인데 이는 마치 별명과 같다. (b는 a의 별명, alias) 그리고 이처럼 하나의 오브젝트에 여러개의 이름을 붙이는 것을 에일리어싱이라고 부른다.
id, value
예제1
=[1,2,3]
a=a
b4)
a.append(=[1,2,3,4] c
여기에서 a,b,c는 모두 같은 value를 가진다.
a
[1, 2, 3, 4]
b
[1, 2, 3, 4]
c
[1, 2, 3, 4]
하지만 그 id까지 같은 것은 아니다.
id(a), id(b), id(c)
(139660748305984, 139660748305984, 139660748286224)
예제2
=[1,2,3]
a=a
b=[1,2,3]+[4] a
a
를 다시 정의한 것이라 보면 될 듯
a,b
([1, 2, 3, 4], [1, 2, 3])
id(a), id(b)
(139660748313296, 139660748290960)
이터닝
예제1
=1+2021
aid(a)
139660748450224
일단 할당
=2023-1
bid(b)
139660748450768
독립적으로 오브젝트 만들었으니 id가 다르지
id(2022)
139660748450832
예제2: 이제 다 이해했다고 생각했는데..
=1+2
aid(a)
7402432
=4-1
bid(b)
7402432
이게 왜 똑같지..?
(해설) 파이썬의 경우 효율성을 위해서 -5~256까지의 정수를 미리 저장해둠.
id(3)
7402432
.copy()
의 사용 (shallow copy의 사용)
예제1
- 아래의 예제를 살펴보자. (참조를 제대로 이해했다면 아래의 예제는 자연스럽게 이해가능)
= [3, [66,55,44]]
l1 = l1 l2
id(l1),id(l2)
(139660748249920, 139660748249920)
0]=4 l1[
l1
[4, [66, 55, 44]]
l2
[4, [66, 55, 44]]
5)
l2.append( l2
[4, [66, 55, 44], 5]
l1
[4, [66, 55, 44], 5]
예제2: R과 같이 = 를 쓰고 싶다면?
= [3, [66,55,44]]
l1 = l1.copy() l2
id(l1),id(l2) ## 드디어 주소가 달라졌다.
(139660748530944, 139660748530784)
주소 달라짐!!
0]=100 l1[
l1
[100, [66, 55, 44]]
l2
[3, [66, 55, 44]]
예제3: 이제 다 이해했다고 생각했는데..
- 이제 다 이해했다고 생각했는데..
= [3,[66,55,44]]
l1 = l1.copy() l2
id(l1),id(l2)
(139660807804960, 139660807804560)
1].append(33) l1[
l1
[3, [66, 55, 44, 33]]
l2
[3, [66, 55, 44, 33]]
왜 또 참조한것마냥 l1과 l2가 같이 바뀌고 있지?
Shallow copy의 이해
- 방금 살펴본 예제3을 이해하기 위해서는 shallow copy를 이해해야 한다.
예제1
=2222
a=2222 b
id(a),id(b)
(139660739479536, 139660739478192)
메모리 상황
- 2222라는 오브젝트가 어떤공간(id(a):
139753545300880
)에 생성되고 그 공간에a
라는 라벨이 붙음 - 2222라는 오브젝트가 어떤공간(id(b):
139753545300880
)에 생성되고 그 공간에b
라는 라벨이 붙음
즉 -5~256 이외의 2개의 메모리 공간을 추가적으로 사용
예제2
=[1,2,2222]
a=[1,2,2222] b
메모리 공간에 1,2는 이미 저장되어 있어서 생성할 필요 없고 2022만 메모리 공간에 생성하면 된다.
id(a), [id(a[0]),id(a[1]),id(a[2])] # a=[1,2,2222]
(139660739560064, [7402368, 7402400, 139660739480272])
id(b), [id(b[0]),id(b[1]),id(b[2])] # b=[1,2,2222]
(139660739560864, [7402368, 7402400, 139660739479312])
4) a.append(
a
[1, 2, 2222, 4]
b
[1, 2, 2222]
메모리상황
- -5~256까지의 숫자는 미리 메모리에 저장되어 있다.(
이터닝
) 이중에서 1은 id(a[0]):7394656
, 2는 id(a[1]):7394688
에 저장되어있음. - 2222가 공간 id(a[2]):
139753178093776
에서 만들어진다. - 어떠한 리스트오브젝트가 공간 id(a):
139753182327904
에서 만들어지고 원소로 [1,2,2222]를 가진다. 이 공간에 a라는 포스트잇을 붙인다. - 2222가 공간 id(a)[2]:
139753178095568
에서 만들어진다. - 어떠한 리스트오브젝트가 공간 id(b):
139753173818656
에서 만들어지고 원소로 [1,2,2222]를 가진다. 이 공간에 b라는 포스트잇을 붙인다. - a라는 포스트잇이 붙은 공간으로 이동하여 원소에 4를 추가시킨다.
즉 -5~256이외에 4개의 메모리 공간을 추가사용 (a,b,a의 2222,b의 2222)
예제3
= [3,[66,55,44]]
l1 = l1.copy() l2
id(l1), [id(l1[0]), id(l1[1])]
(139660748288960, [7402432, 139660739562784])
id(l2), [id(l2[0]), id(l2[1])]
(139660739539504, [7402432, 139660739562784])
메모리상황
- -5~256까지의 숫자가 메모리에 저장되어 있다.
- 저장된 숫자중 66,55,44를 묶어서 리스트로 구성하고 이 리스트를 공간 id(l1[1]):
139753183707216
에 저장. - 숫자 3과 공간 id(l1[1]):
139753183707216
에 저장된 리스트 [66,55,44]를 하나로 묶어서 새로운 리스트를 구성하고 이를 공간 id(l1):139753183437040
에 저장. 공간 id(l1):139753183437040
에l1
이라는 포스트잇 생성. - 공간 id(l2):
139753182311120
에l1
의 원소들을 모아서 새로운 리스트를 구성함. 공간 id(l2):139753182311120
에l2
라는 포스트잇 생성.
0] = 7777
l1[ l1,l2
([7777, [66, 55, 44]], [3, [66, 55, 44]])
id(l1), [id(l1[0]), id(l1[1])]
(139660748288960, [139660739478544, 139660739562784])
id(l2), [id(l2[0]), id(l2[1])]
(139660739539504, [7402432, 139660739562784])
- l1[0]은 원래 공간
7394720
와 binding 되어 있었음.
- 그런데 7777이라는 새로운 오브젝트가 공간 id(l1):
139753178092080
에 생성되고 l1[0]이 공간139753178092080
와 다시 binding 됨.
예제4
= [3,[66,55,44]]
l1 = l1.copy()
l2 7777) l1.append(
l1,l2
([3, [66, 55, 44], 7777], [3, [66, 55, 44]])
id(l1), [id(l1[0]), id(l1[1]), id(l1[2])]
(139660739540064, [7402432, 139660748258672, 139660739477968])
id(l2), [id(l2[0]), id(l2[1])]
(139660748282608, [7402432, 139660748258672])
예제3, 예제4를 통하여 리스트가 가변형객체라는 것을 확인할 수 있다. 예제3의 경우 l1이 저장되어있던 메모리공간의 내용물이 [3,[66,55,44]] 에서 [7777,[66,55,44]] 로 바뀌었다. 예제4의 경우 l1이 저장되어있던 메모리공간의 내용물이 [3,[66,55,44]] 에서 [3,[66,55,44],7777] 로 바뀌었다.
예제5: 우리를 힘들게 했던 그 예제.
(시점1)
= [3,[66,55,44]]
l1 = l1.copy() l2
l1,l2
([3, [66, 55, 44]], [3, [66, 55, 44]])
id(l1), [id(l1[0]), id(l1[1])]
(139660748600336, [7402432, 139660748255792])
id(l2), [id(l2[0]), id(l2[1])]
(139660748237712, [7402432, 139660748255792])
(시점2)
1].append(7777) l1[
l1[1]의 묶음방식이 저장된 공간에 7777 추가할 거야
l1,l2
([3, [66, 55, 44, 7777]], [3, [66, 55, 44, 7777]])
id(l1), [id(l1[0]), id(l1[1])]
(139660748600336, [7402432, 139660748255792])
id(l2), [id(l2[0]), id(l2[1])]
(139660748237712, [7402432, 139660748255792])
해설: 사실 시점1에서 메모리 주소상황을 잘 이해했다면 신기한 일이 아니다. .copy()
는 l1과 l2의 주소만 다르게 만들 뿐 내용물인 l1[0]
,l1[1]
는 동일하니까.
예제6: 신임교수=[‘최규빈’,‘이영미’]
-
최규빈, 이영미는 신임교수임
= ['최규빈','이영미'] 신임교수
id(신임교수), id('최규빈'), id('이영미')
(139660748511984, 139660748209680, 139660748209200)
-
신임교수를 누군가는 막내들이라고 부르기도 함.
= 신임교수 막내들
참조
id(막내들), id(신임교수)
(139660748511984, 139660748511984)
“막내들”이라는 단어와 “신임교수”라는 단어는 사실 같은 말임
여기까지 참조 설명
-
새로운 교수 “박혜원”이 뽑혔음.
"박혜원") 신임교수.append(
신임교수, 막내들
(['최규빈', '이영미', '박혜원'], ['최규빈', '이영미', '박혜원'])
-
전북대 통계학과에서 R특강팀을 구성하여 방학중 R교육을 실시하고자함. 특강팀은 우선 신임교수들로 구성.
= 신임교수.copy()
R특강팀 R특강팀
['최규빈', '이영미', '박혜원']
-
R특강팀에 최혜미
교수님 추가. (그렇지만 최혜미교수님이 막내는 아니야.. // 참조와 shallow copy의 차이점)
"최혜미") R특강팀.append(
R특강팀, 신임교수, 막내들
(['최규빈', '이영미', '박혜원', '최혜미'], ['최규빈', '이영미', '박혜원'], ['최규빈', '이영미', '박혜원'])
-
R특강팀에서 양성준 교수를 추가하여 파이썬 특강팀을 구성
= [R특강팀, "양성준"]
파이썬특강팀 파이썬특강팀
[['최규빈', '이영미', '박혜원', '최혜미'], '양성준']
-
이영미교수는 다른 일이 많아서 R특강 팀에서 제외됨. (그럼 자연히 파이썬에서도 제외됨!!)
"이영미") R특강팀.remove(
R특강팀, 파이썬특강팀
(['최규빈', '박혜원', '최혜미'], [['최규빈', '박혜원', '최혜미'], '양성준'])
하지만 이영미교수는 여전히 신임교수이면서 막내들임
신임교수, 막내들
(['최규빈', '이영미', '박혜원'], ['최규빈', '이영미', '박혜원'])
-
새로운 교수로 “손흥민”이 임용됨.
"손흥민") 막내들.append(
막내들, 신임교수
(['최규빈', '이영미', '박혜원', '손흥민'], ['최규빈', '이영미', '박혜원', '손흥민'])
-
그렇다고 해서 손흥민 교수가 바로 R이나 파이썬 특강팀에 자동소속되는건 아님
shallow copy로 id 주소가 각각 할당되었기 때문
R특강팀, 파이썬특강팀
(['최규빈', '박혜원', '최혜미'], [['최규빈', '박혜원', '최혜미'], '양성준'])
Deep copy
예제1: Motivation example
-
아래의 상황을 다시 생각해보자.
= ["양성준",["최규빈","이영미","최혜미"]]
파이썬특강팀 = 파이썬특강팀.copy()
ADSP특강팀 -1].remove("이영미") 파이썬특강팀[
파이썬특강팀, ADSP특강팀
(['양성준', ['최규빈', '최혜미']], ['양성준', ['최규빈', '최혜미']])
이슈: 이영미교수가 파이썬특강에서 제외되면서 ADSP특강팀에서도 제외되었음. 그런데 사실 이영미교수가 파이썬특강팀에서만 제외되길 원한 것이지 ADSP특강팀에서 제외되길 원한게 아닐수도 있음.
해결: Deep copy의 사용
import copy
패키지 필요
= ["양성준",["최규빈","이영미","최혜미"]]
파이썬특강팀 = copy.deepcopy(파이썬특강팀)
ADSP특강팀 -1].remove("이영미") 파이썬특강팀[
파이썬특강팀, ADSP특강팀
(['양성준', ['최규빈', '최혜미']], ['양성준', ['최규빈', '이영미', '최혜미']])
copy?
Type: module String form: <module 'copy' from '/home/csy/anaconda3/envs/py37/lib/python3.7/copy.py'> File: ~/anaconda3/envs/py37/lib/python3.7/copy.py Docstring: Generic (shallow and deep) copying operations. Interface summary: import copy x = copy.copy(y) # make a shallow copy of y x = copy.deepcopy(y) # make a deep copy of y For module specific errors, copy.Error is raised. The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances). - A shallow copy constructs a new compound object and then (to the extent possible) inserts *the same objects* into it that the original contains. - A deep copy constructs a new compound object and then, recursively, inserts *copies* into it of the objects found in the original. Two problems often exist with deep copy operations that don't exist with shallow copy operations: a) recursive objects (compound objects that, directly or indirectly, contain a reference to themselves) may cause a recursive loop b) because deep copy copies *everything* it may copy too much, e.g. administrative data structures that should be shared even between copies Python's deep copy operation avoids these problems by: a) keeping a table of objects already copied during the current copying pass b) letting user-defined classes override the copying operation or the set of components copied This version does not copy types like module, class, function, method, nor stack trace, stack frame, nor file, socket, window, nor array, nor any similar types. Classes can use the same interfaces to control copying that they use to control pickling: they can define methods called __getinitargs__(), __getstate__() and __setstate__(). See the documentation for module "pickle" for information on these methods.
예제2
-
deepcopy
= [3,[66,[55,44]]]
l1 = copy.deepcopy(l1) l2
1][1].append(33) l2[
l1,l2
([3, [66, [55, 44]]], [3, [66, [55, 44, 33]]])
print('level 1')
print('l1:', id(l1))
print('l2:', id(l2))
level 1
l1: 139660748278352
l2: 139660748540176
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: 139660748278352 [7402432, 139660739509776]
l2: 139660748540176 [7402432, 139660739569056]
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: 139660748278352 [7402432, [7404448, 139660748244224]]
l2: 139660748540176 [7402432, [7404448, 139660739567776]]
묶음 방식이 달라지면서 다른 주소가 할당된 모습
-
비교를 위한 shallow copy
= [3,[66,[55,44]]]
l1 = l1.copy() l2
1][1].append(33) l2[
l1,l2
([3, [66, [55, 44, 33]]], [3, [66, [55, 44, 33]]])
print('level 1')
print('l1:', id(l1))
print('l2:', id(l2))
level 1
l1: 139660739568976
l2: 139660748318112
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: 139660739568976 [7402432, 139660807428768]
l2: 139660748318112 [7402432, 139660807428768]
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: 139660739568976 [7402432, [7404448, 139660748313776]]
l2: 139660748318112 [7402432, [7404448, 139660748313776]]
-
비교를 위한 참조
= [3,[66,[55,44]]]
l1 = l1 l2
1][1].append(33) l2[
l1,l2
([3, [66, [55, 44, 33]]], [3, [66, [55, 44, 33]]])
print('level 1')
print('l1:', id(l1))
print('l2:', id(l2))
level 1
l1: 139660748238432
l2: 139660748238432
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: 139660748238432 [7402432, 139660748240128]
l2: 139660748238432 [7402432, 139660748240128]
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: 139660748238432 [7402432, [7404448, 139660748531664]]
l2: 139660748238432 [7402432, [7404448, 139660748531664]]
주소 다 똑같아
Shallow copy 연습문제
예제1
-
아래의 코드결과를 예측하라. 결과가 나오는 이유를 설명하라.
= [3,[66,55,44]]
l1= l1.copy()
l2-1].append(33) l1[
l1,l2
([3, [66, 55, 44, 33]], [3, [66, 55, 44, 33]])
shallow copy 썼으니까 같이 추가된 모습
예제2
-
아래의 코드결과를 예측하라. 결과가 나오는 이유를 설명하라.
= [3,[66,55,44]]
l1= l1.copy()
l2-1] = l1[-1]+[33] l1[
l1,l2
([3, [66, 55, 44, 33]], [3, [66, 55, 44]])
id(l1),id(l2)
(139660748276032, 139660748245744)
l1에 재할당한 것이라 생각하면 된다.
예제3
= [3,[66,55,44]]
l1= l1.copy()
l2-1] = l1[-1]+[33]
l1[-1].remove(33) l1[
l1,l2
([3, [66, 55, 44]], [3, [66, 55, 44]])
id(l1),id(l2)
(139660807804240, 139660748282048)
예제4
= [3,[66,55,44]]
l1= l1.copy()
l2-1] = l1[-1]+[33]
l1[-1].remove(33)
l1[-1].append(33) l1[
(잘못된 상상) 아래의 코드와 결과가 같을거야!!
= [3,[66,55,44]]
l1= l1.copy()
l2# l1[-1] = l1[-1]+[33]
# l1[-1].remove(33)
-1].append(33) l1[
l1,l2
([3, [66, 55, 44, 33]], [3, [66, 55, 44, 33]])
(하지만 현실은)
= [3,[66,55,44]]
l1= l1.copy()
l2-1] = l1[-1]+[33]
l1[-1].remove(33)
l1[-1].append(33) l1[
l1,l2
([3, [66, 55, 44, 33]], [3, [66, 55, 44]])
재할당의 개념이 있었기 때문에 주소가 다른게 할당된 것이고, 결국 다른 결과가 나온다.
예제5
= [3,[66,55,44]]
l1= l1.copy()
l2-1] += [33] # l1[-1] = l1[-1]+[33]
l1[-1].remove(33)
l1[-1].append(33) l1[
l1,l2
([3, [66, 55, 44, 33]], [3, [66, 55, 44, 33]])
id(l1),id(l2)
(139660739562464, 139660739561344)
주소는 다르지만 재할당의 개념이 없어!
l1에 append 하는 식으로 되어서 묶음 방식이 같이 움직인다.
??? 예제4랑 예제5는 같은코드가 아니었음!!!
a += [1]
는 새로운 오브젝트를 만드는게 아니고, 기존의 오브젝트를 변형하는 스타일의 코드였음! (마치 append 메소드처럼)
불변형 객체
Motivation example
-
우리는 이제 아래의 내용은 마스터함
= [3,[66,55,44]]
l1= l1.copy()
l2-1] += [33] # l1[-1].append(33)이랑 같은거.. l1[
l1,l2
([3, [66, 55, 44, 33]], [3, [66, 55, 44, 33]])
-
아래의 결과를 한번 예측해볼까?
=[3,(66,55,44)]
l1=l1.copy()
l21] += (33,) l2[
이번엔 튜플로
l1,l2
([3, (66, 55, 44)], [3, (66, 55, 44, 33)])
해설
(시점1)
=[3,(66,55,44)]
l1=l1.copy() l2
이번엔 리스트 안에 튜플을 넣어봄
l1,l2
([3, (66, 55, 44)], [3, (66, 55, 44)])
print('level 1')
print('l1:', id(l1))
print('l2:', id(l2))
level 1
l1: 139660739586080
l2: 139660807832512
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: 139660739586080 [7402432, 139660748091792]
l2: 139660807832512 [7402432, 139660748091792]
(시점2)
1] += (33,) l2[
l1,l2
([3, (66, 55, 44)], [3, (66, 55, 44, 33)])
print('level 1')
print('l1:', id(l1))
print('l2:', id(l2))
level 1
l1: 139660739586080
l2: 139660807832512
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: 139660739586080 [7402432, 139660748091792]
l2: 139660807832512 [7402432, 139660739627824]
주소 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 = lst1.copy()
lst2 ## 예제2
= ['양성준',['최규빈','이영미','최혜미']]
lst1 = copy.deepcopy(lst1) lst2
- 예제1: 4+1+2 = 7개의 공간 사용
- 예제2: 4+2+2 = 8개의 공간 사용
-
분노: 참조인지 카피인지 구분하는것도 힘든데, 카피도 shallow 인지 deep 인지 구분해서 사용해야해? 겨우 저 메모리때문에?
요약
-
파이썬은 메모리를 아끼기 위해서 shallow copy라는 이상한 행동을 한다.
-
통찰1: 그런데 오묘하게도 [1,2,3,4,5,6]와 같이 중첩된 리스트가 아니라면 문제가 되지 않음. (메모리는 아끼면서 문제가 되지 않는다?? 천재인데??)
- 중첩된 리스트가 아닐 경우는
shallow copy = deep copy
임.
1차원이면 이런 문제 생기지 않지, 하지만 모든 데이터가 1차원?
-
통찰2: 생각해보니까 모든 자료형이 불변형인 경우에도 문제가 되지 않음. (R은 모든 자료형이 불변형이다)
-
문제상황요약: [[1,2],[3,4]] 와 같이 리스트에 리스트가 포함된 형태라면 문제가 생긴다. (이건 개선이 필요함) - 개선1: 깊은복사(import copy) - 개선2: 넘파이
numpy
import numpy as np
2차원의 실체
-
2차원 array a,b를 선언하자.
= np.array([[11,22,33,44]]).reshape(2,2)
a = np.array([[11,22,33,44,55,66]]).reshape(2,3)
b = np.array([11,22,33,44]).reshape(4,1)
c = np.array([11,22,33,44]) d
-
a,b,c,d 속성비교
## 차원 a.shape, b.shape, c.shape, d.shape
((2, 2), (2, 3), (4, 1), (4,))
## 차원이랑 관련이 있어보임.. + 8의 배수 a.strides, b.strides, c.strides, d.strides
((16, 8), (24, 8), (8, 8), (8,))
-
strides는 무엇?
- strides: (다음 행으로 가기위해서 JUMP해야하는 메모리 공간수, 다음 열로 가기위해서 JUMP해야하는 메모리 공간수)
-
사실 a,b,c,d 는 모두 1차원으로 저장되어있음. (중첩된 리스트꼴이 아니라)
shape이나 strides 등의 옵션으로 1차원이 아니게 보여지는 것 뿐
참조
-
a를 선언, b는 a의 참조
=np.array([[1,2],[3,4]])
a=a ## 참조 b
a
array([[1, 2],
[3, 4]])
b
array([[1, 2],
[3, 4]])
a.shape
(2, 2)
b.shape
(2, 2)
-
a의 shape을 바꾸어보자 \(\to\) b도 같이 바뀐다
= (4,) a.shape
a
array([1, 2, 3, 4])
b
array([1, 2, 3, 4])
id(a),id(b)
(139660739644368, 139660739644368)
view
-
a를 선언, b는 a의 view
=np.array([[1,2],[3,4]])
a=a.view() ## shallow copy 라고 부르기도 한다. b
a
array([[1, 2],
[3, 4]])
b
array([[1, 2],
[3, 4]])
a.shape
(2, 2)
b.shape
(2, 2)
= (4,1) a.shape
a
array([[1],
[2],
[3],
[4]])
b
array([[1, 2],
[3, 4]])
id(a), id(b)
(139660382911248, 139660382911344)
view가 shallow copy 같은 이유 껍데기 주소만 복사해옴, 공간에 대한 주소는 같지 않음
-
그런데..
0]=100 a[
a
array([[100],
[ 2],
[ 3],
[ 4]])
b
array([[100, 2],
[ 3, 4]])
-
출생의 비밀
b
array([[100, 2],
[ 3, 4]])
b.base
array([[100],
[ 2],
[ 3],
[ 4]])
- ? 이거 바뀐 a아니야?
id(b.base), id(a)
(139660382911248, 139660382911248)
-
View - b가 a의 뷰라는 의미는, b가 a를 소스로하여 만들어진 오브젝트란 의미이다. - 따라서 이때 b.base는 a가 된다. - b는 자체적으로 데이터를 가지고 있지 않으며 a와 공유한다. - 이러한 의미에서 view를 shallow copy 라고 부른다. (stride, shape과 같은 껍데기만 새로 생성, base는 유지)
note1
원본 ndarray의 일 경우는 .base가 None으로 나온다.
a.base
print(a.base)
None
note2
b.base의 shpae과 b의 shape은 아무 관련없다.
b.shape
(2, 2)
b.base.shape
(4, 1)
copy
-
a를 선언, b는 a의 copy
=np.array([[1,2],[3,4]])
a=a.copy() # 껍데기를 새로 생성 (strides, shape) + base도 새로생성 b
id(a),id(b)
(139660382910288, 139660382910000)
-
a의 shape을 바꿔도 b에는 적용되지 않음
= (4,1)
a.shape a
array([[1],
[2],
[3],
[4]])
b
array([[1, 2],
[3, 4]])
-
그리고 a[0]의 값을 바꿔도 b에는 적용되지 않음.
0]=100 a[
a
array([[100],
[ 2],
[ 3],
[ 4]])
b
array([[1, 2],
[3, 4]])
-
b의 출생을 조사해보니..
a.base,b.base
(None, None)
출생의 비밀은 없었다. 둘다 원본.
-
.view()
는 껍데기만, .copy()
는 껍데기 + base 까지 새로생성
Appendix: .copy의 한계(?)
=np.array([1,[1,2]],dtype='O')
a a
array([1, list([1, 2])], dtype=object)
=a.copy() b
b
array([1, list([1, 2])], dtype=object)
0]=222 a[
a
array([222, list([1, 2])], dtype=object)
b
array([1, list([1, 2])], dtype=object)
1][0]=333 a[
a
array([222, list([333, 2])], dtype=object)
b
array([1, list([333, 2])], dtype=object)
해결책: 더 깊은 복사
import copy
=np.array([1,[1,2]],dtype='O')
a=copy.deepcopy(a) b
a
array([1, list([1, 2])], dtype=object)
b
array([1, list([1, 2])], dtype=object)
0]=100 a[
a,b
(array([100, list([1, 2])], dtype=object),
array([1, list([1, 2])], dtype=object))
1][0]=200 a[
a,b
(array([100, list([200, 2])], dtype=object),
array([1, list([1, 2])], dtype=object))
-
중간요약
- 사실
.copy()
는 온전한 deep copy 가 아니라 level 2 deep copy 이다. - 따라서
.copy()
는 base의 정보를 shallow copy 한다 (level 1 deep copy 한다.) - 그래서 base가 다시 중첩구조를 가지는 경우는 온전한 deep-copy가 수행되지 않는다.
- 그런데 일반적으로 넘파이를 이용할때 자주 사용하는 데이터 구조인 행렬, 텐서등은 base가 중첩구조를 가지지 않는다. (1차원 array로만 저장되어 있음)
- 따라서 행렬, 텐서에 한정하면
.copy()
는 온전한 deep copy라고 이해해도 무방하다.
행/열 같지 않으면 numpy쓰면 힘들걸..
모든 데이터 구조가 2차원까지로 정리 된다.
별명, 뷰, 카피
-
test 함수 작성
def test(a,b):
if id(a) == id(b):
print("별명")
elif id(a) == id(b.base) or id(a.base)==id(b):
print("뷰")
elif (id(a.base)!=id(None) and id(b.base)!=id(None)) and id(a.base) == id(b.base):
print("공통의 base를 가짐")
else:
print("카피, 혹은 아무 관련없는 오브젝트")
-
잘 동작하나?
(테스트1)
=np.array([1,2,3,4])
a=a b
test(a,b)
별명
참조
(테스트2)
=np.array([1,2,3,4])
a=a.view() b
test(a,b)
뷰
(테스트3)
=np.array([1,2,3,4])
a=a.view()
b=a.view() c
test(b,c)
공통의 base를 가짐
test(a,b)
뷰
test(a,c)
뷰
(테스트4)
=np.array([1,2,3,4])
a=a.copy() b
test(a,b)
카피, 혹은 아무 관련없는 오브젝트
결론
-
우리가 사용했던 어떠한 것들이 뷰가 나올지 카피가 나올지 사실 잘 모른다. (그래서 원리를 이해해도 대응할 방법이 사실없음)
예시1
=np.array([1,2,3,4])
a=a[:3] b
a
array([1, 2, 3, 4])
b
array([1, 2, 3])
test(a,b)
뷰
=a[[0,1,2]]
c c
array([1, 2, 3])
test(a,c)
카피, 혹은 아무 관련없는 오브젝트
= a[3] d
test(a,d)
카피, 혹은 아무 관련없는 오브젝트
예시2
=np.array([[1,2],[3,4]])
a a
array([[1, 2],
[3, 4]])
=a.flatten()
b=a.ravel()
c=a.reshape(-1) d
test(a,b)
카피, 혹은 아무 관련없는 오브젝트
test(a,c)
뷰
test(a,d)
뷰
test(c,d)
공통의 base를 가짐
test(b,c)
카피, 혹은 아무 관련없는 오브젝트