Python / len() / 문자열의 길이 반환하는 함수

Python의 len(text)는 문자열을 이루는 유니코드 코드 포인트의 개수를 정수로 반환하며 공백과 줄바꿈도 셉니다. 바이트 수나 화면에서 사람이 한 글자로 느끼는 단위와 항상 같지는 않습니다. 리스트·튜플·딕셔너리 등 길이를 지원하는 컬렉션에는 항목 수를 반환합니다.

기본 사용법과 공백·줄바꿈

len()은 길이를 지원하는 객체를 인수 하나로 받습니다. 빈 문자열은 0이며 따옴표 자체는 길이에 포함되지 않습니다. 문자열의 유니코드 코드 포인트는 각 문자 요소를 식별하는 값으로, Python의 str 자료형이 다루는 단위입니다.

Python 3에서 전체 예제를 UTF-8의 length_example.py로 저장한 뒤 python length_example.py로 실행합니다. 환경에 따라 python3을 사용하며 각 예제는 파일 내용을 교체해 독립적으로 실행할 수 있습니다.

for text in ("abc", "", "a b", "a\nb"):
    print(repr(text), len(text))

예상 결과:

'abc' 3
'' 0
'a b' 3
'a\nb' 3

"a b"에는 가운데 공백이, "a\nb"에는 줄바꿈이 하나 있어 각각 3입니다. 소스 코드의 \n은 두 글자를 입력한 표기이지만 문자열 값에서는 줄바꿈 하나를 나타냅니다. repr()로 출력하면 그 차이를 눈으로 확인하기 쉽습니다.

문자열 길이와 UTF-8 바이트 수

문자열을 UTF-8로 인코딩하면 저장·전송에 쓰는 bytes가 됩니다. 같은 내용이라도 len(text)는 코드 포인트 수, len(text.encode("utf-8"))는 UTF-8 바이트 수입니다. 다음 예제의 \u00e9는 악센트가 붙은 문자 é를 나타냅니다.

text = "caf\u00e9"
data = text.encode("utf-8")
print(len(text))
print(len(data))
4
5

문자열은 4개 코드 포인트지만 마지막 문자가 UTF-8에서 2바이트이므로 전체는 5바이트입니다. 파일·통신 규격의 제한이 바이트 기준이라면 실제 지정된 인코딩으로 계산합니다. len()이 반환한 문자열 길이를 그대로 메모리 사용량으로 해석해서도 안 됩니다.

결합 문자와 이모지는 보이는 글자 수와 다를 수 있습니다

é는 하나의 코드 포인트 또는 기본 문자 e와 결합 악센트 두 개의 코드 포인트로 표현할 수 있습니다. 모양이 비슷해도 문자열 값과 길이는 다를 수 있습니다. 여러 코드 포인트가 한 글자처럼 묶여 보이는 단위는 유니코드 문자소 클러스터로 구분합니다.

import unicodedata

composed = "\u00e9"
decomposed = "e\u0301"
emoji = "\U0001f469\u200d\U0001f4bb"
print(len(composed), len(decomposed))
print(composed == decomposed)
print(len(unicodedata.normalize("NFC", decomposed)))
print(len(emoji))
1 2
False
1
3

unicodedata.normalize("NFC", text)는 유니코드 정규화로 정준적으로 결합 가능한 표현을 합성합니다. 여기서는 길이 2인 표현을 1로 만들지만, 모든 이모지나 결합을 코드 포인트 하나로 바꾸는 함수는 아닙니다. 예제의 여성 개발자 이모지 시퀀스는 3개 코드 포인트로 구성됩니다.

사용자 화면의 “글자 수” 제한을 구현하려면 코드 포인트·문자소 클러스터·바이트 중 어떤 기준인지 먼저 정합니다. NFC 정규화만으로 문자소 계산을 대신하지 말고, 해당 유니코드 분할 규칙을 지원하는 구현과 실제 입력 사례로 확인해야 합니다.

컬렉션의 항목 수와 지원하지 않는 값

문자열 외에도 리스트·튜플·딕셔너리·집합 등에 사용할 수 있습니다. 딕셔너리는 키 수, 집합은 중복을 제거한 원소 수를 셉니다. 중첩 리스트에서는 바깥 항목 수이며 내부 값을 자동으로 모두 더하지 않습니다.

print(len([10, 20, 30]))
print(len((10, 20)))
print(len({"a": 10, "b": 20}))
print(len({1, 1, 2}))
rows = [[1, 2], [3, 4]]
print(len(rows))
print(sum(len(row) for row in rows))
3
2
2
2
2
4

숫자나 None에는 길이가 정의되지 않아 TypeError가 발생합니다. 숫자의 표시 글자 수가 필요하면 먼저 문자열로 바꿀 수 있지만, 음수 부호와 소수점도 길이에 포함됩니다. 반복 가능한 객체라고 해서 모두 len()을 지원하는 것은 아니며 일반적인 제너레이터에도 길이가 없습니다.

for value in (1234, None):
    try:
        print(len(value))
    except TypeError:
        print("TypeError")
print(len(str(1234)))

items = (n * 2 for n in range(3))
try:
    print(len(items))
except TypeError:
    print("TypeError")
values = list(items)
print(len(values))
print(list(items))
TypeError
TypeError
4
TypeError
3
[]

제너레이터를 리스트로 바꾸면 항목 수를 구할 수 있지만, 그 과정에서 원소를 소비하고 메모리에 담습니다. 예제에서 다시 list(items)를 호출하면 빈 리스트가 나옵니다. 대량 데이터나 무한 반복자를 길이 확인만을 위해 리스트로 바꾸지 않습니다.

입력 길이를 검사하는 전체 예제

다음 함수는 문자열만 받아 앞뒤 공백을 제거한 값의 코드 포인트 수가 1~12인지 확인합니다. 빈 문자열과 공백만 있는 입력은 거부하며, 함수가 원래 문자열 자체를 수정하는 것은 아닙니다.

def valid_name(text):
    if not isinstance(text, str):
        raise TypeError("name must be a string")
    name = text.strip()
    return 1 <= len(name) <= 12

for text in ("Ada", "   ", "a" * 13, "  Ada  "):
    print(repr(text), valid_name(text))
'Ada' True
'   ' False
'aaaaaaaaaaaaa' False
'  Ada  ' True

이 검사는 문자열의 내용이나 문자 종류까지 검증하지 않습니다. 모든 공백을 제거하거나 화면상의 글자 수를 제한하는 규칙도 아닙니다. 서비스가 정한 길이 단위와 공백 처리, 허용 문자, 서버 측 검증을 일치시켜야 같은 입력을 화면과 저장 단계에서 다르게 판단하는 일을 줄일 수 있습니다.