Computer Engineering/Fluent Python 정리

Fluent Python Chapter 2-1. Sequence (Sequence의 분류, listcomp, generator, tuple, slicing)

jordan.bae 2021. 12. 23. 23:37

Chapter 1정리에 이어서 Chapter2로 넘어왔습니다. 책의 전체적인 내용을 다루기보다는 제가 읽으면서 도움이 되었던 내용 그리고 중요하다고 생각한 내용 위주로 정리할 생각입니다. 제 글을 읽고 조금 더 알고 싶다는 생각이 드신다면 꼭 책을 구입하셔서 읽어보시길 추천드립니다.

 

이번 정리는 Chapter 2.4까지의 정리입니다. 내용이 길어져서 2번으로 나눠서 올리려고 합니다.

가장 인상 깊었던 부분.

각 부분을 정리한 내용을 살펴보기 전에 가장 인상 깊었던 부분을 소개합니다.

symbols = '$%^&*'
codes = []
for symbol in symbols:
    codes.append(ord(symbol))
                 
print(codes)
[36, 37, 94, 38, 42]


# listcomp
symbols = '$%^&*'
codes = [ord(symbol) for symbol in symbols]
print(codes)
[36, 37, 94, 38, 42]

왜 아래 list comprehension으로 list를 생성한게 더 좋은 코드라고 생각 하시나요?

짧다. 짧아서 가독성이 좋다라는 답변도 좋은 답변이지만, 조금 더 좋은 답변은 어떤 것을 하려고 하는지 분명한 의도를 가진 코드이기 때문이라는 답변일 것 같습니다.

for 문은 sequence를 읽을 때나, 수정할 때 다양한 일에 활용될 수 있습니다. 하지만, listcomp에서 사용한 for문은 오직 한 가지 목적인 list를 생성하는데만 사용합니다. 의도가 매우 명시적입니다. 

 

내용 별 정리

Sequence의 분류

Sequence가 가지고 있는 데이터의 단일이냐 복합형인가에 따른 분류

 

- Container Sequences: 서로 다른 데이터 타입의 객체를 담을 수 있는 list, tuple, deque 형

객체에 대한 참조를 담고 있음.

 

- Flat Sequences : 단 하나의 데이터 타입을 담을 수 있는 str, bytes, bytearray, memoryview, array.array 형

객체에 대한 참조 대신 자신의 메모리 공간에 각 항목의 값을 직접 담는다.

+ 예를 들어, 많은 정수 배열을 만든다고 하면 array.array형으로 만드는게 메모리 관점에서 훨씬 효율적.

 

Sequence의 가변성에 따른 분류

 

- Mutable Sequences: list, bytearray, array.array, deque, memory view 형

mutable sequence의 ABC를 살펴보면 추가(insert, append), 할당(__setitem__), 삭제(__delitem__) 인터페이스들이 포함되어 있다는 것을 확인 할 수 있다.

 

- Immutable Sequences: tuple, str, bytes

 

 

List Comprehension / Generator

List Comprehension은 위에서 설명한 것 처럼 list를 생성하기 정말 좋은 방법입니다. 

그럼 바로 generator에 대해서 살펴보겠습니다. generator는 syntax 는 list comp에서 대괄호 대신 괄호를 사용하면 됩니다.

 

generator같은 경우는 어떤 객체를 저장할 필요가 없이 iteration만 해야 할 경우 정말 효과적입니다.

 

책에서는 단순히 데카르곱을 출력하는 경우를 예시로 소개합니다.

colors = ['black', 'white']
sizes = ['S', 'M', 'L', 'XL']

for tshirts in ('%s %s' % (c, s) for c in colors for s in sizes):
    print(tshirts)
    
black S
black M
black L
black XL
white S
white M
white L
white XL

많은 곳에서 generator를 사용하는 것을 볼 수 있는데 이는 메모리에 유지할 필요가 없는 데이터를 생성하기 위해 제네이레이터 표현식을 사용하는 것입니다.

 

Tuple (튜플)

레코드로서의 튜플

튜플은 unpacking이 되기 때문에 일정한 순서를 가진 record로도 활용할 수 있습니다.

# 항목 순서와 수가 고정된 record로 활용하기 좋음. unpacking 기능 또한 record로 활용하기에 더 좋음.
lax_coordinates = (33.9425, -118)
latitude, longitude = lax_coordinates

latitude, longitude
(33.9425, -118)

# 함수의 인자로 *을 붙여서 tuple을 unpacking할 수 잇음.
a = (19, 20)
divmod(*a)

하지만, 각 칼럼에 접근하기 위해서는 순서를 알고 있어야 한다는 불편함이 있기 때문에 namedtuple을 사용할면 record로 사용하기에 정말 좋습니다. (namedtuple은 제가 자주 사용하는 데이터 객체 중 하나입니다.)

아까 위의 코드를 namedtuple로 정의해서 사용해 보겠습니다.

lax_coordinates = Coordinates(latitude=33.9425, longitude=-118)

lax_coordinates
Coordinates(latitude=33.9425, longitude=-118)


lax_coordinates.latitude, lax_coordinates.longitude
(33.9425, -118)

# 클래스의 필드명을 확인하는 속성
Coordinates._fields
('latitude', 'longitude')

# instance를 OrderedDict로 변환해서 반환하는 인스턴스 메서드
lax_coordinates._asdict()
{'latitude': 33.9425, 'longitude': -118}

 

immutable sequence로서의 튜플

tuple은 element를 변경할 수 없는 컨테이너입니다. 그렇기 때문에 list와 비교해보면 element를 추가/수정/삭제하는 magic method들을 가지고 있지 않습니다. 하지만, 그 밖에 데이터를 조회하거나 순회하는 인터페이스들을 가지고 있습니다. 

 

Slicing (슬라이싱)

파이썬에서 제공하는 list, tuple, str등의 모든 sequence형은 슬라이싱 연산을 지원합니다.

 

슬라이싱 연산 시에 마지막 항목을 포함하지 않는 중단점(포함되지 않는)인 이유는 길이를 연산하기 쉽고, 시퀀스를 분할하기 좋기 때문이다.

s = [i for i in range(5)]

# index = lengh of sequence
len(s[:3])
3

# 마지막 항목 - 첫번째 항목 = lengh of sequence
len(s[1:3])
2

# 특정 인덱스 기준으로 시퀀스 분할이 쉬움.
s[:2], s[2:]
([0, 1], [2, 3, 4])

또, s[a:b:c] 를 활용해 c의 보폭으로 항목을 건너뛰게 만든다. 어떤 일정한 주기를 가진 데이터를 조회할 때 효율적이다.

예를 들어 1장에서 살펴 본 frenchdeck에서 A 카드만 뽑고 싶은 경우.

 

 

이렇게 Chatpter2에서 앞 부분을 정리해봤습니다. 개인적으로 listcomp와 generator를 잘 활용하고, numpy가 없을 때 많은 수의 배열을 사용할 때 array.array를 사용하는 부분, 그리고 namedtuple정도를 잘 이해하고 적절하게 사용할 수 있게 된다면 충분하게 많은 도움이 될 것 같습니다.. 저의 공부를 위해서 정리한 내용이지만 다른 분들에게도 도움이 되셨으면 좋겠습니다. 그럼 곧 또 Chatper2의 후반부를 정리해 보도록 하겠습니다.

 

 

Reference

- Fluent Python


반응형