파이썬 메모리 관리와 데이터 타입: 가변성, 불변성, 복사 및 인코딩

  1. 가변 및 불변 데이터 타입 =============

1.1 개념

가변 데이터 타입: 값이 수정될 때 메모리 주소가 변경되지 않음

불변 데이터 타입: 값이 수정될 때 메모리 주소가 변경됨

1.2 일반 데이터 타입의 코드 구현

가변 타입: list, dict, set

불변 타입: str, int, float, bool, tuple

(1) 정수 - 불변

num_val = 10
print(id(num_val))  # 140709452925800
num_val = 20
print(id(num_val))  # 140709452926080

(2) 부동소수점 - 불변

float_val = 3.14
print(id(float_val))  # 1369240625488
float_val = 2.71
print(id(float_val))  # 1369240630864

(3) 문자열 - 불변

text_data = "python"
print(text_data, type(text_data), id(text_data))  # python <class 'str'> 2077972897840
text_data = "programming"
print(text_data, type(text_data), id(text_data))  # programming <class 'str'> 2077972974448

(4) 불리언 - 불변

flag = True
print(flag, type(flag), id(flag))  # True <class 'bool'> 140709452925800
flag = False
print(flag, type(flag), id(flag))  # False <class 'bool'> 140709452925832

(5) 튜플 - 불변

collection = (1, 2, 3)
print(collection, type(collection), id(collection))  # (1, 2, 3) <class 'tuple'> 2759659910528
collection = collection.__add__((4, 5, 6))
print(collection, type(collection), id(collection))  # (1, 2, 3, 4, 5, 6) <class 'tuple'> 2759660046368

(6) 리스트 - 가변

data_list = [1, 2, 3]
print(data_list, type(data_list), id(data_list))  # [1, 2, 3] <class 'list'> 2513866568320
data_list.append(999)
print(data_list, type(data_list), id(data_list))  # [1, 2, 3, 999] <class 'list'> 2513866568320

(7) 딕셔너리 - 가변

info_dict = {'name': 'lee'}
print(info_dict, type(info_dict), id(info_dict))  # {'name': 'lee'} <class 'dict'> 2838915653376
info_dict['name'] = 'kim'
print(info_dict, type(info_dict), id(info_dict))  # {'name': 'kim'} <class 'dict'> 2838915653376

(8) 집합 - 가변

data_set = {'apple', 'banana'}
print(data_set, type(data_set), id(data_set))  # {'banana', 'apple'} <class 'set'> 2297948368096
data_set.add('orange')
print(data_set, type(data_set), id(data_set))  # {'orange', 'banana', 'apple'} <class 'set'> 2297948368096

요약: 파이썬에서 가변과 불변의 차이는 값이 수정된 후 메모리 주소가 변경되는지 여부에 있습니다.

1.3 값 전달과 참조 전달

(1) 개념

값 전달: 각 값은 고유한 메모리 주소를 가집니다

참조 전달: 중간에 메모리 주소가 해당 값을 중계하는 역할을 합니다

엄밀히 말해, 파이썬은 순수한 값 전달도 참조 전달도 아닙니다

(2) 파이썬의 전달 규칙:

만약 불변 타입을 전달한다면, 함수 내에서 수정해도 원래 변수에는 영향이 없습니다

만약 가변 데이터 타입을 전달한다면, 함수 내에서 수정할 때 재할당이 아닌 수정이라면 원래 변수에 영향을 줍니다

(3) 값과 참조의 개념

값은 변수 = 구체적인 값 (하나의 메모리 공간에 변수 값이 저장됨)

참조는 변수 = 메모리 주소 (메모리 주소가 값을 가리킴)

(4) 매개변수 전달

가변 타입의 매개변수 전달:

리스트, 딕셔너리 등의 경우, 함수 내에서 매개변수를 수정하면 원래 객체에도 영향을 줍니다

불변 타입의 매개변수 전달:숫자, 문자열 등의 경우, 함수 내에서 매개변수를 수정해도 원래 객체에는 영향을 주지 않습니다**# 가변 데이터 타입은 값을 수정해도 메모리 주소가 변하지 않습니다** # 불변 데이터 타입은 값을 수정하면 메모리 주소가 변합니다

  1. 힙과 스택의 개념 =========

힙(데이터 구조): 선입선출(FIFO)

스택(데이터 구조): 후입선출(LIFO)

  1. 가비지 컬렉션 메커니즘 ==========

3.1 개념:

가비지 컬렉션(GC)은 파이썬 인터프리터가 내장한 메커니즘으로, 사용할 수 없는 변수 값이 차지하는 메모리 공간을 회수하는 역할을 합니다(메모리에서 변수명이 가리키지 않는 데이터는 모두 쓰레지 데이터입니다)

3.2 역할:

프로그램 실행 과정에서 많은 메모리 공간이 할당되는데, 일부 사용하지 않는 메모리 공간을 제때 정리하지 않으면 메모리 고갈(메모리 오버플로우)이 발생하여 프로그램이 중단될 수 있습니다

3.3 힙 영역과 스택 영역

변수를 정의할 때 변수명과 변수값 모두 저장되어야 합니다

스택 영역: 변수명과 값의 메모리 주소 연관 관계는 일반적으로 프로그램의 스택 영역에 저장됩니다

힙 영역: 변수값은 힙 영역에 저장되며, 메모리 관리 및 회수는 힙 영역의 내용을 대상으로 합니다

3.4 원리

주로 참조 계수 방식을 사용하며, 세대별 회수 및 표시-삭제 방식을 보조로 사용합니다

(1) 참조 계수

x = 1  # 변수값 1의 참조 횟수는 1
y = z = x  # 변수값 1의 참조 횟수는 3
y = z = 2  # 변수값 1의 참조 횟수는 0
x = 1  # 변수값 1의 참조 횟수는 1

인터프리터는 참조 횟수가 0인 데이터를 제거합니다

(2) 표시-삭제

메모리는 매번 실행 시 스캔됩니다

첫 번째 스캔---특정 값이 쓰레지로 발견됨---즉시 제거하지 않음---어떤 코드에서 해당 값에 다시 참조할 수 있음---해당 쓰레지에 태그 표시---1회 발생

두 번째 스캔---해당 값이 여전히 쓰레지임---즉시 제거하지 않음---해당 쓰레지에 태그 표시---2회 발생

태그 횟수가 10회가 되면 가중치가 상승합니다

(3) 세대별 회수

매번 스캔에서 쓰레지가 발견되지만, 모든 쓰레지가 현재 반드시 제거해야 할 쓰레지는 아닙니다. 태그를 표시합니다

등급 1 : 10분마다 스캔, 특정 쓰레지가 계속 존재할 경우, 표시 횟수가 100회가 되면 권한 상승 등급 2 : 20분마다 스캔, 특정 쓰레지가 계속 존재할 경우, 표시 횟수가 200회가 되면 권한 상승 등급 3 : 30분마다 스캔, 특정 쓰레지가 계속 존재할 경우, 표시 횟수가 300회가 되면 삭제

쓰레지가 다시 사용될 수 있으므로 반복적인 메모리 오버헤드를 방지하기 위해 위와 같은 메커니즘이 도입되었습니다

  1. 작은 정수 풀 =======

파이썬에서 자주 사용하는 일부 숫자들은 작은 정수 풀로 정의되어 있으며, 범위는 [-5, 256]입니다. 파이썬은 이 숫자들에 대해 미리 메모리 공간을 생성해 두었습니다.

여러 번 재정의해도 새로운 공간이 다시 생성되지 않지만, 작은 정수 풀 범위外的 숫자는 재정의 시마다 새로운 공간이 생성됩니다.

따라서 작은 정수 풀 내의 숫자는 메모리 주소가 반드시 동일하며, 풀 외의 숫자는 메모리 주소가 다릅니다.

  1. 깊은 복사와 얕은 복사 =======

5.1 깊은 복사

완전히 새로운 객체를 생성하며, 이 객체는 원본 객체와 완전히 독립적입니다

모든 중첩 객체를 재귀적으로 복사하여 내용까지 복사하므로, 새로운 객체를 수정해도 원본 객체에는 영향이 없습니다

(완전 복사)

import copy
original = [1, 2, 3, [7, 8, 9]]
copied = copy.deepcopy(original)  # 깊은 복사로 생성된 리스트
print(copied)  # [1, 2, 3, [7, 8, 9]]
# 원본 리스트의 가변 타입 요소 수정
original[3][0] = 666
print(original)  # 원본 [1, 2, 3, [666, 8, 9]]
print(copied)  # 깊은 복사 리스트 [1, 2, 3, [7, 8, 9]]
# 원본 리스트의 불변 타입 수정
original[1] = 222
print(original)  # [1, 222, 3, [666, 8, 9]]
print(copied)  # [1, 2, 3, [7, 8, 9]]

새 객체와 원본 객체는 가변 데이터 타입에 대한 참조가 일치하지 않습니다

5.2 얕은 복사

새로운 객체를 생성하고 원본 객체의 요소를 새 객체로 복사합니다

그러나 원본 객체에 가변 요소(예: 리스트)가 포함된 경우, 새 객체의 이러한 요소는 여전히 원본 객체의 해당 요소와 동일한 메모리 주소를 공유합니다

(단일 계층만 복사)

import copy
source = [1, 2, 3]
target = copy.copy(source)  # 얕은 복사로 생성된 새 리스트
source.append(666)
print(source)  # 원본 리스트 [1, 2, 3, 666]
print(target)  # 얕은 복사 리스트 [1, 2, 3]
target.append(999)
print(target)  # 얕은 복사 리스트 수정이 원본에 영향을 주지 않음 [1, 2, 3, 999]
import copy
base = [1, 2, 3, [7, 8, 9]]
duplicate = copy.copy(base)  # 얕은 복사로 생성된 리스트
print(duplicate)  # [1, 2, 3, [7, 8, 9]]
# 원본 리스트의 가변 데이터 타입 요소 수정
base[3][0] = 666
print(base)  # 원본 [1, 2, 3, [666, 8, 9]]
print(duplicate)  # 얕은 복사 리스트 [1, 2, 3, [666, 8, 9]]<br></br># 원본 리스트의 불변 타입 수정
base[1] = 222
print(base)  # [1, 222, 3, [666, 8, 9]]
print(duplicate)  # [1, 2, 3, [666, 8, 9]]

새 객체와 원본 객체는 가변 데이터 타입에 대한 참조가 일치합니다

요약:

깊은 복사는 완전히 독립적인 객체를 생성하며, 객체 내의 모든 관계를 재귀적으로 복사합니다

얕은 복사는 새로운 객체를 생성하지만, 새 객체는 원본 객체의 가변 데이터 타입 관계를 공유합니다

  1. 문자 인코딩 =======

6.1 개념:

컴퓨터는 이진수만 인식할 수 있으며, 인간과 컴퓨터가 상호작용할 때 사용하는 것은 인간이 이해할 수 있는 문자(예: 한자, 영문)입니다.

인간이 일반적으로 사용하는 문자와 컴퓨터의 숫자 간 상호작용은 반드시 변환 및 번역 과정을 거쳐야 하며, 이 과정은 특정 표준을 참조해야 합니다. 이 표준을 문자 인코딩 표라고 합니다.

문자 인코딩 표에는 문자와 숫자의 일대일 대응 관계가 저장되어 있습니다.

인코딩은 인간이 일반적으로 사용하는 문자를 컴퓨터가 인식할 수 있는 숫자로 변환하는 과정을 의미합니다.

6.2 문자 인코딩의 발전 과정

첫 번째 단계(독점 시대):

컴퓨터는 미국인이 발명했으며, 컴퓨터가 영문을 인식할 수 있도록 영문 문자와 숫자의 대응 관계를 정의했습니다. 이것이 ASCII 코드 표이며, 영문 문자와 숫자의 대응 관계만 기록합니다.

1바이트가 1개의 영문 문자에 해당하며, 1바이트=8비트로 최대 256개의 숫자를 포함할 수 있으며, 이는 모든 영문 문자를 표현하기에 충분합니다.

ASCII 코드 표에서:

A-Z: 65-90

a-z: 97-122

0-9: 48-57

두 번째 단계(다국어 시대):

컴퓨터의 보급과 발전에 따라 각 국가도 자국의 문자를 이진수로 변환해야 했습니다.

GBK 코드(국제 표준):

중국어, 영문 문자와 숫자의 대응 관계를 기록합니다.

1바이트로 영문 문자를 저장하고, 2바이트로 중국어 문자를 저장합니다. 부족하면 3바이트 또는 4바이트를 사용합니다(2바이트로 표현할 수 있는 중국어 문자의 최대 수는 2^16=65536개입니다).

Euc_kr:

한국어, 영문 문자와 숫자의 대응 관계를 기록합니다.

shift_JIS:

일본어, 영문 문자와 숫자의 대응 관계를 기록합니다.

세 번째 단계(통일 시대):

유니코드(만국 코드):

모든 국가의 문자와 숫자의 대응 관계를 기록하며, 모든 문자는 최소 2바이트로 저장됩니다.

현재 컴퓨터는 모든 국가의 문자를 출력할 수 있으며, 메모리에는 유니코드 인코딩이 사용됩니다.

유니코드는 최소 2바이트를 사용하지만, ASCII 코드에서 1바이트가 1개의 영문 문자에 해당하므로 유니코드는 저장 공간과 입출력 시간을 낭비하게 됩니다. 따라서 새로운 인코딩(utf-8)이 개발되었습니다.

utf-8:

1바이트로 영문 문자를 저장하고

3바이트로 중국어 문자를 저장합니다

결론:

메모리 내 인코딩은 고려할 필요가 없으며, 하드 디스크에 저장된 형식만 고려하면 됩니다

한자, 영문------유니코드------GBK

한글, 영문------유니코드------Euc_kr

모든 국가 문자--------유니코드-------utf-8

6.3 문자 인코딩 적용

6.3.1 인코딩 및 디코딩

인코딩(encode): 인간이 일반적으로 사용하는 문자를 컴퓨터가 인식할 수 있는 이진수로 변환

디코딩(decode): 컴퓨터의 이진수를 인간이 일반적으로 사용하는 문자로 변환

text = '안녕하세요'
# 문자열을 gbk로 인코딩
print(text.encode(encoding='gbk'))  # b'\xb9\xe3\xbe\xc6\xbb\xa4\xbc\xbc'
# 문자열을 utf-8로 인코딩
print(text.encode())  # b'\uc548\uc6b0\ud558\uc138\uc694'

문자열 앞에 b가 붙으면 해당 데이터 타입이 바이트 타입이며, 바이트 타입은 이진수로 볼 수 있습니다.

# gbk 형식의 이진수를 여전히 gbk로 디코딩
print(b'\xb9\xe3\xbe\xc6\xbb\xa4\xbc\xbc'.decode('gbk'))  # 안녕하세요
# utf-8 형식의 이진수를 여전히 utf-8로 디코딩
print(b'\uc548\uc6b0\ud558\uc138\uc694'.decode())  # 안녕하세요
print(b'\uc548\uc6b0\ud558\uc138\uc694'.decode('utf-8'))  # 안녕하세요

6.3.2 코드에서 문자 깨짐 문제 해결 방법

데이터가 처음 어떤 형식으로 인코딩되었는지 그대로 그 형식으로 디코딩해야 합니다.

6.3.3 파이썬 인터프리터가 문자 깨짐 문제를 해결하는 방법

파이썬2 인터프리터: 기본 인코딩은 ASCII 코드입니다

파이썬3 인터프리터: 기본적으로 utf-8 인코딩을 사용합니다

방법 1

파일 헤더: 파일의 가장 상단에 작성하여 인터프리터가 지정된 인코딩을 사용하도록 알려야 합니다

coding:utf8

-- coding:utf8 -- (이것은 가독성을 위한 작성 방식입니다)

파이썬2 인터프리터에서 인코딩 형식을 지정하지 않고 중국어 주석을 작성하면 실행 시 오류가 발생합니다(파이썬2는 기본적으로 ASCII 사용).

gbk 또는 utf-8로 인코딩하면 오류가 발생하지 않습니다.

방법 2

문자열 접두사: 파이썬2 인터프리터 환경에서 문자열을 정의할 때 앞에 u를 붙이는 것이 관례입니다.

(u는 유니코드를 의미하며, 메모리 내 형식은 유니코드이고, 하드 디스크 형식은 utf-8입니다)

파일 헤더를 utf-8로 지정하지 않고 문자열에 u 접두사를 붙여도 여전히 ASCII로 인코딩되어 오류가 발생합니다.

태그: 파이썬 메모리 관리 데이터 타입 가변성 불변성

6월 2일 22:42에 게시됨