📌 이 포스팅에서는 Python의 Sequence 자료형을 정리하였습니다.
🔥 List 자료형
🔥 Tuple 자료형
🔥 Dict 자료형
🔥 Set 자료형
✔️ 지능형 리스트(Comprehending Lists)를 사용할 때와 사용하지 않을 때를 비교해 보면, 지능형 리스트가 가독성이 높고, 코드가 간결합니다.
✔️ 또한 데이터의 복잡도가 높아질수록 지능형 리스트의 속도가 비교적 우세합니다.
✔️ 단, 지능형 리스트 내에 반복문이랑 조건문이 3개 이상 존재한다면 가독성이 떨어집니다.
# 지능형 리스트 방식을 사용하지 않은 일반적인 경우 chars = '!@#$%^&*()_+' res = [] for s in chars: res.append(ord(s)) # 👈 ord 함수는 문자를 유니코드로 변환해줍니다. print(res) # [33, 64, 35, 36, 37, 94, 38, 42, 40, 41, 95, 43]
# 지능형 리스트 방법을 사용할 경우 chars = '!@#$%^&*()_+' res = [ord(s) for s in chars] print(res) # [33, 64, 35, 36, 37, 94, 38, 42, 40, 41, 95, 43]
✔️ 지능형 리스트와 filter&map 조합 비교(유니코드 값이 40보다 큰 문자열 출력하기)
# 지능형 리스트 풀이 chars = '!@#$%^&*()_+' codes = [ord(s) for s in chars if ord(s) > 40] res = [chr(s) for s in codes] print(res) # ['@', '^', '*', ')', '_', '+']
# filter & map 풀이 chars = '!@#$%^&*()_+' codes = list(filter(lambda x: x > 40, map(ord, chars))) res = [chr(s) for s in codes] print(res) # ['@', '^', '*', ')', '_', '+']
✔️ marks1는 append 과정이 생략된 지능형 리스트이기 때문에 리스트 내 요소값이 일치여부와 관련없이 각 요소는 서로 다른 객체이나, marks2는 요소를 복사를 한 것이기 때문에 내부적으로 모두 같은 id값를 갖게됩니다.
marks1 = [['~']*3 for n in range(3)] marks2 = [['~']*3]*3 print(marks1) # [['~', '~', '~'], ['~', '~', '~'], ['~', '~', '~']] print(marks2) # [['~', '~', '~'], ['~', '~', '~'], ['~', '~', '~']] # id값이 모두 다릅니다. print([id(i) for i in marks1]) # [140429515520448, 140429515520384, 140429515520320] # id값이 모두 같습니다. print([id(i) for i in marks2]) # [140429515520256, 140429515520256, 140429515520256]
✔️ 따라서 marks1과 marks2의 원소값을 수정하면, 서로 다른 결과를 발생시킵니다.
✔️ marks1은 서로 다른 id값을 갖고 있기 때문에 해당 인덱스의 원소만 수정되고, marks2는 서로 같은 id값을 갖고 있기 때문에 모두가 수정됩니다.
✔️ 즉, 지능형 리스트의 내부는 서로 다른 id를 가진 독립적인 원소들로 구성되기 때문에 예상치 못한 문제를 예방할 수 있습니다.
marks1[0][1] = 'X' marks2[0][1] = 'X' print(marks1) # [['~', 'X', '~'], ['~', '~', '~'], ['~', '~', '~']] print(marks2) # [['~', 'X', '~'], ['~', 'X', '~'], ['~', 'X', '~']]
✔️ sorted는 정렬 후 "새로운" 객체를 반환하기 때문에 원본에는 영향을 주지 않고, 옵션값으로 key=func, reverse 등이 있습니다.
f_list = ["orange", "apple", "mango", "papaya", "lemon", "strawberry", "coconut"] # options : reverse, key=len, key=str.lower, key=func print(sorted(f_list)) # 👉 ['apple', 'coconut', 'lemon', 'mango', 'orange', 'papaya', 'strawberry'] print(sorted(f_list, reverse=True)) # 👉 ['strawberry', 'papaya', 'orange', 'mango', 'lemon', 'coconut', 'apple'] print(sorted(f_list, key=len)) # 👉 ['apple', 'mango', 'lemon', 'orange', 'papaya', 'coconut', 'strawberry'] # 원소의 마지막 알파벳을 기준으로 정렬( key=lambda x: x[-1]) ) print(sorted(f_list, key=lambda x: x[-1])) # 👉 ['papaya', 'orange', 'apple', 'lemon', 'mango', 'coconut', 'strawberry'] print(sorted(f_list, key=lambda x: x[-1], reverse=True)) # 👉 ['strawberry', 'coconut', 'mango', 'lemon', 'orange', 'apple', 'papaya']
✔️ sort는 정렬 및 원본 객체를 직접 변경합니다.
✔️ 또한 sort는 반환값이 'None'이기 때문에 원본을 출력해야 정렬된 결과를 확인할 수 있어요.
✔️ 즉, 원본 데이터와 정렬 후 데이터의 비교가 필요할 때, 또는 원본 데이터가 중요하기 때문에 손실 없이 처리해야할 경우 "sort" 보다 "sorted"가 더 적합합니다.
print(f_list.sort(), f_list) # 👉 None ['apple', 'coconut', 'lemon', 'mango', 'orange', 'papaya', 'strawberry'] print(f_list.sort(reverse=True), f_list) # 👉 None ['strawberry', 'papaya', 'orange', 'mango', 'lemon', 'coconut', 'apple'] print(f_list.sort(key=len), f_list) # 👉 None ['mango', 'lemon', 'apple', 'papaya', 'orange', 'coconut', 'strawberry'] print(f_list.sort(key=lambda x:x[-1]), f_list) # 👉 None ['papaya', 'apple', 'orange', 'lemon', 'mango', 'coconut', 'strawberry'] print(f_list.sort(key=lambda x:x[-1], reverse=True), f_list) # 👉 None ['strawberry', 'coconut', 'mango', 'lemon', 'apple', 'orange', 'papaya']
✔️ Packing & Unpacking에서 *(아스타) 사용 예시
print(divmod(100, 9)) # (11, 1) print(divmod(*(100, 9))) # (11, 1) print(*(divmod(100,9))) # 11 1
x, y, *rest = range(10) print(x,y,rest) # 0 1 [2, 3, 4, 5, 6, 7, 8, 9] x, y, *rest = 1,2,3,4,5 print(x,y,rest) # 1 2 [3, 4, 5] x, y, *rest = range(2) print(x,y,rest) # 0 1 []
✔️ python에서 자료형은 특성에 따라 컨테이너형(Container), 플랫형(Flat), 가변형(Mutable), 불변형(Immutable)으로도 구분할 수 있어요.
✔️ tuple과 list는 서로 다른 자료형을 저장할 수 있는 container형이지만, list는 가변형과 tuple은 불변형이라는 차이가 존재합니다.
✔️ tuple과 list에서 곱하기(*
) 연산을 사용하면 id값이 변하는데, 이는 객체를 새로 생성하기 때문입니다! 즉, 곱하기 연산은 값의 수정이 아닌 extend 개념이기 때문에 tuple와 list 모두 곱하기 연산이 가능합니다.
t = (10, 15, 20) l = [10, 15, 20] print(t,l) # (10, 15, 20) [10, 15, 20] print(id(t), id(l)) # 140259790427136 140259809781248 t = t * 2 l = l * 2 print(t, l) # (10, 15, 20, 10, 15, 20) [10, 15, 20, 10, 15, 20] print(id(t), id(l)) # 140259790530496 140259790387584
✔️ 할당연산자에서는 list와 tuple의 차이가 발생합니다. list는 기존 객체에 연산 후 데이터를 재할당하기 때문에 id값의 변화가 없지만, tuple은 새로운 객체를 생성하기 때문에 id값이 변경됩니다.
t *= 2 # 👈 tuple은 할당연산자 사용 시, 객체를 새로 생성해요:) l *= 2 # 👈 list은 할당연산자 사용 시, 객체에 재할당해요:) print(t, l) # (10, 15, 20, 10, 15, 20, 10, 15, 20, 10, 15, 20) [10, 15, 20, 10, 15, 20, 10, 15, 20, 10, 15, 20] print(id(t), id(l)) # 140259808807376 140259790387584
✔️ Dict형은 Key를 중복시킬 수 없습니다. 이에 Key가 중복되는 Dict형 데이터 구조가 필요할 때는 동일 Key에 배열을 사용하여 값을 추가시킵니다.
✔️ 중복되는 key가 존재하는 튜플형 데이터를 dict로 변환할 때, Setdefault을 사용하지 않는다면 아래처럼 조건문을 통해 분기해야 합니다.
source = ( ("k1", "val1"), ("k1", "val2"), ("k2", "val3"), ("k2", "val4"), ("k2", "val5"), ) new_dict = {} for k, v in source: if k in new_dict1: # 👈 k값 new_dict1에 없으면,,, new_dict[k].append(v) else: # 👈 k값 new_dict1에 있으면,,, new_dict[k] = [v] print(new_dict) # {'k1': ['val1', 'val2'], 'k2': ['val3', 'val4', 'val5']}
✔️ setdefault의 기능은 딕셔너리에 value를 추가하려고할 때, 해당 key가 없으면 새로 생성하고 존재하면 key의 값을 반환해줍니다.
✔️ 이에 Setdefault을 사용하면, 분기를 거치지 않기 때문에 속도가 더 좋습니다.
source = ( ("k1", "val1"), ("k1", "val2"), ("k2", "val3"), ("k2", "val4"), ("k2", "val5"), ) new_dict = {} for k, v in source: new_dict.setdefault(k, []).append(v) print(new_dict) # {'k1': ['val1', 'val2'], 'k2': ['val3', 'val4', 'val5']}
✔️ Dict은 가변형(mutable)이지만, MappingProxyType 사용하여 불변형(Immutable)으로 사용 가능합니다.
✔️ MappingProxyType(Immutable Dict)을 사용하면, 값을 추가할 수도 없고, 수정할 수 없습니다.
from types import MappingProxyType d = {"key1": "TEST!"} d_frozen = MappingProxyType(d) # Read Only print(d, id(d)) # {'key1': 'TEST!'} 140425349177664 print(d_frozen, id(d_frozen)) # {'key1': 'TEST!'} 140425355947936 # 값은 같으나 다른 객체임 print(d is d_frozen) # False print(d == d_frozen) # True # Dict는 수정 가능하지만, MappingProxyType는 수정 불가 d['key1'] = 'TEST2' print(d) # {'key1': 'TEST2'} # d_frozen['key1'] = 'TEST2' # 👈 TypeError: 'mappingproxy' object does not support item assignment
✔️ frozenSet은 Set을 불변형(Immutable)으로 쓰고자할 때 사용하기 때문에 추가 및 수정이 불가능합니다.
s1 = {'Apple', 'Orange', 'Apple', 'Orange', 'Kiwi'} s5 = frozenset({'Apple', 'Orange', 'Apple', 'Orange', 'Kiwi'}) s1.add('Melon') print(s1, type(s1)) # {'Apple', 'Melon', 'Orange', 'Kiwi'} <class 'set'> # s5.add('Melon') # AttributeError
✔️ 참고로 List, Tule, Dict, Set 모두 Comprehending의 문법을 지원합니다.
from unicodedata import name print({name(chr(i), '') for i in range(0, 50)}) # {'', 'EXCLAMATION MARK', 'QUOTATION MARK', 'HYPHEN-MINUS', 'NUMBER SIGN', 'PLUS SIGN', 'APOSTROPHE', 'LEFT PARENTHESIS', 'ASTERISK', 'COMMA', 'SOLIDUS', 'AMPERSAND', 'PERCENT SIGN', 'SPACE', 'DIGIT ZERO', 'DOLLAR SIGN', 'RIGHT PARENTHESIS', 'DIGIT ONE', 'FULL STOP'}