ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] 시퀀스 프로토콜(Sequence Protocol)을 이해한다는 것
    IT 2026. 1. 21. 16:05

     

    파이썬을 쓰다 보면 우리는 거의 매 순간 시퀀스를 다루고 있습니다. 리스트, 튜플 문자열, range 객체까지 모두 너무 자연스럽게 사용하다 보니, 어느 순간 이런 질문은 잘 떠오르지 않습니다.

    "이 객체는 왜 인덱싱이 될까?"
    "왜 for 문으로 순회할 수 있을까?"

    이 질문들에 대한 답을 시작하겠습니다.

     

    우리는 이미 시퀀스를 알고 있다

    다음 코드들은 너무 익숙합니다.

    items = [10, 20, 30]
    print(items[0])
    
    
    for x in items:
        print(x)
    
    
    print(len(items))

    이 세 가지 동작은 모두 자연스럽게 받아들여집니다. 하지만 조금만 생각해 보면 흥미로운 사실 하나가 보입니다.

    • 인덱스로 접근할 수 있고, 길이를 알 수 있고, 순서대로 순회할 수 있다

    파이썬은 이 세 가지 성질을 만족하는 객체를 시퀀스처럼 다룰 수 있다고 판단합니다. 이 판단 기준이 바로 시퀀스 프로토콜 입니다.

     

    시퀀스 프로토콜이란 무엇인가

    시퀀스 프로토콜은 한 문장으로 요약하면 이렇습니다.

    "이 객체를 순서가 있는 데이터 묶음처럼 다뤄도 되는가?"

    이를 위해 파이썬은 몇 가지 약속된 메서드를 제공합니다. 그중에서도 핵심은 다음 두 가지입니다.

    • __len__()
    • __getitem__()

    이 두 메서드만 제대로 구현되어 있다면, 그 객체는 리스트처럼 행동하기 시작합니다.

     

    __getitem__이 만들어내는 마법

    다음과 같은 간단한 클래스를 생각해봅시다.

    class MySequence:
        def __init__(self, data):
            self.data = data
    
    
        def __getitem__(self, index):
            return self.data[index]

    이 클래스에는 __iter__도 없고, __next__도 없습니다. 하지만 놀랍게도 다음 코드는 정상적으로 작동합니다.

    seq = MySequence([1, 2, 3])
    
    print(seq[0])
    
    for x in seq:
        print(x)

    파이썬은 for 문을 만났을 때, 먼저 이 객체가 이터레이터인지 확인합니다. 만약 아니라면, 인덱스 0부터 시작해 __getitem__을 호출하며 값을 꺼내려 시도합니다. 그리고 더 이상 꺼낼 값이 없으면 순회를 멈춥니다.

    즉, __getitem__ 하나만으로도 파이썬은 이 객체를 시퀀스로 취급할 수 있는 것입니다.

     

    len이 있다는 것은 무엇을 의미할까

    len() 함수가 동작한다는 것은 단순히 길이를 알 수 있다는 의미를 넘어섭니다.

    len(seq)

    이 호출은 내부적으로 seq.__len__()을 실행합니다. 이는 파이썬에게 "이 객체는 크기를 개념적으로 정의할 수 있다"는 신호를 보내는 것과 같습니다.

    그래서 시퀀스 프로토콜을 충실히 따르는 객체는, 단순히 순회만 가능한 객체보다 더 많은 곳에서 자연스럽게 사용될 수 있습니다.

     

    시퀀스 프로토콜과 이터레이터의 관계

    앞서 이터레이터와 제너레이터를 다뤘다면, 여기서 한 가지 연결 지점이 보입니다.

    • 이터레이터 → __iter____next__
    • 시퀀스 → __getitem____len__

    이 둘은 서로 다른 프로토콜이지만, for 문이라는 동일한 인터페이스를 통해 사용됩니다. 파이썬은 객체가 어떤 프로토콜을 따르는지에 따라, 순회를 위한 전략을 다르게 선택할 뿐입니다.

    이 점이 바로 파이썬의 강점입니다. 하나의 문법(for)이 여러 내부 구조를 자연스럽게 감싸고 있습니다.

     

    슬라이싱도 프로토콜의 일부다

    시퀀스를 시퀀스답게 만드는 또 하나의 요소는 슬라이싱입니다.

    items[1:3]

    이 문법 역시 내부적으로는 __getitem__으로 처리됩니다. 인덱스 대신 slice 객체가 전달될 뿐입니다.

    def __getitem__(self, index):
        if isinstance(index, slice):
            return MySequence(self.data[index])	
        return self.data[index]

    이처럼 시퀀스 프로토콜은 단순한 접근 기능을 넘어, 객체를 다루는 감각 자체를 정의합니다.

     

    언제 시퀀스 프로토콜을 고려해야 할까?

    모든 반복 가능한 객체가 시퀀스일 필요는 없습니다. 오히려 많은 경우 이터레이터나 제너레이터가 더 적절합니다.

    하지만 다음과 같은 질문에 "그렇다"라고 답하게 된다면, 시퀀스 프로토콜은 좋은 선택이 됩니다.

    • 이 객체는 순서가 중요한가?
    • 인덱스로 접근하는 것이 자연스러운가?
    • 길이라는 개념이 의미 있는가?

    이 질문들은 단순히 구현의 문제가 아니라, 데이터 모델링의 문제에 가깝습니다.


    마무리하며

    시퀀스 프로토콜은 파이썬의 객체 모델이 얼마나 유연한지를 잘 보여주는 예입니다. 파이썬은 객체에게 "너는 리스트야"라고 강요하지 않습니다. 대신 이렇게 묻습니다.

    "너는 리스트처럼 행동할 수 있니?"

    그 질문에 적절한 메서드로 답할 수 있다면, 파이썬은 그 객체를 자연스럽게 시퀀스로 받아들입니다.

    이 관점을 이해하고 나면, 파이썬의 많은 문법들이 단순한 기능이 아니라 약속과 신뢰 위에 세워진 구조라는 점이 보이기 시작할 것입니다.

     

     

    이상입니다.

    감사합니다.

Designed by Tistory.