Sister Nosilv story

[+복습(1)] 3. 프로그래밍과 데이터 中 자료형분류와 가변성

by 노실언니

[복습위치]

▶ 프로그래밍과 데이터-Python programming and data 中 자료형들 & 자료형의 가변성

 처음에 int, float, bool, str type 그리고 그들의 형변환 conversion만 배웠고

나중에 list, tuple, dict, set type 배우면서 conversion은 간단하게 넘어갔는데

위 type object에 관련된 function, method 배울 땐 뇌가 약간 과부하오더니

mutable/immutable 배우는 구간에서 뇌정지가 왔다.

 

 공부량으로 치면 function, method에서 숙지해야할 양이 더 많은데 mutable/immutable에서 뇌정지가 온 이유는

듣긴 들었는데 뭔말인가, 지금 내 수준으론 이해 못 할 영역을 핥고가는 느낌이어서 찝찝해서이다.

 

 새로 배운 type들을 익히는 것, 그 object에 관련한 function, method를 익히는 것도 신경쓸거지만,

위의 것들을 익히기 전에, 자료형 분류와 가변성에 대해서 간단히 정리를 하기로 했다.

뇌용량최적화의 느낌으로!

도식화한 이미지는 ⓒ 2021 no-silver. all rights reserved. copy가능, 수정가능, 출처표시만 부탁드려요 :)

 

1. 자료형 분류하기 : Data type categorization

[분류기준1 : 요소갯수] 해당 자료형의 요소가 한 개여야만 하는지 혹은, 요소가 여러 개도 가능하고 또 요소별 자료형도 있는지

[분류기준2 : 가변성] 변수가 가리키는 주소는 불변인 채, 값만 변경하는 것이 가능한지

(가변성에 대한 정의가 나에게 모호함, 사실 잘 모르겠음★)

 

[분류기준1 : 요소갯수 factor]

 해당 자료형의 요소가 한 개여야만 하는지 혹은, 요소가 여러 개도 가능하고 또 요소별 자료형도 있는지

[remark]

- float → int 형변환시, 반올림이 아닌 소숫점이하 버림(내림)을 한다.

- type 표기방식 예시를 보면, 어느 하나 똑같은 모양새가 없다. 1  1.0  True  "1"  [1]  (1)  {1:1}  {1} 당연한거지만 놀라움

- string(문자열)은 'str' "str" '''str''' """str""" 여러 방식으로 표기한다.

- 위 기준을 알면 형변환은 자세하게 파고들어서 외울 게 아니라 common sense approach, 상식적으로 처리가 가능하다.

- 요소를 많이 담을 수 있는 자료형은 수많은 요소 중 특정 요소만을 어떻게 불러올 수 있는지가 궁금했다.

 특정 요소만을 부를 수 있으려면, 각 요소들은 유일한 지표를 가져야 한다. (사람으로 치면, 전세계 유일한 주민등록번호같은 것)

 string list tuple → 순차(0~) 혹은 역순(-1~)으로 자동생성되는 정수형 인덱스

 dict → 개발자가 직접 지정할 수 있는 key

 set → 요소가 중복되지않음으로 요소 그 자체가 유일한 지표

 

[분류기준2 : 가변성 mutable]

 변수가 가리키는 주소는 불변인 채, 값만 변경하는 것이 가능한지

 

정말ㅠㅠ 가변성에 대한 정의는 나에게 모호하다.

좀 더 파보려니 address도 무슨 heap영역이 있다하고 다른 뭐 어쩌구.. pass by value, call by reference, call by assignment ?

파지 않으니 모호하고, 파려니 조금 파가지고는 명확하게 될 것 같지 않다.

나는 class, object에 대한 개념도 정확치않고, 파이썬 초급핥는중이고,

어차피 중고급으로 넘어가면서 찬찬히 배울텐데.. 자바배울때 또 배울거같은데..

그래서, 많이 파지말고 모호한 선에서 최대한 이해해보자가 현 복습목표다. 아마 다 아시는 분께는 우습겠지요ㅠㅠ

[변수→메모리주소id(a)와 연결→주소id(a)에 값(객체)들어있음]의 상황에서,

- 모두 다 : 변수→[메모리주소id(a)의 변경]으로 전체 값(객체)를 바꿀 수 있음

- Mutable : 변수→메모리주소id(a)고정→일부 값(객체)변경이 가능

- Immutable : 변수→메모리주소id(a)고정→일부 값(객체)변경 불가능

 

[Remark] 날 힘들게 한 의문(모호함)과 해결

 int, float, bool의 경우, a = 1 → a = 2 으로 바꿀 수 있었고,

str, tuple의 경우, str_a = "123" → str_a[0]=a 은 바꿀 수 없었기때문에,

왜 이 두 부류를 다 Immutable(불변성)에 넣어야하나? 특히 int float bool은 에러도 안 나고 잘 바뀌던데?

이런 의문이 있었다.

 

 고민과 써치를 한 후,

Immutable이 말하는 불변성은, 무조건 일부값변경의 상황에서만 나오는 단어이고,

일부 값 변경시 에러가 나거나, 함수를 사용해서 전체값 변경메커니즘(메모리주소id(A)의 변경)으로 바꿀 수 있구나.

Mutable은 일부 값 변경시 변경된 값이 들어가는 주소는 바뀌더라도, 변수가 가리키는 주소는 변하지않는구나.

라고 깨달았다.

 

 ★ 이를 찾는 과정에서, call by reference/value/assignment 를 알게 되었다가

mutable = call by reference & immutable = call by value ? 이렇게 '착각'을 해버려서 또 오래 헷갈렸음.

전혀 다르다고 보면 되겠다.

immutable type이어도 call by reference가능하나 일부요소값 변경이 불가할 뿐! 이고,

mutable type이어도 특정 코드를 작성하면 call by value할 수 있다.

걍 다르다. 그래서 python은 call by assignment라고 부르나? 그치만, 안 배워서 모르겠음 ㅜ ㅜ

mutable/immutablecall by 는 어떻게 다른건지 내가 아는 선에서 가능한 명확하게 구분해보았다.

 

2. Declaring, Alias/Copy, Changing value

* not exact, just a means of understanding for newbie(me)

나를 헷갈리게 한 개념들에 대한, Overview

① 변수선언&객체생성

Creating object with declaring variable

 1. 객체생성

→ 2. 메모리공간에 객체담음

 → 3. 변수가 해당 객체가 들어있는 메모리공간 or 시작점 주소를 가리킴

   n factor type의 경우, 변수가 시작점의 주소를 가리키고-현 object가 다음 object의 주소를 연쇄적으로 가리킴

 

[remark]

- [1 factor type] int, float, bool → 메모리 공간 1칸

  변수variable는 값이 들어간 해당 칸의 주소id(a)를 가리킴

- [n factor type] string list tuple dict set ... → 메모리 공간 1(시작)+n칸

  ★변수variable는 사용자가 입력한 값이 없는 출발주소id(a)를 가리킴★ → mutable이 mutable인 이유임

  id(a) id(a[0]) : 변수가 가리키는 칸과 ↔ 사용자가 입력한 여러 요소값들이 시작되는 메모리 칸이 다르다.

 

② 객체값 변경 → 객체전체/일부변경여부 & 가변성여부

changing the value of an object(⊃variable)

- [객체전체 변경] : 값(객체전체)를 새로운 메모리주소에 담고, 변수는 그 새로운 메모리주소값을 가리킨다.

- [객체일부 변경] : 가변성(mutable)여부에 따라 결과가 달라진다.

 ㄴ mutable +whole object changed : variable → 새로운 주소(새로운 값)

 ㄴ mutable +some parts of object changed → 값을 변경한 영역의 주소는 변하나, 변수가 가리키는 주소 불변 like call by reference

 ㄴ immutable + whole object changed : variable → 새로운 주소(새로운 값)

 ㄴ immutable +some parts of object changed→[TypeError:'this type'object does not support item assignment]

 

[Remark]

- 전체든 일부든, 값을 바꾸면 무조건 새로운 값은 새로운 메모리에 들어온다. 기존 메모리 새로운 값 overwrite X

- ★ 일부요소값만 변경하는 경우만, 가변성여부 mutable에 따라 기전이 달라진다. → 나를 명확하게 해준 지점

 mutable : 일부요소값만 변경하는 것 가능

 immutable : 오류가 뜨므로, 정 바꾸고 싶다면 함수를 써서 whole change방식으로 돌아돌아 바꿔줘야함

- 전체/일부 변경에 따라 달라지는 이! <값변경 매커니즘>을 잘 이해해둬야

 a=val1   b=a   b=val2 에 대한 기전도 잘 이해할 수 있다.

 

③ 변수의 alias 혹은 복사본 만들기 & alias의 값변경

0. [ 변수명a = 값value ] : 값을 메모리주소에 담고, 변수a는 그 메모리주소값을 가리킨다.

 

- [alias] : 완전 그 자체 가상현실캐릭터가 죽을 때, 현실인간도 죽는다면 alias 관계

  변수가 가리키는 주소가 같다면 alias ≒ call by reference

  python 자료형이 mutable/immutable이든 상관없이 a = b는 call by reference같은 방식으로 진행된다.

 

- [alias의 값변경] : 기존 값변경 기전과 똑같음! ★가변성여부전체변경/일부변경여부에 따라 방식이 달라짐

 전체변경 → alias였던 변수가 다른 주소를 가리키게되면서, alias상태가 끊어짐

  값도 달라 주소도 달라

 일부변경 → 가변성여부에 따라 방식이 달라짐

* 우선, 일부변경은 [n factor type]string tuple/list dict set만 가능

  1 factor type의 1 factor를 변경하는 것은 전체변경이니까

 * ① 변수선언의 remark 中...

  -[n factor type] string list tuple dict set ... 에서,

   ★변수variable는 값이 없는 시작칸의 주소id(a)를 가리킴★ → mutable이 mutable인 이유

 id(a)id(a[0]) : 변수가 가리키는 칸과 값이 시작되는 칸이 다르다.

 

 다시(위는 본 설명을 위한 배경설명), 일부변경은mutable여부에 따라서 방식이 달라진다.

 - mutable : 변경된 일부 값들의 주소는 달라진다, 그러나 변수가 가리키는 시작칸의 주소id(a)=id(b)는 달라지지않는다.

       따라서 alias상태가 유지되므로, 변수b를 통해 일부값을 변경하는 것은 변수a에게도 동일한 영향을 준다.

       그래서, id(a) id(b) 주소불변상태로 가변형(mutable)이라는 이름을 붙임

 - immutable : 에러가 뜬다. 함수를 통해야한다. 그러면 전체변경 메커니즘을 사용하게되므로 alias상태가 끊어진다.

 

- [copy] : 값만 똑같지, 완전 개별적인 일란성쌍둥이? 분신?같은 관계 (분신만 죽지 본체는 안 죽으니까)

  값은 같으나 주소가 다름 ≒ call by value

 

[remark]

 python에서 call by reference/value는 mutable/immutable과 일대일대응하는 개념이 아니다.

가변성여부랑 call by ■에 많이 매몰되어있으면(나),

전체/일부변경에 따라서 또 달라진다는 것을 놓쳐버리면서, 그때부터 머리터지기시작한다.

왜냐하면 immutable은 불변형, 즉 변하지않아야한다는 느낌이 강한데,

 1. 가변형 : list type b[0] = "a"

 2. 불변형 : str type b = "123"

 3. 불변형 : int type b = 1

이거 다 문제없이 잘 변경되고, print(b)했을 때, 잘 출력되니까... 불변이라면서 왜 잘 바뀌는데..?

뭔말인가,

가변형이 대체 뭐고,  call by ■는 모고, 왜 바뀌냐..하면서 머리터졌따(나)

하지만, 인간은 언제나 답을 찾는다!

이렇게 정리하면서, 명쾌해졌다.

 

가변성과 상관없이!

[=]  a = b → 주소동일 - call by reference같은 방식으로 진행된다. 

[Changing object] 가변형이든, 불변형이든, 전체든, 일부변경이든 변경한 값은 새로운 주소에 담긴다.

+ 가변성은 일부값변경과 연관있고, 그에 대한 기전을 이해하기위해서, call by ■ 를 배우는 것 뿐이지,

mutable = call by reference도 immutable = call by value이 아니다.

 

Coding으로 확인해볼까!

[CODE] a=b는 변수의 주소를 넘긴다. = Call by reference (가변성 상관없음)

# = : address unchanged (Call by reference)
  
  # 1. immutable
  a = 1
  b = a
  print(a, b)		# 1  1
  print(a is b)   # True 주소동일
  
  c = "ABC"
  d = c
  print(c, d)		# ABC  ABC
  print(c is d)   # True 주소동일
  
  
  # 2. mutable
  a = [1, 2, 3]
  b = a
  print(a, b)		# [1, 2, 3]  [1, 2, 3]
  print(a is b)	# True 주소동일
  
  c = {1, 2, 3}
  d = c
  print(c, d)		# {1, 2, 3}  {1, 2, 3}
  print(c is d)   # True 주소동일
  
  # → All True 주소동일=주소불변
  # = : address unchanged (Call by reference)

 

[CODE] 변경한 값은 새로운 주소에 담긴다. (전체일부/가변성 상관없음)

# Changing value(object) : address changed
    
    # 1. immutable_whole changed: int, float, string + bool, tuple, frozenset
    # 가변형type의 전체값(object)변경하기
    a = 1
    b = a
    b = 1.0
    print(a, b)		# 1  1.0
    print(a is b)   # False		변수가 가리키는 주소변함
    
    c = "ABC"
    d = c
    d = "abc"
    print(c, d)		# ABC  abc
    print(c is d)   # False		변수가 가리키는 주소변함
    
    
    # 2. mutable_whole changed : list, set + dict, user-definded class
    # 가변형type의 전체값(object)변경하기
    a = [1, 2, 3]
    b = a
    b = [4, 5, 6]
    print(a, b)		# [1, 2, 3]  [4, 5, 6]
    print(a is b)	# False		변수가 가리키는 주소변함
    
    c = {1, 2, 3}
    d = c
    d = {4, 5, 6}
    print(c, d)		# {1, 2, 3} {4, 5, 6}
    print(c is d)   # False		변수가 가리키는 주소변함
    
    # 1, 2. → All False : 전체 값 변경시, 항상 변수가 가리키는 주소가 변함
    # Changing whole value(object) : address changed & no alias
    
    
    # 3. mutable_some parts changed : list + set, dict, user-definded class
    # 가변형type의 일부 값(object)변경하기
    a = [1, 2]
    print(a)					# [1, 2]
    print(id(a), id(a[0]))		# a:2694911775616, a[0]:2694906145072
    b = a						# make alias
    b[0] = "A"					# 일부요소값만 변경
    print(a, b)					# [A, 2]  [A, 2] both changed → = still alias
    print(id(a), id(a[0]))		# a:2694911775616(불변), a[0]:2694911692400(변화)
    print(id(b), id(b[0]))		# b:2694911775616(불변), b[0]:2694911692400(변화)
    print(a == b, a[0] == b[0], a is b, a[0] is b[0])	# All True = still alias
    # Changing some part of mutable type : id(a)unchanged, id(a[.])changed & still alias

 

[CODE] 일부 값(object)변경은 mutable/immutable에 따라 기전이 다르다.

"""
      Changing only some part of value(object) : 일부 값 변경
      1. immutable → error or using some function to "Changing whole value"
      2. mutable → Changing like call by reference
      """
      
      # 1. immutable : string, tuple + int, float, bool, frozenset
      a = "ABC"
      b = a
      b[0] = "a"      # TypeError: 'str' object does not support item assignment
      
      c = (1, 2, 3)
      d = c
      d[0] = "a"      # TypeError: 'tuple' object does not support item assignment
      # Immutable → Error : TypeError: this object does not support item assignment
      # immutable의 일부 값 변경은 함수사용으로 전체 값 변경으로 치환하기 전엔 불가능
      
      
      # 2. mutable : list, dict + set, user-definded class
      a = [1, 2, 3]
      print(a)
      b = a
      b[0] = "A"
      print(a, b)		# [A, 2, 3]  [A, 2, 3]
      print(a is b)	# True : still alias
      
      c = {1: 'a', 2: 'b', 3: 'c'}
      print(c)
      d = c
      d[1] = 1
      print(c, d)		# {1, 2, 3}  {1, 2, 3}
      print(c is d)   # True : still alias
      # mutable의 일부 값 변경은 해당 값의 주소는 변하더라도,
      # 변수가 가리키는 주소값은 변하지 않으므로 still alias & call by reference

 

반응형

블로그의 정보

노력하는 실버티어

노실언니

활동하기