05의 개발 계발

[TIL] 230331 [Python] 객체의 immutable(불변) 과 mutable(가변) 본문

TIL

[TIL] 230331 [Python] 객체의 immutable(불변) 과 mutable(가변)

생각하는 코댕이 2023. 4. 1. 21:44
728x90

 


TIL 학습목표

  • immutable 과 mutable 의 차이점을 이해한다.
  • 객체의 Type 에 따라 immutable(불변) 과 mutable(가변) 을 구분 할 수 있다.
  • immutable 과 mutable 의 차이를 활용해 코딩에 적용하는 예를 안다.

1) 문제점 (Problem)

for stage, monsters in stages.items():
    # monsters 안에 데이터가 아무것도 남지 않을 때 까지 무한하게 반복해라!
    while monsters:
        for monster in monsters:
            if monster.current_hp <= 0:
                monsters.remove(monster)

위 코드를 작성 하였더니, 아래와 같이 경고문(warning)이 나왔다.

pylint 확장의 경고문

위 경고문은 for 문에서 mutable(가변) 인 list 가 수정이 일어나면 문제가 생길 수 있으니, 복사를 해서 사용하라는 권고문(consider)이다.

코드를 아래와 같이 ' [::] 깊은 복사 ' 를 이용해 list를 immutable(불변)으로 만들어 문제가 발생할 가능성을 배제해 주었다.

for stage, monsters in stages.items():
    # monsters 안에 데이터가 아무것도 남지 않을 때 까지 무한하게 반복해라!
    while monsters:
        for monster in monsters[::]:
            if monster.current_hp <= 0:
                monsters.remove(monster)

위 과정을 살펴보며 반복문을 활용하는데에 있어 immutable(불변) 과 mutable(가변) 을 구분 할 수 있어야함을 느끼고 학습을 진행하였다.

이를 정리하며, 데이터 type에 따라 immutable(불변) 과 mutable(가변) 을 구분해보자.


2) 학습내용

학습한 내용을 정리하도록 하자. 참고자료

▶용어 정리 

표준용어 한글용어 설명 예시
immutable 불변성 깊은복사를 하며, 복사 시 독립적인 주소를 가진다.
즉, 복사한 객체의 변화에 원본 객체는 독립적이다.
str , int , float , bool ...
mutable 가변성 얕은복사를 하며, 복사 시 하나의 주소를 공유한다. 즉, 복사한 객체의 변화를 원본 객체도 공유한다. dict , list , set
deep copy 깊은 복사 값 자체를 복사해 새로운 독립적인 객체를 만든다. [:] , [::] , copy.deepcopy()
shallow copy 얕은 복사 값이 들어있는 id를 참조해 의존적인 객체를 만든다. = , copy() , copy.copy()

* 깊은 복사와 얕은 복사는 참고자료를 통해 이해하도록 하자.

▼ immutable한 데이터타입은 복사한 객체  (a_) 의 변화 (+2) 에 원본 객체 (a) 가 영향을 받지 않는다.
# immutable한 int
a=10
a_=a	#a를 깊은 복사한 a_
a_+=2
print(a)	# 결과 a = 10
print(a_)	# 결과 a_= 12
▼ mutable한 데이터타입은 복사한 객체  (b_) 의 변화 (append 3) 에 원본 객체 (b) 가 영향을 받는다.
# mutable한 list 
b=[1,2]
b_=b	#b를 얕은 복사한 b_
b_.append(3)
print(b)	#결과 [1,2,3]
print(b_)	#결과 [1,2,3]

▶객체 구분    |     참고자료


▶Python의 데이터 보관방식   |   참고자료

j = 1
i = j
i += 1
print(id(1))  # 2631976747248 | 1의 id
print(id(j))  # 2631976747248 | 1의 id를 참조
print(id(i))  # 2631976747280 | 2의 id를 참조
print(id(2))  # 2631976747280 | 2의 id
print(id(3))  # 2631976747324 | 3의 id
print(id(3))  # 2631976747324 | 3의 id

위 코드에서 확인 할 수 있듯이, python은 객체를 생성하지 않은 값("3")도 임의로 id를 주어 저장 하고 있다.

그리고 객체를 선언하는 순간 저장해둔 값의 id를 참조하는 형식으로 구동하고 있다.


3) 시사점

[:]을 이용한 깊은 복사 시 주의해야할 점

print("\nmutable한 list")
b = [1, 2]
b_ = b  # b를 얕은 복사한 b_
b_.append(3)
print(b)  # 결과 [1,2,3]
print(b_)  # 결과 [1,2,3]

print("\n요소가 immutable한 list[:]")
b = [1, 2]
b_ = b[:]  # b를 깊은 복사한 b_
b_.append(3)
print(b)  # 결과 [1,2]
print(b_)  # 결과 [1,2,3]

print("\n요소의 내부는 mutable한 list[:]")
b = [[1, 2]]
b_ = b[:]  # b를 깊은 복사한 b_
b_.append(3)
b_[0].append(3)
print(b)  # 결과 [[1, 2, 3]]
print(b_)  # 결과 [[1, 2, 3], 3]

*참고:      [:] 대신 [::]를 사용해도 결과는 같게 나왔다.

list의 요소인 list는 mutable함을 유지한다.

위 코드에서 [:] 슬라이싱을 사용해 리스트를 추가 참조하여, mutable한 list를 immutable한 효과를 주었다.

하지만 list를 요소로 가지는 list의 경우, 요소인 list는 상위 list의 immutable함과는 별개로 mutable함을 보였다.

또한 [:] 대신 [::]를 사용해도 결과는 같았다.

즉, [:]를 사용 시, mutable한 요소를 가지고 있는 list일 경우 문제가 발생할 수 있다.
따라서 [:]는 권장되지 않는 방법이다. 대신 copy.deepcopy() 를 이용하도록 하자.

4) 알게 된 점(Learnd)

C언어에서의 포인터와는 조금 다른 python에서의 변수 참조 방법을 생각해보는 기회가 되었다.

또한  [:]를 이용한 깊은 복사와, 그 단점을 알 수 있었다.

추가로 [:] 와 [::] 의 차이점을 알아보려 했으나 정보가 부족하였다. 다음에 알게된다면 추가 포스팅을 하도록 하자.


230403 알게되었다. 간략히 슬라이싱에 대해 답변해준 GPT 의 응답을 첨부한다.

즉, [:] 와 [::] 는 모두 슬라이싱(slicing)을 통해 시퀀스를 깊은 복사(deep copy)하는 방법이다.
       차이점은 [::] 는step 이라는 인자를 받아 시퀀스 생성 시 부분 선택이 가능하다.
728x90