• 파이썬 데이터 모델
    BOOK 2024. 12. 28. 20:14

    전문가를 위한 파이썬

    "한빛미디어 서평단 <나는리뷰어다> 활동을 위해서 책을 제공 받아 작성된 서평입니다."

     

    파이썬의 큰 장점 중 하나는 일관성. 한동안 파이썬으로 작업하다 보면 감각이 생겨서 새로운 기능도 어떻게 사용할지 예측할 수 있다.

    그러나 파이썬 이전에 다른 객체지향 언어를 배웠다면 collection.len()이 아닌 len(collection)을 사용하는 게 이상하게 느껴질 것.

    이런 이상함은 빙산의 일각일 뿐이지만, 제대로 이해해야 소위 말하는 파이썬다운(Pythonic) 경지에 도달할 수 있다. 빙상 전체를 '파이썬 데이터 모델'이라고 하는데, 이 모델이 제공하는 API를 이용해야 직접 정의한 객체에 파이썬 관용구를 적용할 수 있다.

     

    데이터 모델은 파이썬을 설명하는 일종의 프레임워크로 생각할 수 있는데, 시퀀스, 반복자(iterator), 함수, 코루틴, 클래스, 콘텍스트 관리자 등 언어 자체를 구성하는 단위 간 인터페이스를 공식적으로 정의.

     

    프레임워크를 사용해 코딩할 때는 프레임워크가 호출하는 메서드를 구현하는 데 많은 시간이 할애된다. 파이썬 데이터 모델을 이용해 새로운 클래스를 만들 때도 마찬가지. 파이썬 인터프리터는 클래서에 정의된 특별 메서드를 호출해서 기본적인 연산을 수행하는데, 종종 특별 구문에 의해 호출된다. 특별 메서드 이름은 언제나 이중 언더바로 시작하고 끝난다. 예를 들어 obj[key] 형태로 호출하려면 이 객체는 특별 메서드 __getitem__()을 구현해야 한다. my_collection[key]의 값을 평가하기 위해 파이썬 인터프리터가 my_collection.__getitem__(key)를 호출하기 때문.

     

    Card의 시퀀스로 구성한 카드 한 벌

    import collections
    
    Card = collections.namedtuple('Card', ['rank', 'suit'])
    
    class FrenchDeck:
        ranks = [str(n) for n in range(2, 11)] + list('JQKA')
        suits = 'spades diamonds clubs hearts'.split()
    
        def __init__(self):
            self._cards = [Card(rank, suit) for suit in self.suits
                                            for rank in self.ranks]
    
        def __len__(self):
            return len(self._cards)
    
        def __getitem__(self, position):
            return self._cards[position]

     

    먼저 collections.nametuple을 이용해 카드 한장을 나타내는 간단한 클래스를 만듦.

    beer_card = Card('7', 'diamonds')
    bear_card
    
    Card(rank='7', suit='diamonds')

     

    하지만, 이 예제의 핵심은 FrenchDeck 클래스. 다른 파이썬 컬랙션 객체와 마찬가지로 len() 함수를 사용해 카드 한 벌(deck)에 들어있는 카드 수를 구할 수 있다.

     

    deck = FrenchDeck()
    len(deck)
    
    52

     

     

    __getitem__() 메서드를 구현한 덕분에, 카드 한 벌(deck)에서 어떤 카드라도 쉽게 알아낼 수 있다.

    deck[0]
    Card(rank='2', suit='spades')
    
    deck[-1]
    Card(rank='A', suit='hearts')

     

    임의의 카드를 골라내려면 메서드를 따로 정의해야 할까? 그럴 필요가 없다. 파이썬에는 시퀀스에서 항목을 무작위로 골라내는 random.choice() 함수가 있기 때문. deck 객체에 다음과 같이 적용할 수 있다.

    from random import choice
    choice(deck)
    
    Card(rank='6', suit='diamonds')

     

    방금 파이썬 데이터 모델에 정의된 특별 메서드를 사용할 때의 장점 두 가지

    • 클래스 사용자는 표준 연산을 수행하는 메서드 이름을 기억할 필요가 없다.(.size()? or .length()?...)
    • random.choice() 함수처럼 파이썬 표준 라이브러리의 풍부한 기능을 따로 구현할 필요 없이 바로 사용 가능

    이뿐만이 아니라, __getitme__() 메서드는 self._cards의 []에 작업을 위임하므로 deck 객체는 슬라이싱(slicing)도 저절로 지원.

    deck[:3]
    
    [Card(rank='2', suit='spades'),
     Card(rank='3', suit='spades'),
     Card(rank='4', suit='spades')]
     
     
     deck[12::13]
     
     [Card(rank='A', suit='spades'),
     Card(rank='A', suit='diamonds'),
     Card(rank='A', suit='clubs'),
     Card(rank='A', suit='hearts')]

     

    특별 메서드의 용도

    특별 메서드는 파이썬 인터프리터가 호출하는 메서드. my_object.__len__()을 직접 호출하지 않고 len(my_object)를 호출.

    my_object가 사용자 정의 클래스의 인스턴스이면, 파이썬은 유저가 정의한 __len__() 메서드를 호출

     

    그러나 list, str, bytearray 등의 내장형이나 넘파이(NumPy) 배열 같은 확장형을 다룰 때 파이썬 인터프린터는 지름길을 선택. C로 작성한 파이썬 가변 크기 컬랙션은 PyVarObject라는 struct 구조체를 가지는데, 이 구조체의 ob_size 필드에 컬렉션 항목 수가 들어 있다. 따라서 my_object가 내장 객체라면 len(my_object)는 ob_size 필드를 읽어 반환하므로 메서드를 호출하는 방식보다 훨씬 빠르다.

     

    종종 특별 메서드는 암묵적으로 호출되기도 한다. 가령 for i in x: 문은 실제로 iter(x)를 호출하는데, __iter__()를 사용할 수 있으면 x.__iter__()가 호출되고, 그렇지 않으면 FrenchDeck처럼 x.__getitem__()이 호출됨.

     

    일반적으로 사용자 코드에서는 특별 메서드를 직접 호출하지 않는다. 메타프로그래밍을 하는 경우가 아니라면 사용자 코드는 대부분 특별 메소드를 구현하기는 하지만, 명시적으로 호출하지는 않는다. 다만 클래스를 정의할 때 사용자가 구현한 __init__() 코드 안에서 슈퍼클래스의 __init__() 초기화 메서드를 직접 호출하는 것은 자주 볼 수 있다. 

     

     

    728x90

    'BOOK' 카테고리의 다른 글

    chatGPT 활용 데이터 분석  (1) 2024.11.17
    쿠버네티스 모니터링  (2) 2024.10.26
    소프트웨어 설계 접근법  (1) 2024.09.28
    뉴스 기사 탐색 챗봇 만들기  (2) 2024.08.24
    회의 요약 보고서 작성법  (1) 2024.07.25
go.