
파이썬은 데이터를 분석하는 과정에서 목적에 따라, 데이터를 처리하기 위한 매우 편리하고 다양한 기능을 제공합니다. 그중에서도 문자열 자르기(슬라이싱, Slicing)은 데이터의 형태나 도메인과 관계없이 핵심적인 역할을 수행하죠. 사용자는 문자열 슬라이싱을 통해 문자열의 특정 부분을 효율적으로 추출하거나, 조작할 수 있으며, 이는 데이터 분석, 웹 애플리케이션 개발, 자동화 스크립트 등 다양한 영역에서 필수적인 기술로 쓰입니다.
파이썬을 사용해 문자열의 일부분을 잘라내는 기본적인 사용법, 그리고 구체적인 작동 원리를 이해하는 것은 더욱 강력하고 유연한 코드를 작성하는 데 있어 결정적인 차이를 만듭니다. 이번 글에서는 파이썬의 문자열 슬라이싱의 기본 문법부터, C언어로 구현된 CPython의 내부 로직까지 깊게 살펴보겠습니다.
우선 파이썬 문자열 슬라이싱은 콜론(:)을 사용하여, 문자열의 일부분을 선택하는 방식으로 이루어집니다. 기본적인 문법은 오브젝트[시작:끝:증감] 형태를 가지며, 각 인덱스(요소)는 슬라이스의 범위를 정의하는 데 중요한 역할을 합니다.
시작(start) 인덱스는 슬라이스가 시작되는 위치를 나타냅니다. 주의할 점으로, 이 위치에 해당하는 문자는 슬라이스 결과물에 포함됩니다. 만약 시작 인덱스가 생략되면 파이썬은 0을 값으로 사용, 즉, 문자열의 처음부터 슬라이스를 시작합니다.
이어서 끝(end) 인덱스는 슬라이스가 끝나는 위치를 나타냅니다. 시작 인덱스와의 차이점으로 이 위치에 해당하는 문자는 슬라이스 결과에 포함되지 않는다는 것입니다. 끝 인덱스가 주어지지 않는다면, 슬라이스는 문자열의 마지막 문자까지 포함합니다.
마지막으로 스텝(step) 값은 슬라이스 과정에서 문자를 건너뛸 간격을 지정합니다. 이 값은 선택 사항이며, 입력되지 않는다면 1을 기본값으로 사용하여 모든 문자를 순차적으로 선택합니다.
이어서 양수 및 음수 인덱스를 활용한 슬라이싱 예시를 보겠습니다.
스텝을 사용하면 더욱 다양한 방식으로 문자열을 슬라이스할 수 있습니다.
문자열 슬라이싱은 실제 다양한 방식으로 활용될 수 있으며, 몇 가지 구체적인 예시는 다음과 같습니다.
불변이란 원래의 오브젝트의 내용을 변경할 수 없음을 의미합니다. 만약 s = ‘www.wishket.com’ 이라는 코드를 실행하면 ‘yozm.wishket.com’에서 s의 내용이 변경되고, 문제가 발생하지 않기에 내용이 변경되는 것으로 오해할 수 있는데요. 이는 메모리에 ‘www.wishket.com’이라는 문자열 오브젝트를 새롭게 만들고, 이 주소를 s에 새롭게 할당한 것입니다.
그렇기에 문자열의 내부를 변경하면 의도한 대로 불변을 변경하는 과정에서의 오류를 확인할 수 있습니다.
이 불변의 개념이 문자열 슬라이싱에서 중요한 이유는, 슬라이싱 또한 불변인 원본 문자열을 변경하지 않으면서, 문자열의 일부를 사용하여 새로운 문자열을 만드는 작업이기 때문입니다. 하지만 파이썬, 엄밀히는 CPython에서는 가능하면 메모리 복사를 피하고, 불필요한 객체 생성을 줄이기 위한 여러 최적화 전략들이 코드에 녹아있습니다. 이제 실제로 CPython이 이 슬라이싱을 처리하는 과정을 내부 코드와 함께 알아보겠습니다.
파이썬 텍스트 객체의 슬라이싱 연산은 객체의 __getitem__ 메소드에 slice 객체를 전달하여 이루어집니다. 즉, 앞서 본 s[5:12]와 아래의 slice 객체를 사용하는 코드는 동일한 역할을 합니다.
CPython에서는 unicode_subscript라는 함수를 통해 이 동작을 구현합니다. 이 함수는 인덱싱과 슬라이싱을 모두 처리하는데, 전달된 item이 정수 인덱스라면 개별 문자를 반환하고 slice 객체라면 하위 문자열을 반환하도록 되어 있습니다. (cpython 코드 원본 참고)
이어지는 코드는 슬라이싱하는 상황(slice 객체를 사용)의 핵심 로직 중 일부로, 먼저 슬라이스 범위에 해당하는 길이 slicelength를 계산한 다음, 이 값에 따라 세 갈래의 처리를 합니다.
1. 빈 문자열 슬라이싱: 만약 slicelength가 0 이하라면 즉, 슬라이싱 결과가 빈 문자열이라면 _Py_RETURN_UNICODE_EMPTY()를 호출합니다. 이 함수는 이름에서 의미하듯 비어있는 unicode를 반환하는 함수인데, 전역적으로 미리 정의된 빈 문자열 객체를 반환합니다. 즉, 임의의 문자열 s에 대해 s[1:1]처럼 정의할 수 있는 빈 문자열 “”는 CPython에 미리 하나만 생성해 두고, 모든 곳에서 참조하는 싱글톤(singleton)이기 때문에, 불필요하게 매번 새로운 객체를 만들지 않습니다.
2. 전체 문자열 슬라이싱: start == 0, step == 1 그리고 원본 문자열의 길이와 slicelength가 같아 전체 문자열을 슬라이싱 하는 경우, 새로운 객체를 만들지 않고 원본 문자열 객체를 그대로 반환합니다. 이 방법 덕분에 아래와 같이 문자열을 같은 값으로 새롭게 정의하는 것은 다른 오브젝트로 계산되지만, 전체 문자열을 슬라이싱하는 정의 방식은 같은 오브젝트로 계산됩니다. 이를 통해 불필요한 메모리 복사를 아낄 수 있습니다.
3. 부분 문자열 슬라이싱 (새 객체 생성): 앞서 다룬 두 가지 외의 일반적인 슬라이싱을 하는 경우로, 슬라이싱한 내용을 담은 새로운 문자열 객체를 생성합니다. 여기서도 한가지 눈여겨 볼 부분은 코드가 step == 1인 경우와 step != 1인 경우로 나뉘는 것입니다.
다시 정리하면, 미리 “상수”처럼 정의된 빈 문자열을 반환하거나, 전체 문자열을 그대로 슬라이싱하여 원본 객체를 반환하는 경우가 아니라면 슬라이싱은 항상 새로운 문자열 객체를 생성하고, 내용 문자를 복사하여 채웁니다. 이는 o(n) 시간 복잡도를 갖습니다. (시간 복잡도에 대해서는아티클을 참고하세요.)
위에서 살펴본 구현으로부터, 슬라이싱 결과 문자열은 원본과 분리된 별도의 객체임을 알 수 있습니다. 따라서 전체 슬라이스의 예외를 빼면 원본 문자열 객체는 슬라이싱 후에도 참조 카운트가 변하지 않으며, 새로 만들어진 하위 문자열 객체의 수명이 독자적으로 관리됩니다. 이러한 설계에는 여러 가지 이유와 부가적인 내부 최적화가 존재합니다.
위 내용을 바탕으로, 실제 파이썬 코드 예제가 내부에서 어떻게 처리되는지 다시 한번 따라가 보겠습니다. 예시로는 아래의 코드를 사용합니다.
이 코드에 대한 CPython 내부 처리 과정은 다음과 같습니다.
이렇게 파이썬 문자열 슬라이싱의 기본적인 문법부터 시작하여, 인덱싱 방식, 파라미터의 역할, 문자열 불변성의 개념, 그리고 CPython 내부 구현까지 깊이 있게 살펴보았는데요.
문자열 슬라이싱은 파이썬에서 텍스트 데이터를 효율적으로 처리하기 위한 강력한 도구이며, 그 작동 원리를 정확히 이해하는 것은 사용자가 더욱 효과적이고 유연한 코드를 작성하는 데 필수적입니다. 기본 문법과 다양한 활용법을 숙지하고, 인덱싱 방식과 파라미터의 역할을 명확히 이해하며, 문자열의 불변성이라는 핵심 개념을 기억한다면, 파이썬을 이용한 문자열 조작 능력을 한 단계 더 향상시킬 수 있을 겁니다.
더 나아가 CPython 소스 코드 탐색을 통해, 슬라이싱의 내부 작동 방식을 이해한다면 파이썬 언어 자체에 대한 깊이 있는 통찰력을 얻을 수 있죠. 앞으로도 파이썬 문서, 튜토리얼, 그리고 다양한 문제 해결을 통해 문자열 슬라이싱을 포함한 프로그래밍 능력을 더욱 발전시킬 수 있길 기대합니다.
©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.