TIL12. Python : Sequence 자료형

ID짱재·2021년 9월 14일
0

Python

목록 보기
24/39
post-thumbnail

📌 이 포스팅에서는 Python의 Sequence 자료형을 정리하였습니다.



🌈 Sequence 자료형

🔥 List 자료형

🔥 Tuple 자료형

🔥 Dict 자료형

🔥 Set 자료형



1. List 자료형

🤔 Comprehending Lists

✔️ 지능형 리스트(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 & sort 비교

✔️ 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']

2. Tuple 자료형

🤔 Packing & Unpacking

✔️ 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 []

🤔 불변형(Mutable)과 가변형(Immutable)의 id값 비교

✔️ python에서 자료형은 특성에 따라 컨테이너형(Container), 플랫형(Flat), 가변형(Mutable), 불변형(Immutable)으로도 구분할 수 있어요.

  • 컨테이너형(Container) : 서로 다른 자료형을 저장할 수 있는 데이터 구조 ⇢ list, tuple, collections.deque 등
  • 플랫형(Flat) : 서로 같은 자료형만 저장할 수 있는 자료 구조 ⇢ str, bytes, bytearray, array.array, memoryview 등
  • 가변형(Mutable) : list, bytearray, array,array, memoryview, deque
  • 불변형(Immutable) : tuple, str, bytes

✔️ 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


3. Dict 자료형

🤔 Setdefault

✔️ 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']}

🤔 MappingProxyType : 불변형 Dict

✔️ 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


4. Set 자료형


🤔 frozenset : 불변형 Set

✔️ 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	

🤔 Comprehending Set

✔️ 참고로 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'}
profile
Keep Going, Keep Coding!

0개의 댓글