2. 어휘 분석
2. 어휘 분석 (Lexical analysis)
Python 프로그램은 *파서(parser)*가 읽어요. 파서로 들어오는 입력은 *어휘 분석기(lexical analyzer, 토크나이저tokenizer라고도 불러요)가 만들어낸 토큰 스트림이에요. 이 장에서는 어휘 분석기가 어떻게 이런 토큰들을 만들어내는지 설명하겠습니다.
본문
Python 프로그램은 *파서(parser)*가 읽어요. 파서로 들어오는 입력은 *어휘 분석기(lexical analyzer, 토크나이저tokenizer라고도 불러요)가 만들어낸 토큰 스트림이에요. 이 장에서는 어휘 분석기가 이 토큰들을 어떻게 만들어내는지 설명할게요.
어휘 분석기는 프로그램 텍스트의 인코딩(기본값은 UTF-8)을 결정하고, 그 텍스트를 소스 문자(source character)로 디코딩해요. 텍스트를 디코딩할 수 없으면 SyntaxError가 발생해요.
다음으로 어휘 분석기는 소스 문자를 사용해 토큰 스트림을 생성해요. 생성되는 토큰의 종류는 일반적으로 처리할 다음 소스 문자에 따라 결정돼요. 마찬가지로 분석기의 다른 특별한 동작들도 아직 처리되지 않은 첫 소스 문자에 의존해요. 다음 표는 이런 소스 문자들에 대한 빠른 요약을 보여주고, 더 자세한 정보가 담긴 섹션으로 연결해 줘요.
| 문자 | 다음 토큰(또는 관련 문서) |
|---|---|
| space, tab, formfeed (공백, 탭, 폼피드) | Whitespace |
| CR, LF | New line, Indentation |
backslash (\) |
Explicit line joining, (문자열 이스케이프 시퀀스에서도 유의미) |
hash (#) |
Comment |
quote (', ") |
String literal |
ASCII letter (a-z, A-Z), non-ASCII character |
Name, Prefixed string or bytes literal |
underscore (_) |
Name, (숫자 리터럴의 일부가 될 수도 있어요) |
number (0-9) |
Numeric literal |
dot (.) |
Numeric literal, Operator |
question mark (?), dollar ($), backquote (`), control character |
Error (문자열 리터럴과 주석 밖에서) |
| other printing character | Operator or delimiter |
| end of file | End marker |
2.1. 행 구조 (Line structure)
Python 프로그램은 여러 개의 *논리 행(logical line)*으로 나뉘어요.
2.1.1. 논리 행 (Logical lines)
논리 행의 끝은 NEWLINE 토큰으로 표현돼요. 문장(statement)은 문법이 NEWLINE을 허용하는 경우(예: 복합 문장 안의 문장 사이)를 제외하고는 논리 행의 경계를 넘을 수 없어요. 논리 행은 행 결합(line joining) 규칙을 명시적 또는 암시적으로 따라 하나 이상의 *물리 행(physical line)*으로 구성돼요.
2.1.2. 물리 행 (Physical lines)
물리 행은 다음 종료 시퀀스 중 하나로 끝나는 문자들의 연속이에요:
- ASCII LF(Linefeed)를 사용하는 Unix 형식
- ASCII CR LF(Return 다음에 Linefeed) 시퀀스를 사용하는 Windows 형식
- ASCII CR(Return) 문자를 사용하는 'Classic Mac OS' 형식
플랫폼과 관계없이 이 시퀀스들은 각각 단일 ASCII LF(Linefeed) 문자로 대체돼요. (이것은 문자열 리터럴 안에서도 이루어져요.) 각 행은 이 시퀀스 중 아무거나 사용할 수 있고, 한 파일 안에서 일관될 필요는 없어요.
입력의 끝(end of input)은 마지막 물리 행의 암시적인 종결자 역할을 해요.
형식적으로는:
newline: <ASCII LF> | <ASCII CR> <ASCII LF> | <ASCII CR>
2.1.3. 주석 (Comments)
주석은 문자열 리터럴의 일부가 아닌 해시 문자(#)로 시작해서 물리 행의 끝에서 끝나요. 암시적 행 결합 규칙이 호출되지 않는 한 주석은 논리 행의 끝을 표시해요. 주석은 문법에서 무시돼요.
2.1.4. 인코딩 선언 (Encoding declarations)
Python 스크립트의 첫 번째 또는 두 번째 줄에 있는 주석이 정규식 coding[=:]\s*([-\w.]+)와 일치하면, 이 주석은 인코딩 선언으로 처리돼요. 이 식의 첫 번째 그룹이 소스 코드 파일의 인코딩을 지칭해요. 인코딩 선언은 반드시 한 줄을 온전히 차지해야 해요. 두 번째 줄에 있다면 첫 번째 줄도 주석만 있는 줄이어야 해요. 인코딩 표현식의 권장 형식은 다음과 같아요.
# -*- coding: <encoding-name> -*-
이 형식은 GNU Emacs도 인식하고,
# vim:fileencoding=<encoding-name>
이 형식은 Bram Moolenaar의 VIM이 인식해요.
인코딩 선언이 없으면 기본 인코딩은 UTF-8이에요. 파일의 암시적 또는 명시적 인코딩이 UTF-8인 경우, 앞에 붙은 UTF-8 바이트 순서 표시(before BOM, b'\xef\xbb\xbf')는 문법 오류가 아니라 무시돼요.
인코딩이 선언되면, 인코딩 이름은 Python이 인식할 수 있어야 해요 (표준 인코딩 참조). 그 인코딩은 문자열 리터럴, 주석, 식별자를 포함한 모든 어휘 분석에 사용돼요.
문자열 리터럴, 주석, 식별자를 포함한 모든 어휘 분석은 소스 인코딩으로 디코딩된 Unicode 텍스트에 대해 이루어져요. NUL 제어 문자를 제외한 모든 Unicode 코드 포인트는 Python 소스에 나타날 수 있어요.
source_character: <any Unicode code point, except NUL>
2.1.5. 명시적 행 결합 (Explicit line joining)
역슬래시(\) 문자를 사용해 둘 이상의 물리 행을 논리 행으로 결합할 수 있어요. 방법은 이래요. 물리 행이 문자열 리터럴이나 주석의 일부가 아닌 역슬래시로 끝나면, 그 역슬래시와 다음의 행 끝 문자를 삭제하고 다음 행과 결합해 하나의 논리 행을 만들어요. 예를 들어:
if 1900 < year < 2100 and 1 <= month <= 12 \
and 1 <= day <= 31 and 0 <= hour < 24 \
and 0 <= minute < 60 and 0 <= second < 60: # Looks like a valid date
return 1
역슬래시로 끝나는 줄은 주석을 가질 수 없어요. 역슬래시는 주석을 계속 이어주지 않아요. 역슬래시는 문자열 리터럴을 제외한 토큰을 계속 이어주지 않아요 (즉, 문자열 리터럴이 아닌 토큰은 역슬래시를 사용해 물리 행을 가로질러 분할할 수 없어요). 역슬래시는 문자열 리터럴 밖의 줄에 있는 다른 위치에서는 불법이에요.
2.1.6. 암시적 행 결합 (Implicit line joining)
괄호, 대괄호, 중괄호 안의 표현식은 역슬래시 없이 여러 물리 행에 걸쳐 분할할 수 있어요. 예를 들어:
month_names = ['Januari', 'Februari', 'Maart', # These are the
'April', 'Mei', 'Juni', # Dutch names
'Juli', 'Augustus', 'September', # for the months
'Oktober', 'November', 'December'] # of the year
암시적으로 이어진 행은 주석을 가질 수 있어요. 이어지는 행의 들여쓰기는 중요하지 않아요. 빈 연속 행도 허용돼요. 암시적 연속 행 사이에는 NEWLINE 토큰이 없어요. 암시적으로 이어진 행은 삼중 따옴표 문자열 안에서도 발생할 수 있는데(아래 참조), 그 경우에는 주석을 가질 수 없어요.
2.1.7. 빈 줄 (Blank lines)
공백, 탭, 폼피드, 그리고 어쩌면 주석만 포함하는 논리 행은 무시돼요 (즉, NEWLINE 토큰이 생성되지 않아요). 문장을 대화형으로 입력하는 동안, 빈 줄의 처리는 read-eval-print 루프의 구현에 따라 달라질 수 있어요. 표준 대화형 인터프리터에서 완전히 빈 논리 행(즉, 공백이나 주석조차 포함하지 않는 행)은 여러 줄 문장을 종료시켜요.
2.1.8. 들여쓰기 (Indentation)
논리 행 시작 부분의 선행 공백(스페이스와 탭)은 그 행의 들여쓰기 레벨을 계산하는 데 사용돼요. 들여쓰기 레벨은 다시 문장들의 그룹화를 결정하는 데 사용돼요.
탭은 (왼쪽에서 오른쪽으로) 하나에서 여덟 개의 스페이스로 대체되는데, 대체를 포함한 총 문자가 8의 배수가 되도록 해요 (Unix에서 사용하는 것과 동일한 규칙을 의도했어요). 첫 번째 비어있지 않은 문자 앞에 오는 스페이스의 총 개수가 그 행의 들여쓰기를 결정해요. 들여쓰기는 역슬래시를 사용해 여러 물리 행으로 분할할 수 없어요. 첫 번째 역슬래시까지의 공백이 들여쓰기를 결정해요.
소스 파일이 탭을 스페이스 몇 개로 보는지에 따라 의미가 달라질 정도로 탭과 스페이스를 섞으면 들여쓰기가 일관되지 않은 것으로 거부되고, 그 경우 TabError가 발생해요.
크로스 플랫폼 호환성 참고: non-UNIX 플랫폼의 텍스트 편집기 특성 때문에, 단일 소스 파일에서 들여쓰기에 스페이스와 탭을 섞어 쓰는 것은 바람직하지 않아요. 또한 플랫폼에 따라 최대 들여쓰기 레벨을 명시적으로 제한할 수도 있다는 점을 알아두세요.
폼피드 문자는 행의 시작 부분에 나타날 수 있으며, 위의 들여쓰기 계산에서는 무시돼요. 선행 공백의 다른 위치에서 나타나는 폼피드 문자는 정의되지 않은 효과를 가져요 (예를 들어, 스페이스 개수를 0으로 재설정할 수도 있어요).
연속된 행들의 들여쓰기 레벨은 스택을 사용해 INDENT와 DEDENT 토큰을 생성하는 데 사용돼요. 방법은 이래요.
파일의 첫 행을 읽기 전에 단일 0이 스택에 푸시돼요. 이것은 다시는 팝되지 않아요. 스택에 푸시되는 숫자들은 아래에서 위로 항상 엄격하게 증가해요. 각 논리 행의 시작에서 그 행의 들여쓰기 레벨이 스택의 맨 위와 비교돼요. 같으면 아무 일도 일어나지 않아요. 크면 스택에 푸시되고 INDENT 토큰 하나가 생성돼요. 작으면, 그것은 반드시 스택에 있는 숫자 중 하나여야 해요. 스택에서 더 큰 모든 숫자가 팝되고, 팝된 숫자마다 DEDENT 토큰이 생성돼요. 파일의 끝에서, 스택에 남아 있는 0보다 큰 각 숫자에 대해 DEDENT 토큰이 생성돼요.
다음은 올바르게 (혼란스럽게 보일지라도) 들여쓰기된 Python 코드의 예시예요:
def perm(l):
# Compute the list of all permutations of l
if len(l) <= 1:
return [l]
r = []
for i in range(len(l)):
s = l[:i] + l[i+1:]
p = perm(s)
for x in p:
r.append(l[i:i+1] + x)
return r
다음 예시는 여러 가지 들여쓰기 오류를 보여줘요:
def perm(l): # error: first line indented
for i in range(len(l)): # error: not indented
s = l[:i] + l[i+1:]
p = perm(l[:i] + l[i+1:]) # error: unexpected indent
for x in p:
r.append(l[i:i+1] + x)
return r # error: inconsistent dedent
(사실, 처음 세 오류는 파서가 감지하고, 마지막 오류만 어휘 분석기가 찾아내요 — return r의 들여쓰기가 스택에서 팝된 레벨과 일치하지 않거든요.)
2.1.9. 토큰 사이의 공백 (Whitespace between tokens)
논리 행의 시작이나 문자열 리터럴 안을 제외하면, 스페이스, 탭, 폼피드 같은 공백 문자들은 토큰을 구분하는 데 서로 바꿔 쓸 수 있어요:
whitespace: ' ' | tab | formfeed
공백은 두 토큰을 이어붙였을 때 다른 토큰으로 해석될 수 있는 경우에만 필요해요. 예를 들어 ab는 한 토큰이지만 a b는 두 토큰이에요. 그러나 +a와 + a는 둘 다 +와 a 두 토큰을 만들어요. +a는 유효한 토큰이 아니기 때문이에요.
2.1.10. 끝 표시 (End marker)
비대화형 입력의 끝에서 어휘 분석기는 ENDMARKER 토큰을 생성해요.
2.2. 기타 토큰 (Other tokens)
NEWLINE, INDENT, DEDENT 외에도 다음 토큰 범주가 존재해요: *식별자(identifier)*와 키워드(keyword) (NAME), 리터럴(literal) (NUMBER와 STRING 같은 것), 그리고 기타 기호 (*연산자(operator)*와 구분 기호(delimiter), OP). 공백 문자(앞에서 설명한 논리 행 종결자를 제외한)는 토큰이 아니지만 토큰을 구분하는 역할을 해요. 모호함이 있는 경우, 토큰은 왼쪽에서 오른쪽으로 읽었을 때 유효한 토큰을 형성하는 가장 긴 문자열로 구성돼요.
2.3. 이름 (Names: identifiers and keywords)
NAME 토큰은 식별자, 키워드, 소프트 키워드를 나타내요.
이름은 다음 문자들로 구성돼요:
- 대문자와 소문자 (
A-Z와a-z) - 밑줄(
_) - 숫자(
0부터9) — 첫 문자로는 올 수 없어요 - 비-ASCII 문자 — 유효한 이름은 "문자형"과 "숫자형" 문자만 포함할 수 있어요. 자세한 내용은 이름의 비-ASCII 문자를 참고하세요.
이름은 최소 하나의 문자를 포함해야 하지만, 상한 길이 제한은 없어요. 대소문자는 구분돼요.
형식적으로 이름은 다음 어휘 정의로 설명돼요:
NAME: name_start name_continue*
name_start: "a"..."z" | "A"..."Z" | "_" | NAME, except keywords>
이 문법이 매칭하는 모든 이름이 유효한 것은 아니라는 점을 유의하세요. 자세한 내용은 이름의 비-ASCII 문자를 참고해요.
2.3.1. 키워드 (Keywords)
다음 이름들은 예약어, 즉 언어의 키워드로 사용되며 일반 식별자로 사용할 수 없어요. 반드시 여기 적힌 대로 정확히 철자해야 해요:
False await else import pass
None break except in raise
True class finally is return
and continue for lambda try
as def from nonlocal while
assert del global not with
async elif if or yield
2.3.2. 소프트 키워드 (Soft Keywords)
버전 3.10에 추가되었어요.
일부 이름은 특정 맥락에서만 예약돼요. 이런 이름을 소프트 키워드라고 해요:
match문에서 사용될 때의match,case,_type문에서 사용될 때의type
이런 이름들은 각각의 특정 맥락에서 문법적으로 키워드처럼 동작하지만, 이 구분은 토큰화가 아니라 파서 레벨에서 이루어져요.
소프트 키워드이기 때문에, 이 이름들을 식별자 이름으로 사용하는 기존 코드와의 호환성을 유지하면서도 문법에서 사용할 수 있어요.
버전 3.12에서 변경: type이 이제 소프트 키워드예요.
2.3.3. 예약된 식별자 클래스 (Reserved classes of identifiers)
(키워드 외에) 특별한 의미를 가진 특정 식별자 클래스들이 있어요. 이 클래스들은 선행 및 후행 밑줄 문자의 패턴으로 식별돼요.
_*:from module import *로 가져오지 않아요._:match문의case패턴 안에서_는 와일드카드(wildcard)를 나타내는 소프트 키워드예요. 별도로, 대화형 인터프리터는 마지막 평가 결과를_변수에 제공해요. (print같은 내장 함수와 함께builtins모듈에 저장돼 있어요.) 그 외의 곳에서_는 일반 식별자예요. "특별한" 항목에 이름을 붙이는 데 자주 사용되지만, Python 자체에 특별한 것은 아니에요.- 참고:
_이름은 국제화(i18n)와 함께 자주 사용돼요. 이 관례에 대한 자세한 정보는gettext모듈 문서를 참고하세요. 또한 사용하지 않는 변수를 나타내는 데도 흔히 쓰여요.
- 참고:
__*__: 시스템 정의 이름으로, 비공식적으로 "dunder" 이름이라고 불러요. 이 이름들은 인터프리터와 그 구현(표준 라이브러리 포함)이 정의해요. 현재 시스템 이름은 특별 메서드 이름 섹션과 다른 곳에서 다뤄져요. 미래 버전의 Python에서 더 추가될 가능성이 높아요. 명시적으로 문서화된 용도에 따르지 않는__*__이름의 어떤 사용도 경고 없이 깨질 수 있어요.__*: 클래스 비공개 이름이에요. 이 범주의 이름은 클래스 정의 맥락에서 사용될 때, 기본(derived) 클래스와 파생(base) 클래스의 "비공개" 속성 사이의 이름 충돌을 피하기 위해 맹글링된(mangled) 형태로 재작성돼요. 이름 식별자(Names) 섹션을 참고하세요.
2.3.4. 이름의 비-ASCII 문자 (Non-ASCII characters in names)
비-ASCII 문자를 포함하는 이름은 위에서 설명한 규칙과 문법 외에 추가적인 정규화와 검증이 필요해요. 예를 들어 ř_1, 蛇, साँप는 유효한 이름이지만, r〰2, €, 🐍는 아니에요.
이 섹션은 정확한 규칙을 설명해요.
모든 이름은 파싱 중에 정규화 형식 NFKC로 변환돼요. 이는 예를 들어 일부 문자의 타이포그래피 변형이 "기본" 형태로 변환된다는 뜻이에요. 예를 들어 fiⁿₐˡᵢᶻₐᵗᵢᵒₙ은 finalization으로 정규화되므로, Python은 이 둘을 같은 이름으로 취급해요:
>>> fiⁿₐˡᵢᶻₐᵗᵢᵒₙ = 3
>>> finalization
3
참고: 정규화는 어휘 레벨에서만 이루어져요. 이름을 문자열로 받는 런타임 함수들은 일반적으로 인자를 정규화하지 않아요. 예를 들어 위에서 정의한 변수는 런타임에 globals() 사전에서 globals()["finalization"]로 접근할 수 있지만 globals()["fiⁿₐˡᵢᶻₐᵗᵢᵒₙ"]로는 접근할 수 없어요.
ASCII 전용 이름이 문자, 숫자, 밑줄만 포함하고 숫자로 시작할 수 없는 것과 유사하게, 유효한 이름은 "문자형" 집합 xid_start에 속한 문자로 시작해야 하고, 나머지 문자는 "문자 및 숫자형" 집합 xid_continue에 속해야 해요.
이 집합들은 Unicode 표준 부속서 UAX-31이 정의하는 XID_Start와 XID_Continue 집합에 기반해요. Python의 xid_start는 추가로 밑줄(_)을 포함해요. Python이 반드시 UAX-31을 준수하는 것은 아니라는 점을 유의하세요.
Unicode Charater Database의 DerivedCoreProperties.txt 파일에서 Unicode가 정의하는 XID_Start와 XID_Continue 집합의 문자들에 대한 비규범적 목록을 볼 수 있어요. 참고로 xid_* 집합의 구성 규칙은 아래에 나와 있어요.
집합 id_start는 다음의 합집합으로 정의돼요:
- Unicode 범주
<Lu>— 대문자 (A부터 Z까지 포함) - Unicode 범주
<Ll>— 소문자 (a부터 z까지 포함) - Unicode 범주
<Lt>— 타이틀케이스 문자 - Unicode 범주
<Lm>— 수식 문자(modifier letter) - Unicode 범주
<Lo>— 기타 문자 - Unicode 범주
<Nl>— 문자 숫자(letter number) {"_"}— 밑줄<other_id_start>— 역방향 호환성을 지원하기 위한 PropList.txt의 명시적 문자 집합
그런 다음 집합 xid_start는 정규화가 id_start id_continue* 형태가 아닌 모든 문자를 제거함으로써 NFKC 정규화 아래에서 이 집합을 닫아요.
집합 id_continue는 다음의 합집합으로 정의돼요:
id_start(위 참조)- Unicode 범주
<Nd>— 십진수 (0부터 9까지 포함) - Unicode 범주
<Pc>— 연결 구두점(connector punctuation) - Unicode 범주
<Mn>— 비간격 부호(nonspacing mark) - Unicode 범주
<Mc>— 간격 결합 부호(spacing combining mark) <other_id_continue>— 역방향 호환성을 지원하기 위한 PropList.txt의 또 다른 명시적 문자 집합
다시, xid_continue는 NFKC 정규화 아래에서 이 집합을 닫아요.
Unicode 범주는 unicodedata 모듈에 포함된 버전의 Unicode Character Database를 사용해요.
참고 자료
- PEP 3131 – Supporting Non-ASCII Identifiers
- PEP 672 – Unicode-related Security Considerations for Python
2.4. 리터럴 (Literals)
리터럴은 일부 내장 타입의 상수 값에 대한 표기법이에요.
어휘 분석 측면에서, Python에는 문자열, 바이트, 숫자 리터럴이 있어요.
다른 "리터럴"들은 키워드(None, True, False)와 특별한 줄임표 토큰(...)으로 어휘적으로 나타내요.
2.5. 문자열 및 바이트 리터럴 (String and Bytes literals)
문자열 리터럴은 작은따옴표(') 또는 큰따옴표(")로 둘러싸인 텍스트예요. 예를 들어:
"spam"
'eggs'
리터럴을 시작하는 데 사용한 따옴표가 그것을 끝내기도 해요. 그래서 문자열 리터럴은 (이스케이프 시퀀스, 아래 참조를 제외하면) 다른 종류의 따옴표만 포함할 수 있어요. 예를 들어:
'Say "Hello", please.'
"Don't do that!"
이 제한을 제외하면, 따옴표 문자(' 또는 ")의 선택은 리터럴이 파싱되는 방식에 영향을 주지 않아요.
문자열 리터럴 안에서 역슬래시(\) 문자는 이스케이프 시퀀스를 시작해요. 이스케이프 시퀀스는 역슬래시 뒤의 문자에 따라 특별한 의미를 가져요. 예를 들어 \"는 큰따옴표 문자를 나타내며 문자열을 끝내지 않아요:
>>> print("Say \"Hello\" to everyone!")
Say "Hello" to everyone!
이런 시퀀스의 전체 목록과 더 자세한 내용은 아래의 이스케이프 시퀀스를 참고하세요.
2.5.1. 삼중 따옴표 문자열 (Triple-quoted strings)
문자열은 세 개의 작은따옴표 또는 큰따옴표의 짝이 맞는 그룹으로도 둘러쌀 수 있어요. 이것들은 일반적으로 삼중 따옴표 문자열이라고 불러요:
"""This is a triple-quoted string."""
삼중 따옴표 리터럴에서는 이스케이프되지 않은 따옴표가 허용되고 (그리고 유지되며), 단 시작할 때 사용한 것과 같은 종류(' 또는 ")의 이스케이프되지 않은 따옴표 세 개가 연속으로 나오면 리터럴을 끝내요:
"""This string has "quotes" inside."""
이스케이프되지 않은 새 줄도 허용되고 유지돼요:
'''This triple-quoted string
continues on the next line.'''
2.5.2. 문자열 접두사 (String prefixes)
문자열 리터럴은 리터럴의 내용이 파싱되는 방식에 영향을 주는 선택적인 접두사를 가질 수 있어요. 예를 들어:
b"data"
f'{result=}'
허용되는 접두사는:
b: 바이트 리터럴r: 원시 문자열(raw string)f: 포맷 문자열 리터럴("f-string")t: 템플릿 문자열 리터럴("t-string")u: 효과 없음 (역방향 호환성을 위해 허용)
각 타입에 대한 자세한 내용은 연결된 섹션을 참고하세요.
접두사는 대소문자를 구분하지 않아요 (예: 'B'는 'b'와 동일하게 동작해요). 'r' 접두사는 'f', 't', 'b'와 결합될 수 있어서, 'fr', 'rf', 'tr', 'rt', 'br', 'rb'도 유효한 접두사예요.
버전 3.3에서 추가: 원시 바이트 리터럴의 'rb' 접두사가 'br'의 동의어로 추가되었어요.
Unicode 레거시 리터럴(u'value')에 대한 지원은 Python 2.x와 3.x 코드베이스를 동시에 유지 관리하는 것을 단순화하기 위해 다시 도입되었어요. 자세한 내용은 PEP 414를 참고하세요.
2.5.3. 형식 문법 (Formal grammar)
"f-strings"과 "t-strings"을 제외한 문자열 리터럴은 다음 어휘 정의로 설명돼요.
이 정의들은 끝나는 따옴표가 리터럴을 끝낸다는 것을 나타내는 음의 lookahead(!)를 사용해요.
STRING: [stringprefix] (stringcontent)
stringprefix: <("r" | "u" | "b" | "br" | "rb"), case-insensitive>
stringcontent:
| "'''" ( !"'''" longstringitem)* "'''"
| '"""' ( !'"""' longstringitem)* '"""'
| "'" ( !"'" stringitem)* "'"
| '"' ( !'"' stringitem)* '"'
stringitem: stringchar | stringescapeseq
stringchar: <any source_character, except backslash and newline>
longstringitem: stringitem | newline
stringescapeseq: "\" <any source_character>
모든 어휘 정의에서와 마찬가지로 공백이 유의미하다는 점을 유의하세요. 특히 (있다면) 접두사 바로 뒤에 시작 따옴표가 와야 해요.
2.5.4. 이스케이프 시퀀스 (Escape sequences)
'r' 또는 'R' 접두사가 없으면, 문자열 및 바이트 리터럴의 이스케이프 시퀀스는 표준 C가 사용하는 것과 유사한 규칙에 따라 해석돼요. 인식되는 이스케이프 시퀀스는:
| 이스케이프 시퀀스 | 의미 |
|---|---|
\\ |
무시되는 행 끝 |
\\\\ |
역슬래시 |
\' |
작은따옴표 |
\" |
큰따옴표 |
\a |
ASCII Bell (BEL) |
\b |
ASCII Backspace (BS) |
\f |
ASCII Formfeed (FF) |
\n |
ASCII Linefeed (LF) |
\r |
ASCII Carriage Return (CR) |
\t |
ASCII Horizontal Tab (TAB) |
\v |
ASCII Vertical Tab (VT) |
\ooo |
8진수 문자 |
\xhh |
16진수 문자 |
\N{name} |
이름이 있는 Unicode 문자 |
\uxxxx |
16진수 Unicode 문자 |
\Uxxxxxxxx |
16진수 Unicode 문자 |
2.5.4.1. 무시되는 행 끝 (Ignored end of line)
줄 끝에 역슬래시를 추가해 새 줄을 무시할 수 있어요:
>>> 'This string will not include \
... backslashes or newline characters.'
'This string will not include backslashes or newline characters.'
삼중 따옴표 문자열이나 괄호와 문자열 리터럴 연결을 사용해도 같은 결과를 얻을 수 있어요.
2.5.4.2. 이스케이프된 문자 (Escaped characters)
비원시(non-raw) Python 문자열 리터럴에 역슬래시를 포함하려면 역슬래시를 두 번 써야 해요. \\ 이스케이프 시퀀스는 단일 역슬래시 문자를 나타내요:
>>> print('C:\\Program Files')
C:\Program Files
마찬가지로 \'와 \" 시퀀스는 각각 작은따옴표와 큰따옴표 문자를 나타내요:
>>> print('\' and \"')
' and "
2.5.4.3. 8진수 문자 (Octal character)
\ooo 시퀀스는 8진수(base 8) 값 ooo를 가진 문자를 나타내요:
>>> '\120'
'P'
최대 세 자리 8진수(0부터 7)가 허용돼요.
바이트 리터럴에서 문자는 주어진 값을 가진 바이트를 의미해요. 문자열 리터럴에서는 주어진 값을 가진 Unicode 문자를 의미해요.
버전 3.11에서 변경: 0o377(255)보다 큰 값을 가진 8진수 이스케이프는 DeprecationWarning을 생성해요.
버전 3.12에서 변경: 0o377(255)보다 큰 값을 가진 8진수 이스케이프는 SyntaxWarning을 생성해요. 미래의 Python 버전에서는 SyntaxError를 발생시킬 거예요.
2.5.4.4. 16진수 문자 (Hexadecimal character)
\xhh 시퀀스는 16진수(base 16) 값 hh를 가진 문자를 나타내요:
>>> '\x50'
'P'
표준 C와 달리 정확히 두 개의 16진수 자릿수가 필요해요.
바이트 리터럴에서 문자는 주어진 값을 가진 바이트를 의미해요. 문자열 리터럴에서는 주어진 값을 가진 Unicode 문자를 의미해요.
2.5.4.5. 이름이 있는 Unicode 문자 (Named Unicode character)
\N{name} 시퀀스는 주어진 이름을 가진 Unicode 문자를 나타내요:
>>> '\N{LATIN CAPITAL LETTER P}'
'P'
>>> '\N{SNAKE}'
'🐍'
이 시퀀스는 바이트 리터럴에는 나타날 수 없어요.
버전 3.3에서 변경: 이름 별칭(alias) 지원이 추가되었어요.
2.5.4.6. 16진수 Unicode 문자 (Hexadecimal Unicode characters)
\uxxxx와 \Uxxxxxxxx 시퀀스는 주어진 16진수(base 16) 값을 가진 Unicode 문자를 나타내요. \u에는 정확히 네 자릿수가 필요하고, \U에는 정확히 여덟 자릿수가 필요해요. 후자는 모든 Unicode 문자를 인코딩할 수 있어요.
>>> '\u1234'
'ሴ'
>>> '\U0001f40d'
'🐍'
이 시퀀스들은 바이트 리터럴에는 나타날 수 없어요.
2.5.4.7. 인식되지 않는 이스케이프 시퀀스 (Unrecognized escape sequences)
표준 C와 달리, 인식되지 않는 모든 이스케이프 시퀀스는 문자열에 그대로 남아요. 즉 역슬래시도 결과에 남아 있어요:
>>> print('\q')
\q
>>> list('\q')
['\\', 'q']
바이트 리터럴의 경우, 문자열 리터럴에서만 인식되는 이스케이프 시퀀스(\N..., \u..., \U...)는 인식되지 않는 이스케이프 범주에 들어간다는 점을 유의하세요.
버전 3.6에서 변경: 인식되지 않는 이스케이프 시퀀스는 DeprecationWarning을 생성해요.
버전 3.12에서 변경: 인식되지 않는 이스케이프 시퀀스는 SyntaxWarning을 생성해요. 미래의 Python 버전에서는 SyntaxError를 발생시킬 거예요.
2.5.5. 바이트 리터럴 (Bytes literals)
바이트 리터럴은 항상 'b' 또는 'B' 접두사가 붙어요. str 타입 대신 bytes 타입의 인스턴스를 생성해요. ASCII 문자만 포함할 수 있어요. 숫자 값이 128 이상인 바이트는 이스케이프 시퀀스(일반적으로 16진수 문자나 8진수 문자)로 표현해야 해요:
>>> b'\x89PNG\r\n\x1a\n'
b'\x89PNG\r\n\x1a\n'
>>> list(b'\x89PNG\r\n\x1a\n')
[137, 80, 78, 71, 13, 10, 26, 10]
마찬가지로, 0 바이트는 이스케이프 시퀀스(일반적으로 \0 또는 \x00)로 표현해야 해요.
2.5.6. 원시 문자열 리터럴 (Raw string literals)
문자열과 바이트 리터럴 모두 선택적으로 'r' 또는 'R' 문자를 접두사로 붙일 수 있어요. 이런 구문을 각각 원시 문자열 리터럴과 원시 바이트 리터럴이라고 하며, 역슬래시를 리터럴 문자로 취급해요. 그 결과 원시 문자열 리터럴에서는 이스케이프 시퀀스가 특별하게 취급되지 않아요:
>>> r'\d{4}-\d{2}-\d{2}'
'\\d{4}-\\d{2}-\\d{2}'
원시 리터럴에서도 따옴표는 역슬래시로 이스케이프할 수 있지만, 역슬래시는 결과에 남아 있어요. 예를 들어 r"\"는 역슬래시 하나와 큰따옴표 하나, 두 문자로 구성된 유효한 문자열 리터럴이에요. r"\"는 유효한 문자열 리터럴이 아니에요 (원시 문자열도 홀수 개의 역슬래시로 끝날 수 없어요). 구체적으로 원시 리터럴은 단일 역슬래시로 끝날 수 없어요 (역슬래시가 뒤의 따옴표 문자를 이스케이프할 것이기 때문이에요). 또한 단일 역슬래시 뒤에 새 줄이 오는 것은 리터럴의 일부인 두 문자로 해석되며, 행 연속(line continuation)으로 해석되지는 않는다는 점을 유의하세요.
2.5.7. f-strings
버전 3.6에 추가되었어요.
버전 3.7에서 변경: f-string 내 표현식에서 await와 async for를 사용할 수 있어요.
버전 3.8에서 변경: 디버그 지정자(=)가 추가되었어요.
버전 3.12에서 변경: f-string 내 표현식에 대한 많은 제한이 제거되었어요. 특히 중첩 문자열, 주석, 역슬래시가 이제 허용돼요.
포맷 문자열 리터럴 또는 f-string은 'f' 또는 'F' 접두사가 붙은 문자열 리터럴이에요. 다른 문자열 리터럴과 달리 f-string은 상수 값을 가지지 않아요. 중괄호 {}로 구분된 *교체 필드(replacement field)*를 포함할 수 있어요. 교체 필드는 런타임에 평가되는 표현식을 포함해요. 예를 들어:
>>> who = 'nobody'
>>> nationality = 'Spanish'
>>> f'{who.title()} expects the {nationality} Inquisition!'
'Nobody expects the Spanish Inquisition!'
교체 필드 밖의 이중 중괄호({{ 또는 }})는 해당하는 단일 중괄호로 대체돼요:
>>> print(f'{{...}}')
{...}
교체 필드 밖의 다른 문자들은 일반 문자열 리터럴과 동일하게 취급돼요. 이는 이스케이프 시퀀스가 디코딩되고(리터럴이 원시 문자열로 표시되지 않은 경우), 삼중 따옴표 f-string에서 새 줄이 가능하다는 뜻이에요:
>>> name = 'Galahad'
>>> favorite_color = 'blue'
>>> print(f'{name}:\t{favorite_color}')
Galahad: blue
>>> print(rf"C:\Users\{name}")
C:\Users\Galahad
>>> print(f'''Three shall be the number of the counting
... and the number of the counting shall be three.''')
Three shall be the number of the counting
and the number of the counting shall be three.
포맷 문자열 리터럴의 표현식은 일반 Python 표현식처럼 취급돼요. 각 표현식은 포맷 문자열 리터럴이 나타나는 맥락에서 왼쪽에서 오른쪽 순서로 평가돼요. 빈 표현식은 허용되지 않으며, lambda와 할당 표현식 :=는 모두 명시적 괄호로 둘러싸야 해요:
>>> f'{(half := 1/2)}, {half * 42}'
'0.5, 21.0'
교체 필드 안에서 바깥 f-string의 따옴표 종류를 재사용하는 것은 허용돼요:
>>> a = dict(x=2)
>>> f"abc {a["x"]} def"
'abc 2 def'
역슬래시도 교체 필드 안에서 허용되며, 다른 맥락에서와 동일한 방식으로 평가돼요:
>>> a = ["a", "b", "c"]
>>> print(f"List a contains:\n{"\n".join(a)}")
List a contains:
a
b
c
f-string을 중첩하는 것도 가능해요:
>>> name = 'world'
>>> f'Repeated:{f' hello {name}' * 3}'
'Repeated: hello world hello world hello world'
휴대용 Python 프로그램은 5단계 이상의 중첩을 사용하지 않아야 해요.
CPython 구현 세부 사항: CPython은 f-string의 중첩을 제한하지 않아요.
교체 표현식은 작은따옴표와 삼중 따옴표 f-string 모두에서 새 줄을 포함할 수 있고 주석도 포함할 수 있어요. 교체 필드 안에서 # 뒤에 오는 모든 것은 주석이에요 (닫는 중괄호와 따옴표를 포함해요). 이는 주석이 있는 교체 필드는 반드시 다른 줄에서 닫혀야 한다는 뜻이에요:
>>> a = 2
>>> f"abc{a # This comment }" continues until the end of the line
... + 3}"
'abc5'
표현식 뒤에는 교체 필드가 선택적으로 다음을 포함할 수 있어요:
- 디버그 지정자 – 등호(
=), 한쪽 또는 양쪽에 선택적으로 공백이 둘러싸여 있을 수 있어요; - 변환 지정자 –
!s,!r,!a; 그리고/또는 - 콜론(
:)이 접두사로 붙은 포맷 지정자.
이 필드들이 어떻게 평가되는지에 대한 자세한 내용은 f-strings에 대한 표준 라이브러리 섹션을 참고하세요.
그 섹션이 설명하듯이, 포맷 지정자는 교체 필드 값을 포맷하기 위해 format() 함수의 두 번째 인자로 전달돼요. 예를 들어 Format Specification Mini-Language를 사용해 필드 너비와 패딩 문자를 지정할 수 있어요:
>>> number = 14.3
>>> f'{number:20.7f}'
' 14.3000000'
최상위 포맷 지정자는 중첩 교체 필드를 포함할 수 있어요:
>>> field_size = 20
>>> precision = 7
>>> f'{number:{field_size}.{precision}f}'
' 14.3000000'
이 중첩 필드들은 자신만의 변환 필드와 포맷 지정자를 포함할 수 있어요:
>>> number = 3
>>> f'{number:{field_size}}'
' 3'
>>> f'{number:{field_size:05}}'
'00000000000000000003'
그러나 이 중첩 필드들은 더 깊이 중첩된 교체 필드를 포함하지 못해요.
포맷 문자열 리터럴은 표현식을 포함하지 않더라도 docstring으로 사용할 수 없어요:
>>> def foo():
... f"Not a docstring"
...
>>> print(foo.__doc__)
None
참고 자료
- PEP 498 – Literal String Interpolation
- PEP 701 – Syntactic formalization of f-strings
str.format(), 관련 포맷 문자열 메커니즘을 사용해요.
2.5.8. t-strings
버전 3.14에 추가되었어요.
템플릿 문자열 리터럴 또는 t-string은 't' 또는 'T' 접두사가 붙은 문자열 리터럴이에요. 이 문자열들은 포맷 문자열 리터럴과 동일한 문법 규칙을 따라요. 평가 규칙의 차이점은 t-strings에 대한 표준 라이브러리 섹션을 참고하세요.
2.5.9. f-strings의 형식 문법 (Formal grammar for f-strings)
F-string은 FSTRING_START, FSTRING_MIDDLE, FSTRING_END 토큰을 생성하는 어휘 분석기와 교체 필드의 표현식을 처리하는 파서가 부분적으로 나눠 처리해요. 작업이 나뉘는 정확한 방식은 CPython 구현 세부 사항이에요.
그에 상응하여 f-string 문법은 어휘 정의와 구문 정의의 혼합이에요.
다음 상황에서 공백은 유의미해요:
FSTRING_START에는 공백이 없어야 해요 (접두사와 따옴표 사이).FSTRING_MIDDLE의 공백은 리터럴 문자열 내용의 일부예요.fstring_replacement_field에서f_debug_specifier가 있으면, 여는 중괄호부터f_debug_specifier까지의 모든 공백과f_debug_specifier바로 뒤의 공백은 표현식의 일부로 유지돼요.
CPython 구현 세부 사항: 표현식은 토큰화 단계에서 처리되지 않아요. { 토큰의 위치와 = 뒤의 토큰을 사용해 소스 코드에서 가져와요.
FSTRING_MIDDLE 정의는 특수 문자(역슬래시, 새 줄, {, })와 시퀀스(f_quote)를 나타내기 위해 음의 lookahead(!)를 사용해요.
fstring: FSTRING_START fstring_middle* FSTRING_END
FSTRING_START: fstringprefix ("'" | '"' | "'''" | '"""')
FSTRING_END: f_quote
fstringprefix: <("f" | "fr" | "rf"), case-insensitive>
f_debug_specifier: '='
f_quote: <the quote character(s) used in FSTRING_START>
fstring_middle:
| fstring_replacement_field
| FSTRING_MIDDLE
FSTRING_MIDDLE:
| (!"\" !newline !'{' !'}' !f_quote) source_character
| stringescapeseq
| "{{"
| "}}"
| <newline, in triple-quoted f-strings only>
fstring_replacement_field:
| '{' f_expression [f_debug_specifier] [fstring_conversion]
[fstring_full_format_spec] '}'
fstring_conversion:
| "!" ("s" | "r" | "a")
fstring_full_format_spec:
| ':' fstring_format_spec*
fstring_format_spec:
| FSTRING_MIDDLE
| fstring_replacement_field
f_expression:
| ','.(conditional_expression | "*" or_expr)+ [","]
| yield_expression
참고: 위 문법 조각에서 f_quote와 FSTRING_MIDDLE 규칙은 컨텍스트에 민감해요. 가장 가까운 바깥 fstring의 FSTRING_START 내용에 의존하거든요. 이 템플릿에서 더 전통적인 형식 문법을 구성하는 것은 독자의 몫으로 남겨 두어요.
t-string의 문법은 f-string과 동일하며, 규칙과 토큰 이름 및 접두사의 시작 부분에서 f 대신 t를 사용해요.
tstring: TSTRING_START tstring_middle* TSTRING_END
<rest of the t-string grammar is omitted; see above>
2.6. 숫자 리터럴 (Numeric literals)
NUMBER 토큰은 숫자 리터럴을 나타내며, 정수, 부동소수점, 허수(imaginary) 세 가지 타입이 있어요.
NUMBER: integer | floatnumber | imagnumber
숫자 리터럴의 숫자 값은 해당 문자열을 int, float, complex 클래스 생성자에 각각 전달한 것과 동일해요. 이 생성자들의 모든 유효한 입력이 리터럴로도 유효한 것은 아니라는 점을 유의하세요.
숫자 리터럴은 부호를 포함하지 않아요. -1 같은 구문은 사실 단항 연산자 '-'와 리터럴 1로 구성된 표현식이에요.
2.6.1. 정수 리터럴 (Integer literals)
정수 리터럴은 정수(whole number)를 나타내요. 예를 들어:
7
3
2147483647
사용 가능한 메모리에 저장할 수 있는 것 외에는 정수 리터럴의 길이에 제한이 없어요:
7922816251426433759354395033679228162514264337593543950336
가독성을 높이기 위해 밑줄로 자릿수를 그룹화할 수 있으며, 리터럴의 숫자 값을 결정할 때는 무시돼요. 예를 들어 다음 리터럴들은 동등해요:
100_000_000_000
100000000000
1_00_00_00_00_000
밑줄은 자릿수 사이에서만 나타날 수 있어요. 예를 들어 _123, 321_, 123__321은 유효하지 않은 리터럴이에요.
정수는 접두사 0b, 0o, 0x를 사용해 각각 2진수(base 2), 8진수(base 8), 16진수(base 16)로 지정할 수 있어요. 16진수의 자릿수 10부터 15는 문자 A-F로 표현되며 대소문자를 구분하지 않아요. 예를 들어:
0b100110111
0b_1110_0101
0o177
0o377
0xdeadbeef
0xDead_Beef
밑줄은 기수 지정자 뒤에 올 수 있어요. 예를 들어 0x_1f는 유효한 리터럴이지만 0_x1f와 0x__1f는 아니에요.
0이 아닌 십진수에서 선행 0은 허용되지 않아요. 예를 들어 0123은 유효한 리터럴이 아니에요. 이는 Python이 3.0 버전 이전에 사용했던 C 스타일 8진수 리터럴과의 혼동을 피하기 위한 것이에요.
형식적으로 정수 리터럴은 다음 어휘 정의로 설명돼요:
integer: decinteger | bininteger | octinteger | hexinteger | zerointeger
decinteger: nonzerodigit ([""] digit)*
bininteger: "0" ("b" | "B") ([""] bindigit)+
octinteger: "0" ("o" | "O") ([""] octdigit)+
hexinteger: "0" ("x" | "X") ([""] hexdigit)+
zerointeger: "0"+ (["_"] "0")*
nonzerodigit: "1"..."9"
digit: "0"..."9"
bindigit: "0" | "1"
octdigit: "0"..."7"
hexdigit: digit | "a"..."f" | "A"..."F"
버전 3.6에서 변경: 리터럴에서 그룹화 목적으로 밑줄이 이제 허용돼요.
2.6.2. 부동소수점 리터럴 (Floating-point literals)
3.14나 1.5 같은 부동소수점(float) 리터럴은 실수의 근사값을 나타내요.
그것들은 각각 십진 자릿수로 구성된 정수 부분과 분수 부분으로 이루어져요. 부분들은 소수점 .으로 구분돼요:
2.71828
4.0
정수 리터럴과 달리 선행 0이 허용돼요. 예를 들어 077.010은 합법적이며 77.01과 같은 숫자를 나타내요.
정수 리터럴에서처럼, 가독성을 돕기 위해 자릿수 사이에 단일 밑줄이 올 수 있어요:
96_485.332_123
3.14_15_93
두 부분 중 하나, 둘 다는 아니지만 비어 있을 수 있어요. 예를 들어:
10. # (equivalent to 10.0)
.001 # (equivalent to 0.001)
선택적으로 정수와 분수 뒤에 지수가 올 수 있어요: 문자 e 또는 E, 그 뒤에 선택적 부호 + 또는 -, 그리고 정수 및 분수 부분과 같은 형식의 숫자. e 또는 E는 "10의 거듭제곱 곱하기"를 나타내요:
1.0e3 # (represents 1.0×10³, or 1000.0)
1.166e-5 # (represents 1.166×10⁻⁵, or 0.00001166)
6.02214076e+23 # (represents 6.02214076×10²³, or 602214076000000000000000.)
정수 부분과 지수 부분만 있는 float에서는 소수점을 생략할 수 있어요:
1e3 # (equivalent to 1.e3 and 1.0e3)
0e0 # (equivalent to 0.)
형식적으로 부동소수점 리터럴은 다음 어휘 정의로 설명돼요:
floatnumber:
| digitpart "." [digitpart] [exponent]
| "." digitpart [exponent]
| digitpart exponent
digitpart: digit (["_"] digit)*
exponent: ("e" | "E") ["+" | "-"] digitpart
버전 3.6에서 변경: 리터럴에서 그룹화 목적으로 밑줄이 이제 허용돼요.
2.6.3. 허수 리터럴 (Imaginary literals)
Python에는 복소수 객체가 있지만 복소수 리터럴은 없어요. 대신 허수 리터럴이 실수부가 0인 복소수를 나타내요.
예를 들어 수학에서 복소수 3+4.2i는 실수 3에 허수 4.2i를 더한 것으로 써요. Python도 비슷한 문법을 사용하지만, 허수 단위는 i 대신 j로 써요:
3+4.2j
이것은 정수 리터럴 3, 연산자 '+', 허수 리터럴 4.2j로 구성된 표현식이에요. 이들은 세 개의 별도 토큰이므로 사이에 공백이 허용돼요:
3 + 4.2j
각 토큰 안에는 공백이 허용되지 않아요. 특히 j 접미사는 그 앞의 숫자와 분리될 수 없어요.
j 앞의 숫자는 부동소수점 리터럴과 같은 문법을 가져요. 따라서 다음은 유효한 허수 리터럴이에요:
4.2j
3.14j
10.j
.001j
1e100j
3.14e-10j
3.14_15_93j
부동소수점 리터럴과 달리 허수가 정수 부분만 있으면 소수점을 생략할 수 있어요. 숫자는 여전히 정수가 아니라 부동소수점으로 평가돼요:
10j
0j
1000000000000000000000000j # equivalent to 1e+24j
j 접미사는 대소문자를 구분하지 않아요. 즉 J를 대신 사용할 수 있어요:
3.14J # equivalent to 3.14j
형식적으로 허수 리터럴은 다음 어휘 정의로 설명돼요:
imagnumber: (floatnumber | digitpart) ("j" | "J")
2.7. 연산자와 구분 기호 (Operators and delimiters)
다음 문법이 연산자와 구분 기호 토큰, 즉 일반적인 OP 토큰 타입을 정의해요. 이 토큰들과 그 이름의 목록은 token 모듈 문서에서도 볼 수 있어요.
OP: | assignment_operator | bitwise_operator | comparison_operator | enclosing_delimiter | other_delimiter | arithmetic_operator | "..." | other_op
assignment_operator: "+=" | "-=" | "=" | "=" | "/=" | "//=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>=" | "@=" | ":=" bitwise_operator: "&" | "|" | "^" | "~" | "<<" | ">>" comparison_operator: "<=" | ">=" | "<" | ">" | "==" | "!=" enclosing_delimiter: "(" | ")" | "[" | "]" | "{" | "}" other_delimiter: "," | ":" | "!" | ";" | "=" | "->" arithmetic_operator: "+" | "-" | "" | "" | "//" | "/" | "%" other_op: "." | "@"
참고: 일반적으로 연산자는 표현식을 결합하는 데 사용되고, 구분 기호는 다른 목적을 위해 사용돼요. 그러나 두 범주 사이에는 명확하고 형식적인 구분이 없어요.
일부 토큰은 사용 방식에 따라 연산자 또는 구분 기호 역할을 할 수 있어요. 예를 들어 *는 곱셈 연산자이자 시퀀스 언패킹에 사용되는 구분 기호이고, @는 행렬 곱셈이자 데코레이터를 도입하는 구분 기호예요.
일부 토큰은 구분이 명확하지 않아요. 예를 들어 어떤 이들은 ., (, )를 구분 기호로 보고, 다른 이들은 getattr() 연산자와 함수 호출 연산자로 봐요.
Python의 일부 연산자, 예를 들어 and, or, not in은 "기호"(연산자 토큰) 대신 키워드 토큰을 사용해요.
세 개의 연속된 마침표(...)의 시퀀스는 Ellipsis 리터럴로서 특별한 의미를 가져요.