[Fluent Python] study week 2

Cha Hwa Young·2025년 1월 18일
0

Fluent Python Study

목록 보기
2/3

Ch 2. 시퀀스의 배열

내장 시퀀스 개요

파이썬의 내장 시퀀스는 두 가지 기준으로 분류 가능하다:

  1. 데이터 타입의 일관성
    • 컨테이너 시퀀스 : 서로 다른 타입의 데이터를 저장할 수 있음. 객체에 대한 참조를 담으며, 객체는 어떤 타입의 값이 될 수 있음.
      ex) list, tuple, collections.deque.
    • 균일 시퀀스 : 동일한 타입의 데이터만 저장할 수 있음. 자신의 메모리 공간에 각 항목의 값을 직접 담음.
      => 메모리를 더 적게 사용하지만, 바이트, 정수, 실수 등 기본적인 자료형만 담을 수 있음.
      ex) str, bytes, array.array.
  1. 가변성
  • 가변 시퀀스 : 생성 후 수정 가능.
    list, bytearray, array.array, collections.deque.

  • 불변 시퀀스 : 생성 후 수정 불가능.
    tuple, str, bytes.

  • 가변 시퀀스가 불변 시퀀스를 상속하면서 여러 메서드를 추가로 구현하였다.

  • 수정 가능/불가능이 무슨 뜻?🤔
    다음 코드를 통해 이해할 수 있을 것이다.

my_list = [1, 2, 3]
my_list[0] = 10  # 수정 가능
my_list.append(4)  # 추가 가능
print(my_list)  # 출력: [10, 2, 3, 4]

my_tuple = (1, 2, 3)
# my_tuple[0] = 10  # 에러 발생! 수정 불가
new_tuple = my_tuple + (4,)  # 새로운 튜플 생성
print(new_tuple)  # 출력: (1, 2, 3, 4)

결론적으로 핵심 시퀀스형은 list
가변적이며 혼합된 자료형을 담을 수 있다.
(다음 섹션 스포 😅)

지능형 리스트와 제너레이터 표현식

  • listcomp는 기존의 for 루프를 한 줄로 표현할 수 있는 방식인데, 이 구문이 두 줄을 넘어간다면 코드를 분할하거나 for문을 이용하는 것이 더 낫다고 한다.
    - 리스트만 만들 수 있고, 다른 시퀀스를 만들려면 제너레이터 표현식 genexp를 사용해야 한다.

  • 바다코끼리 연산자 !=

    (반가운 바다코끼리)
    이 연산자는 변수에 값을 할당하고 동시에 사용할 수 있게 해주는데, listcomp에서 사용되는 경우가 종종 있다.

  • 람다(lambda)를 사용하지 않고도 함수 map() 혹은filter()를 함께 쓸 수 있다.
    - 실제 스터디 팀원 분들 어떤 상황의 코드에서 쓰시는지 궁금.

  • 데카르트 곱 : listcomp는 두 개 이상의 반복 가능한 자료형의 데카르트 곱을 나타내는 리스트를 만들 수 있는데, 곱의 각 항목은 입력으로 받은 iterable한 데이터의 각 요소에서 만들어진 튜플로 구성된다.
    - itertools.product : 데카르트 곱 예제를 출력하며 보다가, 문득 생각났다.
    옛날에 코테 문제 풀면서 알게 된 함수이다.
    이 책에서는 listcomp와 함께 데카르트 곱을 구하는 예제가 나왔지만, 요 함수 하나로 여러 리스트의 모든 가능한 조합을 바로 확인할 수 있다!!

    colors = ['black', 'white']
    sizes = ['S', 'M', 'L']
    
    tshirts = [(color, size) for color in colors for size in sizes]
    print(tshirts) --- (a)
    
    print(list(itertools.product(colors, sizes))) --- (b)

    (a)와 (b) 모두 출력 결과는 동일!

  • 제너레이터 표현식 : 튜플, 배열 등의 시퀀스형을 초기화하려면 listcomp를 사용할 수도 있지만 genexp가 메모리를 더 적게 사용한다.
    => 반복자 프로토콜을 이용해 항목을 하나씩 생성하기 때문.
    - 대괄호 [] 대신 소괄호 () 사용.

튜플

레코드로서의 튜플

  • 튜플을 필드의 집합으로 사용할 때는 항목 수가 고정되며 그 순서가 중요
    - 순서를 정렬하면 정보가 파괴됨😱

불변 리스트로서의 튜플

  • 리스트의 길이가 절대 바뀌지 않음
  • list보다 메모리를 적게 소비하여 파이썬 인터프리터 최적화 수행 가능
  • 튜플 안의 참조(주소)는 삭제되거나 바뀔 수 없다.
    - but, 이러한 불변성은 그 안에 포함된 참조에만 적용된다.
    참조가 가변 객체(like 리스트)를 가리키고, 해당 객체의 값이 바뀌면 튜플 값도 바뀐다.
  • 값이 절대 바뀌지 않는 객체만 해시 가능하며, 불가능한 튜플은 dict 키나 set 항목으로 추가할 수 없다.
  • 불변 리스트로 사용되는 튜플이 리스트보다 더 효율적인지에 대한 질문에 대한 답에서 constant folding이라는 키워드를 찾을 수 있는데, 이를 이해하면 도움이 될 듯 하다.

언패킹

  • 튜플 언패킹은 병렬 할당(parallel assignment) 할 때 가장 두드러짐.

    병렬 할당이란?
    : 반복형 데이터를 변수로 구성된 튜플에 할당하는 것

    a, b, c = (1, 2, 3)
    print(a) # 출력 : 1
    print(b) # 출력 : 2
    print(c) # 출력 : 3
  • 변수의 값 맞바꾸기
    b, a = a, b

  • 인수 앞에 * 붙이기

    first, *rest = [1, 2, 3, 4, 5]
    print(first)  # 출력: 1
    print(rest)   # 출력: [2, 3, 4, 5]

    별표로 할당을 하는 건 직접 써보지 않았는데, 꽤 편할 듯 하다.
    단, 하나의 변수에만 적용 가능. 위치에는 제한 X

  • list, tuple, set 형의 리터럴 정의할 때도 사용 가능

  • 중첩 구조 언패킹

    # 중첩 구조 언패킹
    data = (1, (2, 3), 4)
    a, (b, c), d = data
    print(a)  # 출력: 1
    print(b)  # 출력: 2
    print(c)  # 출력: 3
    print(d)  # 출력: 4

시퀀스를 이용한 패턴 매칭

  • 패턴 매칭에 대한 설명
  • 패턴 매칭되는 조건
    1. 대상이 시퀀스이다.
    2. 대상과 패턴의 항목 수가 같다.
    3. 내포중첩된 항목을 포함해 해당 항목이 매칭된다.

슬라이싱

  • 왜 마지막 항목이 슬라이스 범위에 포함되지 않는가?
    - 번호를 0부터 매겨야 하는 이유에서 이러한 프로그래밍 관례를 이해할 수 있을 것이다.
  • 슬라이스는 시퀀스에서 정보 추출 뿐만 아니라 가변 시퀀스의 값 변경할 때도 사용 가능하다.

덧셈과 곱셈 연산자

  • 덧셈에서는 피연산자 두 개가 같은 타입의 데이터여야 하며, 동일한 타입의 시퀀스가 새로 만들어진다.
  • a에 가변 항목이 있을 때 a * n과 같은 표현식 사용하려면 주의해야 한다.
    ex) 리스트의 리스트 초기화할 때 my_list = [[]] * 3으로 초기화하면 동일한 내부 리스트에 대한 참조 세 개를 가진 리스트가 만들어져서 원치 않는 결과가..!
  • 복합 할당 : +=, *=
    - 덧셈 연산 할당자 += : __iadd__() 메서드 통해 구현
    a += b에서 a가 list, bytearray, array.array 등 가변 시퀀스라면 a의 값이 변경된다. (a.extend(b)와 유사)
    - 곱셈 연산 할당자 *= : __imul__() 메서드 통해 구현

list.sort()sorted() 내장 함수

  • list.sort() : 사본을 만들지 않고 리스트 내부를 변경해 정렬. receiver를 변경하고 새로운 리스트를 생성하지 않았음을 알려주려고 None 반환
    => random.shuffle() 함수도 동일하게 작용한다.
  • sorted() 내장 함수 : 새로운 리스트를 생성해 반환하므로, 불변 시퀀스 및 제너레이터 등 모든 iterable한 객체를 인수로 받는다.
  • reverse : True이면 내림차순. default는 False!
  • key : 각 항목에 적용할 함수로, len 함수를 key로 사용하여 문자열의 길이를 기준으로 정렬하며
    기본값은 정체성 함수?(정말 단어 그대로 옮기니까 그렇다😅) - Identity function 으로 항목 자체를 비교한다.

리스트가 답이 아닐 때

  • 리스트 안에 숫자만 있다면 배열(array.array) >>> 리스트보다 효율
    - 가변 시퀀스가 제공하는 연산 (ex: pop(), insert(), extend()을 모두 지원,
    frombytes(), tofile() 메서드도 지원
  • 메모리뷰(memoryview)
    - 이것을 모른다면 중요한 부분을 놓치고 있다고 한다. (이제라도 알아서 다행인가?)
    파이썬 자체에 들어 있는 넘파이 배열 구조체를 일반화한 것으로, 데이터셋이 커질 때 중요한 기법이라고 한다.
    GIF 이미지로부터 높이, 너비 값 추출할 때 사용하는 예제를 참고하자.
    (PIL을 두고 이것을 쓸지는 모르겠다.)
  • deque, queue, multiprocessing, asyncio, heapq ‼️‼️

Ch 12. 시퀀스 특별 메서드

돌아온 특별 메서드!

프로토콜과 덕 타이핑

  • 프로토콜 : 문서에만 정의되고 실제 코드에서는 정의되지 않는 비공식 인터페이스로, 특정 기능을 구현하기 위해 필요한 메서드들의 집합이라고 할 수 있겠다.
    ex) 파이썬의 시퀀스 프로토콜은
    __len__(), __getitem__() 메서드를 동반
  • 시퀀스처럼 작동하기 때문에 시퀀스인 것.

슬라이스 가능한 시퀀스

  • S.indices(len) -> (start, stop, stride) : 길이가 len인 시퀀스 S가 나타내는 확장된 슬라이스의 start와 stop 인덱스 및 stride 길이를 계산한다.
    => 주어진 길이의 시퀀스 경계 안에 들어가도록 조정된 0이나 양수인 start, stop, stride로 구성된 '정규화된' 튜플을 생성.
  • 예제에서 다루는 Vector 코드는 slice 인수를 받을 때 _components 배열에 처리하도록 하므로 slice.indices() 메서드를 구현할 필요가 없다.
    def __len__(self):
        return len(self._components)

    def __getitem__(self, key):
        if isinstance(key, slice):  
            cls = type(self)  
            return cls(self._components[key])  
        index = operator.index(key)  
        return self._components[index] 
  • operator.index() 함수가 __index__() 메서드 호출
    - operator.index()는 절절한 인덱스를 가져올 수 없을 때 TypeError 예외를 발생시킨다.

동적 속성 접근

  • __getattr__ 메서드를 사용하여 구현 가능
  • 객체가 일관성 있게 작동하려면 __getattr__() + __setattr__()도 구현할 것.

해싱 및 더 빠른 ==

  • __hash__ 메서드
    - 모든 벡터 요소의 해시를 계산하는 연산은 reduce()로 해결!
    - XOR 연산자(^)
  • __eq__ 메서드
    - all()
  • zip() 함수 : 반복형에서 나온 항목을 튜플로 묶어서 두 개 이상의 반복형을 병렬로 반복하기 쉽게 해 준다.
def __eq__(self, other):
	return len(self) == len(other) and all(a == b for a, b in zip(self, other))
profile
기회를 잡는 사람이 되도록!

0개의 댓글