개발 · 자동화기초 · 21분 읽기

Python 객체·자료형과 복사

변수는 객체를 가리키는 이름입니다. 가변성, 같은 객체의 공유, 얕은 복사와 자료형별 연산을 구분합니다.

개념부터 찾아보기

정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.

이 노트에서 설명할 수 있게 되는 것

  1. 변수 대입·가변 객체 변경·재대입을 참조 관계로 설명한다.
  2. 중첩 리스트에서 얕은 복사 뒤 공유되는 객체를 찾아낸다.
  3. is·==·자료형을 이용해 출력 차이의 원인을 확인한다.

01이름의 대입과 객체의 변경을 분리해서 읽기

  • 에서 a = [1, 2]는 객체에 a라는 이름을 연결한다.
  • b = a는 그 객체를 복제하는 작업이 아니라 같은 객체에 다른 이름을 연결한다.
  • b.append(3)은 두 이름이 함께 참조하는 내부를 바꾸지만 b = [9]는 b가 다른 객체를 가리키게 할 뿐 a의 연결을 바꾸지 않는다.
    • 이 차이가 함수 인자와 클래스 속성에서도 반복된다.
  • ==는 값의 동등성을, is는 동일 객체인지를 비교한다.
  • 작은 정수나 문자열의 구현상 재사용 때문에 is가 우연히 참이 되는 결과에 의존하면 안 된다.
  • None 같은 단일값과의 비교에는 is None을 사용한다.
  • id는 객체 생존 중의 식별값을 제공하지만 모든 구현에서 그 숫자를 사용자가 역참조할 메모리 주소로 다뤄서는 안 된다.
a = [1, 2]
b = a
b.append(3)
print(a, a is b)      # [1, 2, 3] True
b = [9]
print(a, b)          # [1, 2, 3] [9]

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

02수·문자열·None·bool의 의미와 변환

  • 는 C의 short//long처럼 사용자가 고정 바이트 수를 선택하는 기본 정수형이 아니다.
  • 사용 가능한 메모리 범위에서 큰 정수를 표현하며 bool은 의 하위형이라 True가 산술에서 1처럼 쓰일 수 있다.
    • 그러나 논리값을 메모리 1바이트 상수로 고정해 설명하는 것은 객체 모델과 맞지 않는다.
  • float는 일반적인 CPython 환경에서 이진 부동소수점으로 표현되어 0.1 같은 십진수를 정확히 담지 못할 수 있다.
  • str은 텍스트, bytes는 바이트열이고 서로 다른 자료형이다.
  • 에 별도 char 기본형은 없으며 문자열의 한 요소도 길이 1인 str이다.
  • None은 값이 없음이라는 상태를 표현하는 객체이며 이름을 아예 정의하지 않은 NameError와 다르다.
  • 빈 문자열·빈 컨테이너·수 0·None은 거짓으로 평가되지만 문자열 "0"은 비어 있지 않아 참이다.
    • bool 변환을 입력 문자열의 의미 검증으로 대신하지 않는다.
print(type(True).__name__, int(True))  # bool 1
print(bool("0"), bool(""))              # True False
print(type(bin(10)).__name__, bin(10))  # str 0b1010
print(int("1010", 2))                   # 10

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

수업 원본 자료

True의 값·자료형·정수 표현

Python에서 True를 출력하면 True, type은 bool, bin 결과는 0b1로 표시된 수업 캡처원본 크기로 보기 ↗
수업 원본 캡처 · bool의 값과 정수 변환 표현을 비교한다.

이 자료에서 읽어낼 것

  1. type(a)는 bool이다. 화면에 0b1이 나온다고 해서 변수의 자료형이 int로 바뀐 것은 아니다.
  2. bin은 정수로 해석할 수 있는 값을 2진수 문자열로 표현한다. bool이 int의 하위 형식이므로 True에는 정수 1에 해당하는 표현이 나온다.
  3. 이 출력은 객체가 차지하는 메모리 크기를 뜻하지 않는다.

03list·tuple·dict·set의 연산과 선택

  • 는 순서가 있는 가변 시퀀스로 서로 다른 타입의 객체를 참조할 수 있다.
  • 은 요소 참조의 배열을 바꾸지 못하는 시퀀스지만 그 안이 이면 내부 는 변할 수 있다.
  • 는 키와 값을 연결하고 키는 hash 가능한 객체여야 한다.
  • 은 중복 없는 원소 집합이며 인덱스로 순서 접근하는 자료형이 아니다.
    • {}는 빈 이고 빈 은 ()으로 만든다.
  • append는 객체 하나를 끝에 붙이고 extend는 iterable의 요소를 하나씩 이어 붙인다.
  • sort는 원본 를 제자리에서 정렬하고 None을 반환하며 sorted는 새 를 만든다.
  • remove는 값 기준, pop은 위치 기준으로 제거하고 pop은 제거한 요소를 반환한다.
  • CPython 는 객체 참조의 동적 배열로 구현되며 일반적인 연결 리스트와 구조가 다르다.
a = [1]
a.append([2, 3])
print(a)             # [1, [2, 3]]
b = [1]
b.extend([2, 3])
print(b)             # [1, 2, 3]
print(b.sort())      # None

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

04index·slice·반복 가능성과 iterator

  • 0부터 시작하는 인덱스는 특정 요소 하나를 고른다.
    • 음수 인덱스 -1은 마지막 요소를 뜻한다.
  • slice의 start:stop:step은 stop을 제외한 범위이고 생략값은 방향과 시퀀스 길이에 따라 정해진다.
  • 의 슬라이스는 새 바깥 를 만들지만 안의 요소 객체를 깊게 복제하지 않는다.
    • 범위를 벗어난 단일 인덱스와 슬라이스의 처리도 다르다.
  • iterable은 iter()로 를 얻을 수 있는 객체이고 는 next()로 값을 하나씩 내다가 끝에서 StopIteration을 알린다.
  • 여러 요소를 현재 메모리에 저장해야만 iterable인 것은 아니다.
  • range나 generator처럼 값을 필요할 때 생산할 수 있다.
  • 를 한 번 소진하면 같은 객체에서 다시 처음부터 반복되지 않을 수 있으므로 파일 스트림과 generator를 처럼 재사용한다고 가정하지 않는다.
a = [10, 20, 30, 40]
print(a[-1], a[1:3])  # 40 [20, 30]
it = iter(a)
print(next(it), next(it))  # 10 20
print(list(it))            # [30, 40]
print(list(it))            # []

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

05얕은 복사와 깊은 복사를 중첩 list로 추적하기

  • copy()나 [:]는 새 바깥 컨테이너를 만들고 기존 요소에 대한 참조를 넣는 얕은 복사다.
    • 따라서 바깥 의 append는 분리되지만 안쪽 를 수정하면 두 바깥 에서 같은 변경이 보일 수 있다.
  • 수정하기 전에는 바깥 객체도 같은 주소였다가 수정 때 자동으로 분리된다는 copy-on-write 설명은 .copy의 동작이 아니다.
    • 복사 시점부터 바깥 객체는 이미 별개다.
  • 는 내부 객체 그래프까지 재귀 복사를 시도하고 memo를 사용하여 반복 참조와 순환을 처리한다. 모든 외부 자원·파일·소켓까지 의미 있게 복제해 주는 만능 함수는 아니므로 공유해야 할 객체의 정책을 고려한다.
  • 불변 안에 가변 가 들어가는 예는 컨테이너 자체의 불변성과 전체 객체 그래프의 불변성이 다르다는 점을 보여 준다.
from copy import deepcopy
a = [[1, 2], 3]
b = a.copy()
c = deepcopy(a)
a[0].append(9)
print(a is b, a[0] is b[0])  # False True
print(b)                    # [[1, 2, 9], 3]
print(c)                    # [[1, 2], 3]

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

LEARN THROUGH PRACTICE

얕은 복사의 바깥 목록과 안쪽 목록을 따로 변경하기

객체 복사 뒤 어떤 참조가 분리되고 어떤 참조가 남는지 실제 변경 결과로 추적한다.

실습 구성과 전제

  • 두 사진은 서로 다른 중첩 리스트를 사용한 복사 사례다. 하나의 동일 리스트를 이어 조작한 화면이 아니다.
  • 의 기본 와 copy 모듈을 대상으로 한다. 아래 재구성 코드는 별도로 실행할 수 있는 작은 예시이며 화면의 객체 주소 숫자를 그대로 재현하지 않는다.
  1. 바깥 목록의 항목 교체를 확인한다

    • a.copy()는 새 바깥 리스트를 만들고 원소에 대한 참조를 복사한다. a[0]을 100으로 바꾸는 연산은 a의 첫 번째 칸이 가리키는 객체를 교체한다.
    • b의 첫 번째 칸은 그대로이므로 바깥 리스트가 독립되었다는 사실을 확인할 수 있다. 이것만으로 중첩 객체까지 모두 복사되었다고 결론내리면 안 된다.
    a = [1, 2, [3, 4]]
    b = a.copy()
    print(a is b, a[2] is b[2])  # False True
    a[0] = 100
    print(a, b)  # [100, 2, [3, 4]] [1, 2, [3, 4]]

    동일성 비교와 바깥 항목 교체를 분리한 재구성 코드와 예상 출력이다.

    python · 코드와 실행 결과 16원본 크기로 보기 ↗
    • a=[1,2,[3,4]]에서 b=a.copy()를 만든 뒤 a[0]=100을 수행한다.
    • 그 결과 a의 첫 원소만 100으로 바뀌고 b의 첫 원소는 1로 남아 있다.
    확인 기준

    바깥 리스트의 동일성은 a is b로, 안쪽 리스트의 동일성은 a[2] is b[2]로 각각 비교한다.

  2. 공유된 안쪽 객체의 변경을 추적한다

    • a[2][0]을 바꾸면 먼저 a[2]가 가리키는 내부 리스트를 찾고 그 객체의 첫 칸을 변경한다. b[2]도 같은 내부 리스트를 참조하므로 b로 읽어도 변경이 보인다.
    • 이는 b가 a의 모든 변경을 자동으로 따라가는 기능이 아니다. 두 경로가 같은 가변 객체에 도달하기 때문에 나타나는 결과다.
    a[2][0] = "Hello"
    print(a)  # [100, 2, ['Hello', 4]]
    print(b)  # [1, 2, ['Hello', 4]]

    바로 앞 재구성 코드에 이어 실행했을 때의 예상 출력이다.

    확인 기준

    a[2] 자체를 새 리스트로 교체하는 연산과 a[2][0]을 변경하는 연산을 구분한다.

  3. deepcopy는 내부 객체도 대조해서 검증한다

    • 두 번째 사진에서는 copy.copy와 로 만든 바깥 객체의 id가 다르게 나온다. 바깥 id 세 개만으로 내부 객체가 분리되었다는 사실까지 직접 확인할 수는 없다.
    • 기본 중첩 에서는 로 내부 도 독립 복제된다. 아래처럼 안쪽 동일성과 변경 후 결과를 비교하면 와 깊은 복사의 차이가 드러난다.
    import copy
    x = [["cat"]]
    y = copy.copy(x)
    z = copy.deepcopy(x)
    x[0].append("lion")
    print(y, z)  # [['cat', 'lion']] [['cat']]
    print(x[0] is y[0], x[0] is z[0])  # True False

    별도의 작은 리스트로 재구성한 예시다. 메모리 주소 숫자 대신 동일성과 값 변화를 비교한다.

    python · 코드와 실행 결과 18원본 크기로 보기 ↗
    • copy.copy(a)와 copy.deepcopy(a)를 각각 b와 c에 대입한다.
    • id(a), id(b), id(c)의 출력 값이 모두 다르지만 내부 리스트의 id는 이 사진에서 직접 출력하지 않는다.
    확인 기준

    사용자 정의 객체·외부 자원에는 별도의 복사 의미가 있을 수 있다. 여기의 결과는 기본 예시로 한정한다.

실습에서 확인한 동작 원리

  • 는 바깥 컨테이너를 분리하지만 안쪽 가변 객체의 참조는 공유할 수 있다.
  • 복사는 id 하나보다 참조 경로와 변경 연산을 함께 추적할 때 정확히 설명된다.

설정·출력 추가로 확인하기 60장

아래 화면을 펼쳐 위 실습의 설정값, 명령과 확인 기준을 비교해 보세요. 각 묶음은 별도 실습 기록입니다.

실습 화면 묶음 01 2장
7/30 - 발표 · 코드와 실행 결과 8원본 크기로 보기 ↗
  • 「시나리오」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
7/30 - 발표 · 코드와 실행 결과 9원본 크기로 보기 ↗
  • 「시나리오」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
실습 화면 묶음 02 1장
Python 객체와 참조 구조원본 크기로 보기 ↗
  • 학습 원본 · Python 객체와 참조 구조의 해당 절에 수록된 화면·도식이다.
실습 화면 묶음 03 1장
Python 객체와 참조 구조원본 크기로 보기 ↗
  • 학습 원본 · Python 객체와 참조 구조의 해당 절에 수록된 화면·도식이다.
실습 화면 묶음 04 1장
Python 객체와 참조 구조원본 크기로 보기 ↗
  • 학습 원본 · Python 객체와 참조 구조의 해당 절에 수록된 화면·도식이다.
실습 화면 묶음 05 54장
python · 실습·개념 참고 화면 13원본 크기로 보기 ↗
  • 「operator」 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 실습·개념 참고 화면 3원본 크기로 보기 ↗
  • 「논리형 데이터 타입 (True, False)」 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 실습·개념 참고 화면 6원본 크기로 보기 ↗
  • 「ASCII TABLE」 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 실습·개념 참고 화면 9원본 크기로 보기 ↗
  • 「데이터 제거」 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 1원본 크기로 보기 ↗
  • 「정수형 데이터 타입」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 1원본 크기로 보기 ↗
  • 「dictionary」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 1원본 크기로 보기 ↗
  • 「List」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 10원본 크기로 보기 ↗
  • 「3. 삭제 ( pop , popitem , clear )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 10원본 크기로 보기 ↗
  • 「위치 찾기 및 개수 세기」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 10원본 크기로 보기 ↗
  • 「Indexing 과 Slicing」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 11원본 크기로 보기 ↗
  • 「위치 찾기 및 개수 세기」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 11원본 크기로 보기 ↗
  • 「4. 복사 ( copy )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 11원본 크기로 보기 ↗
  • 「escape 문자 (특수 문자)」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 12원본 크기로 보기 ↗
  • 「promotion vs casting」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 12원본 크기로 보기 ↗
  • 「위치 찾기 및 개수 세기」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 12원본 크기로 보기 ↗
  • 「operator」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 13원본 크기로 보기 ↗
  • 「promotion vs casting」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 13원본 크기로 보기 ↗
  • 「정렬 및 변형」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 14원본 크기로 보기 ↗
  • 「bool()」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 14원본 크기로 보기 ↗
  • 「정렬 및 변형」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 15원본 크기로 보기 ↗
  • 「정렬 및 변형」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 17원본 크기로 보기 ↗
  • 「copy (shallow, deep copy)」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 19원본 크기로 보기 ↗
  • 「EX」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 2원본 크기로 보기 ↗
  • 「dir()」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 2원본 크기로 보기 ↗
  • 「1. 조회 및 정보 확인 ( get , keys , values , items )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 2원본 크기로 보기 ↗
  • 「실수형 데이터 타입」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 20원본 크기로 보기 ↗
  • 「EX」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 21원본 크기로 보기 ↗
  • 「EX」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 22원본 크기로 보기 ↗
  • 「EX」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 23원본 크기로 보기 ↗
  • 「is 문법」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 24원본 크기로 보기 ↗
  • 「null」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 29원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 3원본 크기로 보기 ↗
  • 「데이터 추가 및 확장」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 3원본 크기로 보기 ↗
  • 「1. 조회 및 정보 확인 ( get , keys , values , items )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 36원본 크기로 보기 ↗
  • 「python」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 37원본 크기로 보기 ↗
  • 「python」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 38원본 크기로 보기 ↗
  • 「python」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 39원본 크기로 보기 ↗
  • 「python」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 4원본 크기로 보기 ↗
  • 「데이터 추가 및 확장」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 4원본 크기로 보기 ↗
  • 「1. 조회 및 정보 확인 ( get , keys , values , items )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 4원본 크기로 보기 ↗
  • 「논리형 데이터 타입 (True, False)」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 5원본 크기로 보기 ↗
  • 「1. 조회 및 정보 확인 ( get , keys , values , items )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 5원본 크기로 보기 ↗
  • 「데이터 추가 및 확장」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 5원본 크기로 보기 ↗
  • 「Array」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 6원본 크기로 보기 ↗
  • 「2. 추가 및 수정 ( update , setdefault , fromkeys )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 6원본 크기로 보기 ↗
  • 「데이터 추가 및 확장」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 7원본 크기로 보기 ↗
  • 「2. 추가 및 수정 ( update , setdefault , fromkeys )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 7원본 크기로 보기 ↗
  • 「ASCII TABLE」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 7원본 크기로 보기 ↗
  • 「데이터 제거」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 8원본 크기로 보기 ↗
  • 「ASCII TABLE」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 8원본 크기로 보기 ↗
  • 「데이터 제거」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 8원본 크기로 보기 ↗
  • 「2. 추가 및 수정 ( update , setdefault , fromkeys )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 9원본 크기로 보기 ↗
  • 「3. 삭제 ( pop , popitem , clear )」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 9원본 크기로 보기 ↗
  • 「Indexing 과 Slicing」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
실습 화면 묶음 06 1장
파이썬 (개인) · 코드와 실행 결과 1원본 크기로 보기 ↗
  • 문서 문맥에 삽입된 원본 코드와 실행 결과이다. Python 객체·자료형과 복사 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
동작·명령 참고 문서
원리 심화

06컨테이너 복제·키 비교·가변성의 내부 경계

  • 리스트 반복 연산은 요소 객체를 재귀적으로 복제하지 않는다. [value] * 3은 같은 value를 가리키는 세 칸을 만들 수 있어 컨테이너 모양과 객체 개수를 구분해야 한다.
  • 와 은 해시 가능성을 요구한다. 동등한 키는 같은 해시값을 가져야 하며 해시값은 키로 사용되는 동안 안정적이어야 한다.
  • 에서 1 == True == 1.0은 참이고 이 값들은 같은 키를 가리킬 수 있다. 자료형이 다르다고 항상 서로 다른 키 슬롯을 차지한다고 가정하지 않는다.
  • 자체는 요소 참조를 바꿀 수 없지만 내부에 가 있으면 그 는 바뀔 수 있다. 또한 그런 은 내부 요소가 해시 불가능하므로 키로 사용할 수 없다.
  • 바깥 컨테이너만 분리할지 내부 가변 객체도 분리할지는 데이터 소유 관계로 정한다. 변경을 공유해야 하는 상태까지 무조건 깊은 복사하면 설계 의도가 바뀔 수 있다.
예제로 추적

073행처럼 보이는 리스트가 하나의 행을 공유할 때

  • grid = [[]] * 3은 안쪽 빈 리스트 한 개를 만들고 그 참조를 세 칸에 반복한다. 바깥 리스트 길이는 3이지만 독립적인 행이 세 개인 것은 아니다.
  • grid[0].append(7)은 첫 칸을 새 값으로 교체하지 않고 그 칸이 가리키는 내부 리스트를 변경한다. 세 칸 모두 같은 객체를 보기 때문에 모두 [7]처럼 보인다.
  • grid[1] = [9]는 둘째 칸의 참조만 새 리스트로 바꾼다. 그 뒤 grid[0].append(8)을 하면 첫째·셋째 칸만 함께 바뀐다.
  • [[] for _ in range(3)]은 반복마다 새 리스트를 평가하므로 세 내부 객체가 독립적이다. comprehension이라는 표기 자체보다 생성 표현식의 실행 횟수가 원인이다.
  • d={1:"first"}; d[True]="second"의 길이는 1이다. 기존과 동등한 키에 새 값을 저장한 것이므로 두 개의 서로 다른 설정 항목처럼 사용하면 값이 덮인다.
grid = [[]] * 3
grid[0].append(7)
print(grid)  # [[7], [7], [7]]
grid[1] = [9]
grid[0].append(8)
print(grid)  # [[7, 8], [9], [7, 8]]
independent = [[] for _ in range(3)]
independent[0].append(7)
print(independent)  # [[7], [], []]

실행하지 않은 교육 예시. 주석의 출력은 언어 규칙으로 추적한 예상값이다.

검증·예외

08중첩 자료의 오류를 동일성·형식·소진 상태로 구분하기

  • 두 값의 출력이 같아도 같은 객체인지는 별개다. 변경 전후의 값과 a is b, a[0] is b[0]처럼 문제가 생기는 내부 깊이의 동일성을 함께 읽는다.
  • unhashable type 오류는 단순히 문법이 틀렸다는 뜻이 아니다. · 같은 가변 자료를 키로 사용했는지, 내부에 그런 자료가 들어 있는지 확인한다.
  • 두 번째 순회에서 결과가 비면 원본 자료가 사라진 것인지 같은 를 끝까지 소비한 것인지 구분한다. iter()는 새 반복자를 만들지만 이미 소진한 를 로 바꿔도 새 자료가 생기지 않는다.
  • .sort()나 append()는 리스트를 변경하고 None을 반환한다. result = items.sort() 이후 result가 None인 현상을 정렬 실패나 빈 리스트로 처리하지 않는다.
  • 설정 키에 숫자·논리값·문자열을 섞어 받으면 "1"과 1, True의 구별이 달라진다. 입력 규격에 맞는 자료형으로 검증한 뒤 컨테이너에 넣는다.

09반복과 변환

  • 인덱스와 슬라이스는 시퀀스에서 범위를 선택하고, 반복문은 원소를 순서대로 처리한다.
  • 컴프리헨션은 변환과 조건을 간결하게 표현하지만 복잡한 부작용을 숨기는 용도로 사용하지 않는다.
    • 문자열 숫자와 실제 숫자도 구분하고 변환 실패를 고려한다.
original = [["alpha"]]
shallow = original.copy()
print(original is shallow)
print(original[0] is shallow[0])

바깥 리스트는 다르지만 내부 리스트를 공유하는 를 조회한다.

10두 이름과 얕은 복사를 그림으로 추적하기

  • a = [["x"]]는 바깥 리스트와 그 안의 리스트를 만든다.
  • b = a는 새 리스트를 복제하지 않고 b가 a와 같은 바깥 객체를 가리키게 한다.
  • c = a.copy()는 바깥 리스트만 새로 만들고 원소가 가리키는 내부 리스트는 공유한다.
    • 따라서 a is b는 참이고 a is c는 거짓이지만 a[0] is c[0]은 참이다.
  • 눈에 보이는 문자열 값이 같다는 사실과 객체가 하나인지 둘인지라는 사실을 구분해야 변경의 전파를 설명할 수 있다.
  • 이 상태에서 a[0].append("y")를 하면 내부 리스트 하나가 바뀌므로 a·b·c에서 모두 [["x", "y"]]가 보인다.
  • 반면 c의 바깥 리스트에 새 원소를 추가하는 동작은 c의 바깥 객체만 바꾸므로 a의 길이를 늘리지 않는다.
  • b = []처럼 이름을 다른 리스트에 재대입하는 것도 기존 객체의 내용을 지우는 동작과 다르다.
  • 깊은 복사가 필요한지 판단하기 전에 어떤 층의 객체를 변경하는지, 그 객체를 누가 공유하는지부터 그려 본다.
  1. a = [["x"]]를 실행하면 바깥 리스트 L1과 내부 리스트 I가 만들어진다. a는 L1을, L1의 첫 요소는 I를 참조한다.
  2. b = a는 리스트를 복사하지 않는다. a와 b가 같은 바깥 리스트 L1을 참조한다.
  3. c = a.copy()는 새 바깥 리스트 L2를 만들지만 그 첫 요소는 여전히 I를 참조한다. L1과 L2는 다른 객체다.
  4. a[0].append("y")는 I를 변경한다. a·b·c에서 내부 목록을 보면 모두 ["x", "y"]이며, 이것이 바깥 리스트까지 같은 객체가 됐다는 뜻은 아니다.

설명과 함께 보는 보조 도식

경로와 상태

이름 셋과 공유된 내부 리스트

화살표는 값 복사가 아니라 객체 참조다. L1·L2·I는 설명용 객체 이름이며 실제 메모리 주소가 아니다.

참조참조참조원소 0원소 0원래 바깥 리스트 참조이름 a대입 뒤 a와 같은 객체 참조이름 b얕은 복사로 만든 바깥 리스트 참조이름 ca와 b가 공유하는 리스트바깥 L1copy 단계에서 생성되는 별도 리스트바깥 L2L1·L2가 함께 참조하는 리스트내부 I
전체 단계를 한 번에 읽기
  1. 01

    a 생성

    바깥 리스트와 내부 리스트를 만든다.

    a
    [["x"]]
  2. 02

    b = a

    새 바깥 객체 없이 같은 L1을 가리킨다.

    a is b
    True
  3. 03

    c = a.copy()

    L2는 새 객체지만 내부 I는 공유한다.

    a is c
    False
    a[0] is c[0]
    True
  4. 04

    내부 append

    a[0]을 통해 I에 y를 추가하면 세 이름에서 변화가 보인다.

    a, b, c
    모두 [["x", "y"]]

화살표: 진행 또는 참조 방향 · 파란색: 현재 단계에서 다루는 관계

학습 모식도. 참조 또는 연결 관계를 표현한다. 흐리게 표시된 요소는 해당 단계에서 아직 사용되지 않거나 제외된 요소다.

11값은 같은데 동작이 다른 상황 조사하기

  • 출력된 내용이 같아도 type과 is 결과는 다를 수 있다.
  • ==는 객체가 정의한 동등성 비교이므로 같은 숫자 모양이나 컬렉션 내용이 보인다는 사실만으로 동일 객체를 뜻하지 않는다.
  • None은 값이 없음을 나타내는 특별한 객체로 is None 형태로 확인한다.
  • 작은 정수나 문자열의 내부 재사용 때문에 우연히 is가 참인 사례를 값 비교 규칙으로 일반화하지 않는다.
  • 자료형을 확인하면 문자열 "3"과 정수 3의 다른 연산 결과도 설명할 수 있다.
  • .append의 반환값과 변경된 리스트를 각각 관찰하면 제자리 연산의 계약을 확인할 수 있다.
  • 반환값이 None이라고 작업이 실패한 것은 아니며 결과를 다시 이름에 대입하면 원래 리스트를 놓칠 수 있다.
  • ·에는 해시 가능한 키·원소가 필요하고 안에 리스트가 있으면 이라고 항상 해시 가능한 것도 아니다.
  • str에서 bytes로 바뀌는 지점은 인코딩을 명시한다.
  • 자동화 입력을 처리할 때는 자료형·가변성·공유 범위를 함께 확인해야 호출 사이의 예상치 못한 변경을 찾을 수 있다.
구성·관찰 대상확인 정보판단할 차이
이름현재 가리키는 객체대입과 객체 복제
바깥 컨테이너객체 동일성과 길이의 새 객체
내부 원소중첩 객체 참조공유된 내부 변경
연산 결과반환값과 변경 내용None 반환과 실패의 차이
READ · OBSERVE · CONNECT

함께 보는 학습 자료

아래 공식 자료를 본문의 개념·예제와 함께 읽어 보세요. 자료별로 확인할 위치와 관찰할 내용을 적었습니다.

공식 가이드

얕은 복사와 깊은 복사의 객체 관계

모듈 앞부분의 assignment·shallow copy·deep copy 설명에 중첩 리스트 예제를 대입한다. 바깥 컨테이너를 복사했을 때 내부 객체가 공유되는 이유를 확인한다.

읽으면서 확인할 것
  • 대입이 객체 복사를 수행하지 않는다는 뜻
  • 의 새 바깥 객체와 공유된 내부 참조
  • 의 memo가 순환·반복 참조를 처리하는 역할
학습 확인

이해했는지 확인하기

  • 값 비교와 객체 동일성 비교를 구분한다.
  • 가변 객체가 여러 이름에서 공유되는지 확인한다.
  • str·bytes와 얕은 복사의 경계를 설명한다.

함께 확인할 공식 자료