일반적인 Python 데이터 구조(가이드)1

2023. 12. 28. 18:14python/basic

데이터 구조는 프로그램을 빌드하는 데 사용되는 기본 구조입니다. 각 데이터 구조는 사용 사례에 따라 효율적으로 액세스할 수 있도록 데이터를 구성하는 특정 방법을 제공합니다. Python은 표준 라이브러리에 광범위한 데이터 구조 세트를 제공합니다.

그러나 Python의 명명 규칙은 다른 언어에서 볼 수 있는 것과 같은 수준의 명확성을 제공하지 않습니다.

  • 어떤 일반적인 추상 데이터 유형이 Python 표준 라이브러리에 내장되어 있나요?
  • 가장 일반적인 추상 데이터 유형이 Python의 이름 지정 체계에 매핑되는 방식
  • 추상 데이터 유형을 다양한 알고리즘에서 실용화 활용하는 방법

Dictionaries, Maps, and Hash Tables

dict: 즐겨찾는 사전

In [ ]:
phonebook = {
    "bob": 7387,
    "alice": 3719,
    "jack": 7052,
}

squares = {x: x * x for x in range(6)}

phonebook["alice"]
In [ ]:
squares

collections.OrderedDict: 키 삽입 순서를 기억하세요

Python에는 추가된 키의 삽입 순서를 기억하는 특수한 dict 하위 클래스인 collections.OrderedDict가 포함되어 있습니다.

In [ ]:
import collections
d = collections.OrderedDict(one=1, two=2, three=3)

d
In [ ]:
d["four"] = 4
d
In [ ]:
d.keys()

collections.defaultdict: 누락된 키에 대한 기본값 반환

In [ ]:
from collections import defaultdict
dd = defaultdict(list)

# 누락된 키에 액세스하면 해당 키가 생성되고
# 기본 팩토리를 사용하여 초기화합니다.
# 즉, 이 예에서는 list()입니다:

dd["dogs"].append("Rufus")
dd["dogs"].append("Kathrin")
dd["dogs"].append("Mr Sniffles")

dd["dogs"]

collections.ChainMap: 단일 매핑으로 여러 사전 검색

collections.ChainMap 데이터 구조는 여러 사전을 단일 매핑으로 그룹화합니다. 조회는 키를 찾을 때까지 기본 매핑을 하나씩 검색합니다. 삽입, 업데이트 및 삭제는 체인에 추가된 첫 번째 매핑에만 영향을 미칩니다.

In [ ]:
from collections import ChainMap
dict1 = {"one": 1, "two": 2}
dict2 = {"three": 3, "four": 4}
chain = ChainMap(dict1, dict2)

chain
In [ ]:
# ChainMap은 키를 찾을 때까지(또는 실패할 때까지) 
# 체인의 각 컬렉션을 왼쪽에서 오른쪽으로 검색합니다.

chain["three"]
In [ ]:
chain["one"]
In [ ]:
chain["missing"]

types.MappingProxyType: 읽기 전용 사전을 만들기 위한 래퍼

MappingProxyType은 예를 들어, 이 객체에 대한 쓰기 액세스를 방해하면서 클래스나 모듈에서 내부 상태를 전달하는 사전을 반환하려는 경우 이 도움이 될 수 있습니다. MappingProxyType을 사용하면 먼저 사전의 전체 복사본을 만들지 않고도 이러한 제한 사항을 적용할 수 있습니다.

In [ ]:
from types import MappingProxyType
writable = {"one": 1, "two": 2}
read_only = MappingProxyType(writable)

# 프락시는 read-only:
read_only["one"]
In [ ]:
read_only["one"] = 23
In [ ]:
# 원본에 대한 업데이트가 프락시에 반영됩니다.:
writable["one"] = 42
read_only

배열 데이터 구조

배열은 인덱스를 기반으로 각 요소를 효율적으로 찾을 수 있는 고정 크기 데이터 레코드로 구성됩니다. 배열은 인접한 메모리 블록에 정보를 저장하기 때문에 연속 데이터 구조로 간주됩니다( 연결 목록과 같은 데이터 구조). 성능 측면에서 요소의 인덱스가 주어지면 배열에 포함된 요소를 찾는 것이 매우 빠릅니다. 적절한 배열 구현은 이 경우에 대해 일정한 O(1) 액세스 시간을 보장합니다.

list: 가변 동적 배열

Lists은 핵심 Python 언어의 일부입니다. 이름에도 불구하고 Python의 Lists은 배후에서 동적 배열으로 구현됩니다.
이는 list을 통해 요소를 추가하거나 제거할 수 있으며 list은 메모리를 할당하거나 해제하여 이러한 요소를 보유하는 백업 저장소를 자동으로 조정한다는 의미입니다.
Python list은 임의의 요소를 보유할 수 있습니다. 함수를 포함하여 Python에서는 모든 것이 객체입니다. 따라서 다양한 종류의 데이터 유형을 혼합하고 일치시켜 모두 단일 list에 저장할 수 있습니다.
이는 강력한 기능일 수 있지만 동시에 여러 데이터 유형을 지원한다는 것은 데이터가 일반적으로 덜 촘촘하게 압축된다는 것을 의미한다는 단점이 있습니다. 결과적으로 전체 구조가 더 많은 공간을 차지합니다.

In [ ]:
arr = ["one", "two", "three"]
arr[0]
In [ ]:
# 목록에는 좋은 표현이 있습니다.:
arr
In [ ]:
# list은 변경 가능합니다.:
arr[1] = "hello"
arr
In [ ]:
del arr[1]
arr
In [ ]:
# ㅣist는 임의의 데이터 유형을 보유할 수 있습니다.:
arr.append(23)
arr

tuple: 불변 컨테이너

list와 마찬가지로 tuple은 Python 핵심 언어의 일부입니다. 그러나 list와 달리 Python의 tuple 객체는 변경할 수 없습니다. 즉, 요소를 동적으로 추가하거나 제거할 수 없습니다. tuple의 모든 요소는 생성 시 정의되어야 합니다.

tuple은 임의의 데이터 유형의 요소를 보유할 수 있는 또 다른 데이터 구조입니다. 이러한 유연성을 갖는 것은 강력하지만, 이는 또한 데이터가 형식화된 배열에 있을 때보다 덜 촘촘하게 압축되어 있음을 의미합니다.

In [ ]:
arr = ("one", "two", "three")
arr[0]
In [ ]:
# tuples에는 좋은 표현이 있습니다.:
arr
In [ ]:
# Tuples는 변경이 가능하지 않습니다.:
arr[1] = "hello"
In [ ]:
del arr[1]
In [ ]:
# tuples은 임의의 데이터 유형을 보유할 수 있습니다.:
# (요소를 추가하면 tuples의 복사본이 생성)
arr + (23,)

array.array: 기본형 배열

array.array 클래스로 생성된 배열은 변경 가능하고 한 가지 중요한 차이점을 제외하고 목록과 유사하게 동작합니다. 단일 데이터 유형으로 제한되는 유형이 지정된 배열입니다.

이러한 제약으로 인해 array.array 요소가 많은 객체는 lists나 tuples보다 공간 효율적입니다. 여기에 저장된 요소는 촘촘하게 압축되어 있으므로 동일한 유형의 요소를 많이 저장해야 하는 경우 유용할 수 있습니다.

또한 배열은 일반 목록과 동일한 여러 메서드를 지원하므로 애플리케이션 코드를 다르게 변경하지 않고도 배열을 즉시 대체하여 사용할 수 있습니다.

In [ ]:
import array
arr = array.array("f", (1.0, 1.5, 2.0, 2.5))

arr[1]
In [ ]:
# Arrays에는 좋은 표현이 있습니다.:
arr
In [ ]:
# Arrays는 변경 가능합니다.:
arr[1] = 23.0
arr
In [ ]:
del arr[1]
arr
In [ ]:
arr.append(42.0)
arr
In [ ]:
# Arrays는 유형이 지정 되었습니다.:
arr[1] = "hello"
arr

str: 유니코드 문자의 불변 배열

Python 3.x는 str 객체를 사용하여 텍스트 데이터를 유니코드 문자의 불변 시퀀스로 저장합니다. 실제로 이는 str 가 불변의 문자 배열임을 의미합니다. 이상하게도 이는 재귀 데이터 구조이기도 합니다. 문자열의 각 문자 자체는 길이가 1인 str 개체입니다.

문자열 객체는 촘촘하게 구성되어 있고 단일 데이터 유형에 특화되어 있기 때문에 공간 효율적입니다. 유니코드 텍스트를 저장하는 경우 문자열을 사용해야 합니다.

Python에서는 문자열이 불변이기 때문에 문자열을 수정하려면 수정된 복사본을 만들어야 합니다. 변경 가능한 문자열에 가장 가까운 것은 개별 문자를 목록 안에 저장하는 것입니다.

In [ ]:
arr = "abcd"
arr[1]
In [ ]:
arr
In [ ]:
# Strings은 변경 불가합니다.:
arr[1] = "e"
In [ ]:
del arr[1]
In [ ]:
# 문자열을 list로 풀어서
# 변경 가능한 표현을 얻을 수 있습니다.
list("abcd")
In [ ]:
"".join(list("abcd"))
In [ ]:
# 문자열은 재귀적인 데이터 구조입니다.:
type("abc")
In [ ]:
type("abc"[0])

bytes: 단일 바이트의 불변 배열

bytes 객체는 단일 바이트의 불변 시퀀스이거나 0 ≤ x ≤ 255 범위의 정수입니다. 개념적으로 bytes 객체는 str 객체와 유사하며 변경할 수 없는 바이트 배열로 생각할 수도 있습니다.

In [ ]:
arr = bytes((0, 1, 2, 3))
arr[1]
In [ ]:
# Bytes literals have their own syntax:
arr
In [ ]:
arr = b"\x00\x01\x02\x03"
In [ ]:
# 유효한 '바이트'만 허용됩니다.:
bytes((0, 300))
In [ ]:
# Bytes are immutable:
arr[1] = 23
In [ ]:
del arr[1]

bytearray: 단일 바이트의 가변 배열

bytearray 유형은 0 ≤ x ≤ 255 범위의 변경 가능한 정수 시퀀스입니다. bytearray 개체는 bytes 개체와 밀접하게 관련되어 있지만 주요 차이점은 bytearray을 자유롭게 수정할 수 있습니다. 덮어쓸 수 있습니다. 요소를 제거하거나, 기존 요소를 제거하거나, 새 요소를 추가하세요. bytearray 개체는 그에 따라 늘어나고 줄어듭니다.

In [ ]:
arr = bytearray((0, 1, 2, 3))
arr[1]
In [ ]:
# The bytearrapr:
arr
In [ ]:
# Bytearrays are mutable:
arr[1] = 23
arr
In [ ]:
arr[1]
In [ ]:
# Bytearrays can grow and shrink in size:
del arr[1]
arr
In [ ]:
arr.append(42)
arr
In [ ]:
# Bytearrays can only hold `bytes`
# (integers in the range 0 <= x <= 255)
arr[1] = "hello"
In [ ]:
arr[1] = 300
In [ ]:
# Bytearrays can be converted back into bytes objects:
# (This will copy the data)
bytes(arr)

Records, Structs, and Data Transfer Objects

dict: 단순 데이터 개체

파이썬에서는 dict를 레코드 데이터 유형이나 데이터 객체로 사용하는 것이 가능합니다. dict은 사전 리터럴 형식으로 언어에 자체 구문 설탕이 내장되어 있으므로 Python에서 쉽게 만들 수 있습니다. dict 구문은 간결하고 입력하기 매우 편리합니다.

In [ ]:
car1 = {
    "color": "red",
    "mileage": 3812.4,
    "automatic": True,
}

car2 = {
    "color": "blue",
    "mileage": 40231,
    "automatic": False,
}

# Dicts have a nice repr:
car2
In [ ]:
# Get mileage:
car2["mileage"]
In [ ]:
# Dicts are mutable:
car2["mileage"] = 12
car2["windshield"] = "broken"
car2
In [ ]:
# No protection against wrong field names,
# or missing/extra fields:
car3 = {
    "colr": "green",
    "automatic": False,
    "windshield": "broken",
}
In [ ]:
car3

tuple: 불변의 객체 그룹

Python의 튜플은 임의의 개체를 그룹화하기 위한 간단한 데이터 구조입니다. 튜플은 변경할 수 없습니다. 일단 생성되면 수정할 수 없습니다.

성능 측면에서 튜플은 CPython의 목록보다 약간 적은 메모리를 차지합니다. 또한 구성 속도도 더 빠릅니다.

아래 바이트코드 디스어셈블리에서 볼 수 있듯이 튜플 상수를 구성하려면 단일 LOAD_CONST opcode가 필요하지만, 동일한 내용으로 목록 객체를 구성하려면 몇 가지 추가 작업이 필요합니다.

In [ ]:
import dis
dis.dis(compile("(23, 'a', 'b', 'c')", "", "eval"))
In [ ]:
dis.dis(compile("[23, 'a', 'b', 'c']", "", "eval"))

일반 튜플의 잠재적인 단점은 여기에 저장한 데이터를 정수 인덱스를 통해 액세스해야만 가져올 수 있다는 것입니다. 튜플에 저장된 개별 속성에는 이름을 지정할 수 없습니다. 이는 코드 가독성에 영향을 줄 수 있습니다.

또한 튜플은 항상 임시 구조입니다. 두 튜플에 동일한 수의 필드와 동일한 속성이 저장되어 있는지 확인하기가 어렵습니다.

이렇게 하면 필드 순서가 뒤섞이는 등의 실수로 인한 버그가 쉽게 발생할 수 있습니다. 따라서 튜플에 저장되는 필드 수를 가능한 한 낮게 유지하는 것이 좋습니다.

In [ ]:
# Fields: color, mileage, automatic
car1 = ("red", 3812.4, True)
car2 = ("blue", 40231.0, False)

# Tuple instances have a nice repr:
car1
In [ ]:
car2
In [ ]:
# Get mileage:
car2[1]
In [ ]:
# Tuples are immutable:
car2[1] = 12
In [ ]:
# No protection against missing or extra fields
# or a wrong order:
car3 = (3431.5, "green", True, "silver")
In [ ]:
car3

사용자 정의 클래스 작성: 더 많은 작업, 더 많은 제어

사용자 정의 클래스를 작성하는 것은 메서드를 사용하여 레코드 개체에 비즈니스 논리와 동작을 추가할 때마다 훌륭한 옵션입니다. 그러나 이는 이러한 개체가 기술적으로 더 이상 일반 데이터 개체가 아님을 의미합니다.

In [ ]:
class Car:
    def __init__(self, color, mileage, automatic):
        self.color = color
        self.mileage = mileage
        self.automatic = automatic

car1 = Car("red", 3812.4, True)
car2 = Car("blue", 40231.0, False)

# Get the mileage:
car2.mileage
In [ ]:
# Classes are mutable:
car2.mileage = 12
car2.windshield = "broken"

car2
In [ ]:
# String representation is not very useful
# (must add a manually written __repr__ method):
car1

dataclasses.dataclass: Python 3.7+ 데이터 클래스

일반 Python 클래스 대신 데이터 클래스를 작성하면 객체 인스턴스가 몇 가지 유용한 기능을 즉시 사용할 수 있어 일부 입력 및 수동 구현 작업을 줄일 수 있습니다.

  • 인스턴스 변수를 정의하는 구문은 init() 메소드를 구현할 필요가 없으므로 더 짧습니다.
  • 데이터 클래스의 인스턴스는 자동 생성된 repr() 메소드를 통해 멋진 문자열 표현을 자동으로 얻습니다.
  • 인스턴스 변수는 유형 주석을 허용하므로 데이터 클래스가 어느 정도 자체 문서화됩니다. 유형 주석은 별도의 유형 검사 도구 없이는 적용되지 않는 힌트일 뿐이라는 점을 명심하세요.
In [ ]:
from dataclasses import dataclass
@dataclass
class Car:
    color: str
    mileage: float
    automatic: bool

car1 = Car("red", 3812.4, True)

# Instances have a nice repr:
car1
In [ ]:
# Accessing fields:
car1.mileage
In [ ]:
# Fields are mutable:
car1.mileage = 12
car1.windshield = "broken"

car1
In [ ]:
# Type annotations are not enforced without
# a separate type checking tool like mypy:
Car("red", "NOT_A_FLOAT", 99)

collections.namedtuple: 편리한 데이터 객체

namedtuple 객체는 일반 튜플과 마찬가지로 불변입니다. 즉, namedtuple 인스턴스가 생성된 후에는 새 필드를 추가하거나 기존 필드를 수정할 수 없습니다. namedtuple객체는 내부적으로 일반 Python 클래스로 구현됩니다. 메모리 사용량에 있어서도 일반 클래스보다 우수하고 일반 튜플만큼 메모리 효율적입니다.

In [ ]:
from collections import namedtuple
from sys import getsizeof

p1 = namedtuple("Point", "x y z")(1, 2, 3)
p2 = (1, 2, 3)

getsizeof(p1)
In [ ]:
getsizeof(p2)
In [ ]:
# sys.getsizeof()는 메모리에 실제로 올라가는 크기입니다. ''와 같이 빈 문자열도 49바이트가 차지하게 됩니다.
In [ ]:
from collections import namedtuple
Car = namedtuple("Car" , "color mileage automatic")
car1 = Car("red", 3812.4, True)

# Instances have a nice repr:
car1
In [ ]:
# Accessing fields:
car1.mileage
In [ ]:
# Fields are immtuable:
car1.mileage = 12
In [ ]:
car1.windshield = "broken"

typing.NamedTuple: 향상된 네임튜플

In [ ]:
from typing import NamedTuple

class Car(NamedTuple):
    color: str
    mileage: float
    automatic: bool

car1 = Car("red", 3812.4, True)

# Instances have a nice repr:
car1
In [ ]:
# Accessing fields:
car1.mileage
In [ ]:
# Fields are immutable:
car1.mileage = 12
In [ ]:
car1.windshield = "broken"
In [ ]:
# Type annotations are not enforced without
# a separate type checking tool like mypy:
Car("red", "NOT_A_FLOAT", 99)

struct.Struct: 직렬화된 C 구조체

struct.Struct 클래스는 Python 값과 Python bytes 객체로 직렬화된 C 구조체 사이를 변환합니다. 예를 들어, 파일에 저장되어 있거나 네트워크 연결에서 들어오는 바이너리 데이터를 처리하는 데 사용될 수 있습니다.

직렬화된 구조체는 순수하게 Python 코드 내에서 처리되는 데이터 객체를 나타내는 데 거의 사용되지 않습니다. 이는 Python 코드에서만 사용되는 메모리에 데이터를 보관하는 방법이 아니라 주로 데이터 교환 형식으로 사용됩니다.

In [ ]:
from struct import Struct
MyStruct = Struct("i?f")
data = MyStruct.pack(23, False, 42.0)

# All you get is a blob of data:
data
In [ ]:
# Data blobs can be unpacked again:
MyStruct.unpack(data)

types.SimpleNamespace: 멋진 속성 액세스

Python에서 데이터 객체를 구현하기 위한 약간 모호한 선택이 하나 더 있습니다: types.SimpleNamespace. 이 클래스는 Python 3.3에 추가되었으며 해당 네임스페이스에 대한 속성 액세스를 제공합니다.

이름에서 알 수 있듯이 SimpleNamespace은 간단합니다! 기본적으로 속성 액세스를 허용하고 멋지게 인쇄하는 사전입니다. 속성은 자유롭게 추가, 수정 및 삭제할 수 있습니다.

In [ ]:
from types import SimpleNamespace
car1 = SimpleNamespace(color="red", mileage=3812.4, automatic=True)

# The default repr:
car1
In [ ]:
# Instances support attribute access and are mutable:
car1.mileage = 12
car1.windshield = "broken"
del car1.automatic
car1
In [ ]:
출처 : https://realpython.com/python-data-structures