파이썬
컨테이너 타입
리스트 vs 튜플 vs 딕셔너리 vs 집합

리스트 튜플 딕셔너리 집합 차이, 언제 사용할 지 정리

파이썬의 내장 컨테이너 타입은 다음의 4가지입니다.: 리스트, 튜플, 딕셔너리, 집합

이번 포스팅에서는 각 타입의 특짐을 비교하며, 언제 사용하는 것이 좋은지에 대해 정리해보려고 합니다.

1. 리스트 vs 튜플

리스트와 튜플은 서로 가장 많이 닮은 타입입니다. 우선, 두 타입 모두 순서가 있으며, 인덱스를 이용해 해당 요소를 가져올 수 있습니다. 또한, 여러 타입의 요소들을 하나의 리스트 혹은 튜플에 담을 수 있기도 합니다. 이 때문에, 많은 경우에 두 타입을 모두 사용할 수 있습니다.

두 타입의 용도를 명확히 하기 위해선 차이점에 주목해야 합니다. 이번 섹션은 두 타입의 차이점을 기반으로 언제 사용하는 것이 좋은지 방향을 잡아보겠습니다.

1.1. 불변성

리스트와 튜플의 가장 큰 차이점은 불변성입니다. 리스트는 요소를 자유롭게 추가, 삭제할 수 있고 리스트에 속한 요소도 쉽게 수정이 가능합니다. 하지만 튜플은 한 번 선언하면 요소 추가, 삭제, 수정이 불가능합니다.

실제 어플리케이션에서 불변성이 필요한 경우와 필요하지 않은 경우를 생각해봅시다.

우리는 파이썬의 컨테이너 타입을 이용해서 쇼핑물의 여러 기능을 구현하는 업무를 맡았습니다. 쇼핑물의 기본적인 기능 중 추가, 삭제가 가능해야 하는 것은 제품 목록, 장바구니, 유저 목록 등이 있습니다. 이러한 기능은 리스트를 이용해야 합니다.

반대로, 불변성이 필요한 기능에는 어떤 것이 있을까요? 유저의 아이디, 가입일자, 판매자의 사업자등록번호 등 변하지 않는 메타데이터를 생각할 수 있겠습니다. 이러한 정보는 실수로라도 데이터가 수정되지 않도록 튜플을 이용하는 것이 좋습니다.

이렇게 어플리케이션 설계에 불변성을 생각하고 있으면, 다른 개발자와의 소통에도 도움이 됩니다. 리스트로 구현한 기능은 수정에 자유롭고, 튜플로 구현한 기능은 수정하지 말아야 한다는 것을 코드 자체로 표현할 수 있기 때문입니다.

1.2. 타입의 다양성

앞서 설명했듯이, 리스트와 튜플은 두 타입 모두 여러 가지 타입의 요소를 함께 포함할 수 있습니다. 하지만, 이전 섹션의 연장선에서 생각해본다면 이야기는 달라질 수 있습니다.

리스트는 제폼 목록, 장바구니 목록, 유저 목록 등에 사용한다고 했습니다. 이 목록들은 같은 종류의 요소들을 포항합니다. 장바구니 목록에는 제품만, 유저 목록에는 다음과 같이 유저만으로 이루어질 것입니다.

users = [User1, User2, User3]

이와 반대로, 튜플을 사용하는 유저의 메타데이터를 생각해보면, 이 튜플의 요소는 서로 다른 타입일 것입니다.

User1 = ('ID', '가입일자', '사업자등록번호', ... )

그리고, 이러한 튜플 데이터들이 모여 리스트를 구성할 수 있습니다.

Users = [
    ('User1', 'ID', '가입일자', '사업자등록번호', ... )
    ('User2', 'ID', '가입일자', '사업자등록번호', ... )
    ('User3', 'ID', '가입일자', '사업자등록번호', ... )
]

1.3. 정리

정리하자면, 튜플은 간단한 클래스 역할을 하듯 하나의 개체의 다양한 정보를 포함할 때 사용하는 방향으로 사용할 수 있습니다. 리스트는 튜플로 만들어진 여러 개의 개체를 목록으로 관리하는 데 사용할 수 있고요. 여기까지가 일반적인 리스트와 튜플의 사용 방향입니다.

여기에 1가지만 더 언급하고 넘어가겠습니다. 쇼핑물의 제품 목록은 쇼핑물 이용자가, 유저 목록은 서비스 관리자만 접근할 수 있어야 합니다. 이것은 변하지 않을 규칙입니다. 그러면 사용자에 따라 접근할 수 있는 목록들을 다음과 같이 튜플로 관리하면 어떨까요?

admin_lists = ([User1, User2, ... ], ... )
customer_lists = ([Item1, Item2, ... ], ... )

이와 같이, 튜플 안에 리스트를 포함하면 해당 리스트의 요소들은 수정가능합니다. 튜플의 불변성이 적용되는 대상은 리스트 자체의 레퍼런스이지, 이 레퍼런스가 포함하는 요소들이 아니기 때문입니다.

2. 딕셔너리

다른 3가지 타입과 딕셔너리를 구별짓는 가장 큰 특징은 딕셔너리를 구성하는 키-값 쌍(key-value pairs)입니다.

딕셔너리의 키는는 리스트나 튜플의 인덱스와 같은 역할을 합니다. 리스트나 튜플에서는 숫자로 된 인덱스로 특정 요소를 가져온다면, 딕셔너리에서는 불변성이 보장된 모든 타입을 값을 가져오는 키로 사용할 수 있습니다.

변화지 않는 키들은 딕셔너리의 기반이 되는 해시 테이블의 대상으로 사용할 수 있는데요. 이 덕분에 딕셔너리에서 키 포함 여부나, 키로 값을 가져오는 작업을 평균적으로 O(1) 의 시간복잡도로 수행할 수 있는 이점이 있습니다. 리스트와 튜플에서 아이템 포함 여부를 판별하기 위해 최대 O(n)만큼 탐색하는 것에 비해 경제적이죠.

키-값을 활용할 수 있는 딕셔너리인 만큼, 사실 튜플보다 더 데이터 모델을 구성하기 쉽습니다. 아래와 같이, 제품 정보나 유저 정보를 구성할 수 있죠. 특히, 수정이 자유로워야 할 정보에 대해서요.

별도의 포스트로 다루겠지만, 불변하는 속성 정보는 NamedTuple 타입으로 쉽게 관리할 수 있습니다.

Item1 = {
    "name": "Product A",
    "price": 50.00,
    "description": "Description A",
    "stock": 10},
}
 
User1 = {
    "name": "John Doe",
    "email": "john@example.com",
    "address": "123 street
}

이처럼 파이썬 딕셔너리는 속성과 속성값이 필요한 모든 개체를 구현하는데 핸디하게 사용할 수 있는 타입입니다.

3. 집합

집합 타입을 다른 3가지 타입과 구별짓는 가장 큰 특징 2가지가 있습니다.

그 중 하나는 중복을 허용하지 않는다는 점입니다. 집합은 set.add() 메서드로 새로운 값을 추가하거나, set() 생성자로 다른 컨테이너 타입을 변환하면 알아서 중복을 제거합니다. 덕분에, 다른 언어보다 쉽게 중복 제거 작업을 수행할 수 있습니다.

아래와 같이 집합으로 타입 캐스팅한 후, 다시 돌아오는 방법은 많이 사용하는 중복 제거 패턴 중 하나입니다.

numbers = [1, 2, 2, 3, 3, 3, 4, 5]
 
unique_numbers = list(set(numbers))
print(unique_numbers)
 
# Output: [1, 2, 3, 4, 5]

집합의 두번째 특징은 포함 여부 판별에 효과적인 점입니다. 2.딕셔너리에서 해시 테이블을 통해, 키 포함 여부를 효율적으로 판별할 수 있다고 했는데요. 집합의 값들도 해시 테이블로 구성되어 있습니다. 덕분에, 요소 포함 여부를 빠르게 알아낼 수 있습니다.

여기에, 집합 타입은 교집합, 합집합, 차집합, 여집합 관계를 알려주는 기본 메서드를 가지고 있습니다. 이러한 기능을 위해, 따로 코드를 작성할 필요가 없다는 뜻이기도 합니다.

예를 들어, 두 리스트의 포함 관계를 비교하고자 한다면 다음과 같이 사용할 수 있습니다.

fruits_in_market = ['apple', 'banana', 'orange', 'pineapple' ]
cart = ['banana', 'orange']
 
set_fruits_in_market = set(fruits_in_market)
is_superset = set_fruits_in_market.issuperset(cart)
 
print(is_superset)
 
# Output: True

들 중 하나의 리스트만 집합으로 변환하면, 위와 같이 간단한 코드로 강력한 작업이 가능합니다.

이제, 집합의 2가지 큰 특징을 이용하는 쇼핑몰의 기능을 생각해보겠습니다.

우선, 아래와 같이 중복을 제거해야 하는 카테고리 목록을 구성하거나,

product_categories = {"Electronics", "Books", "Clothing", "Home Decor", "Groceries"}

찜 목록과 장바구니 집합을 비교해서, 찜한 상품은 장바구니에서 하트 표시를 해주는 작업에도 사용할 수 있겠습니다.

wish_list = ['iPhone 14 pro', 'PlayStaion 5', 'Bose QC 45']
cart = ['PlayStaion 5', 'Nintendo Switch']
 
heart_list = set(wish_list).intersection(cart)
 
print(heart_list)
 
# Ouput: {'PlayStaion 5'}

4. 마치며

지금까지 파이썬 내장 컨테이너 타입의 특징과 사용 방향을 알아보았는데요. 어디까지나 4가지 타입에 대한 대략적인 감을 잡는데 도움이 되도록 정리한 내용이니만큼, 사용 방향에 대해서는 항상 열린 마음으로 창의적으로 생각하셨으면 좋겠습니다.

각 타입에 대한 더 자세한 내용은 각각의 하위 포스트를 확인해주세요.


© 2023 All rights reserved.