카테고리 없음

파이썬) 시퀀스 이해하기 1

STUFIT 2024. 11. 7. 18:13
반응형

시퀀스형 자료는 서로 다른 자료형을 담을 수 있는 컨테이너형과 단일 자료형만 포함하는 플랫형으로 나눌 수 있습니다. 또한, 시퀀스형 자료는 가변형(수정 가능)과 불변형(수정 불가능)으로 구분됩니다.

  • 컨테이너(Container): 리스트(list), 튜플(tuple), 컬렉션(collections.deque) 등 서로 다른 자료형을 포함할 수 있습니다.
  • 플랫(Flat): 문자열(str), 바이트(bytes), bytearray, array.array, memoryview 등 한 가지 자료형만 담습니다.
  • 가변형: 리스트(list), bytearray, array.array, memoryview, deque.
  • 불변형: 튜플(tuple), 문자열(str), 바이트(bytes).

 

리스트 컴프리헨션(Comprehending Lists)

예제 1: 리스트 컴프리헨션으로 유니코드 리스트 생성하기

리스트 컴프리헨션을 사용하면 반복문과 조건문을 이용해 리스트를 간단하고 빠르게 생성할 수 있습니다.

chars = '+_)(*&^%$#@!'
# 일반 반복문으로 유니코드 값 생성
code_list1 = []
for s in chars:
    code_list1.append(ord(s))
print(code_list1)

# 리스트 컴프리헨션으로 동일한 결과 생성
code_list2 = [ord(s) for s in chars]
print(code_list2)

위의 코드에서 ord() 함수는 각 문자의 유니코드 값을 반환합니다.

예제 2: 리스트 컴프리헨션 + 조건문과 map, filter

컴프리헨션에 조건을 추가해 특정 조건에 맞는 요소만 추가하거나, map과 filter를 사용해 더욱 복잡한 처리를 할 수 있습니다.

# 조건을 추가한 리스트 컴프리헨션
code_list3 = [ord(s) for s in chars if ord(s) > 40]
print(code_list3)

# map과 filter 사용
code_list4 = list(filter(lambda x: x > 40, map(ord, chars)))
print(code_list4)

map(ord, chars)는 각 문자를 유니코드로 변환하며, filter는 유니코드 값이 40보다 큰 경우만 남깁니다.

 

제네레이터와 메모리 효율성

제네레이터 생성

제네레이터는 한 번에 한 개의 항목만 생성하며, 메모리를 효율적으로 사용합니다. 예를 들어, 리스트 컴프리헨션과 유사한 형태로 제네레이터 표현식을 사용하면 메모리를 적게 사용하면서도 데이터를 처리할 수 있습니다.

# 제네레이터 생성
tuple_g = (ord(s) for s in chars)
print(next(tuple_g))
print(next(tuple_g))

# array 모듈을 사용해 제네레이터로 배열 생성
import array
array_g = array.array('I', (ord(s) for s in chars))
print(array_g.tolist())

제네레이터는 필요할 때만 데이터를 생성하기 때문에 메모리 효율이 뛰어나며, 대량의 데이터를 처리할 때 유용합니다.

중첩 리스트의 주의점

파이썬에서 중첩 리스트를 생성할 때는 주의가 필요합니다. 동일한 객체를 참조하는 경우, 하나를 수정하면 다른 모든 참조가 함께 수정될 수 있습니다.

# 서로 다른 리스트를 각각 생성하는 방식
marks1 = [['~'] * 3 for _ in range(5)]
print(marks1)

# 동일한 객체를 참조하는 방식
marks2 = [['~'] * 3] * 5
print(marks2)

# 수정
marks1[0][1] = 'X'
marks2[0][1] = 'X'
print(marks1)
print(marks2)

위 코드에서 marks2의 경우, 모든 행이 동일한 객체를 참조하여 하나의 요소를 수정하면 다른 모든 행도 영향을 받습니다.

 

특히 여기서는 깊은 복사와 얇은 복사의 차이점까지 알아두면 좋습니다.

1. 얕은 복사(Shallow Copy)

얕은 복사는 리스트의 상위 구조만 복사하며, 리스트 안에 있는 하위 리스트(중첩된 리스트)들은 원본과 같은 객체를 참조하게 됩니다. 따라서 하위 리스트를 수정하면 원본 리스트도 함께 변경됩니다.

예제: 얕은 복사

import copy

# 중첩 리스트 생성
original = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 얕은 복사
shallow_copied = copy.copy(original)
shallow_copied[0][0] = 'X'  # 첫 번째 하위 리스트의 첫 번째 요소를 수정

print("Original:", original)
print("Shallow Copied:", shallow_copied)

위 코드에서 shallow_copied는 original의 하위 리스트를 참조하므로, shallow_copied[0][0]을 수정하면 original 리스트도 함께 수정됩니다.

2. 깊은 복사(Deep Copy)

깊은 복사는 리스트의 모든 요소를 재귀적으로 새로 복사하여 완전히 독립적인 복사본을 생성합니다. 따라서 하위 리스트의 요소를 수정하더라도 원본 리스트에는 영향을 주지 않습니다.

예제: 깊은 복사

# 깊은 복사
deep_copied = copy.deepcopy(original)
deep_copied[0][0] = 'Y'  # 첫 번째 하위 리스트의 첫 번째 요소를 수정

print("Original:", original)
print("Deep Copied:", deep_copied)

copy.deepcopy를 사용하면 리스트의 모든 하위 리스트까지 복사되므로, deep_copied[0][0]을 수정해도 original은 변하지 않습니다.

중첩 리스트에서 얕은 복사와 깊은 복사의 차이점

중첩 리스트에서 얕은 복사를 사용하면 하위 리스트들이 원본과 동일한 객체를 참조하기 때문에, 하나의 요소를 수정할 때 예상치 못한 결과가 발생할 수 있습니다. 반면, 깊은 복사를 사용하면 리스트의 하위 구조가 완전히 복제되므로 독립적인 복사본으로 안전하게 수정할 수 있습니다.

결론

  • 얕은 복사: 상위 리스트만 복사하며 하위 리스트는 원본을 참조하여 메모리를 절약하지만, 수정 시 원본에 영향을 줄 수 있습니다.
  • 깊은 복사: 상위와 하위 모든 요소를 새로 복사하여 독립성을 보장하지만, 더 많은 메모리를 사용합니다.

깊은 복사와 얕은 복사를 상황에 맞게 사용하면 중첩 리스트를 더욱 안전하고 효율적으로 관리할 수 있습니다.

반응형