복합 문장
복합 문장 (Compound statements)
복합 문장은 (여러 개의) 다른 문장들을 묶어서 담고 있어요. 그리고 그 안에 있는 문장들의 실행을 어떤 식으로든 조절하거나 제어하죠. 일반적으로 복합 문장은 여러 줄에 걸쳐 펼쳐지는데, 단순한 형태라면 복합 문장 전체가 한 줄에 들어갈 수도 있어요.
if, while, for 문은 전통적인 제어 흐름(control flow) 구문을 구현해요. try는 문장 묶음에 대한 예외 처리기와/또는 정리(cleanup) 코드를 지정하고, with 문은 코드 블록 주변에서 초기화·종료 코드가 실행되게 감싸 줘요. 함수 정의와 클래스 정의도 문법적으로는 복합 문장이에요.
복합 문장은 하나 이상의 '절(clause)'로 이루어져요. 절은 헤더(header)와 '수트(suite)'로 구성돼요. 특정 복합 문장의 절 헤더들은 모두 같은 들여쓰기 수준에 있어요. 각 절 헤더는 그 절을 식별해 주는 유일한 키워드로 시작하고 콜론으로 끝나요. 수트는 절이 제어하는 문장 묶음이에요. 수트는 헤더와 같은 줄에서 헤더의 콜론 뒤에 오는, 세미콜론으로 구분된 단순 문장 하나 이상일 수도 있고, 다음 줄들에서 들여쓰기된 문장 하나 이상일 수도 있어요. 이 두 번째 형태만 중첩된 복합 문장을 담을 수 있어요. 아래처럼 쓰는 건 불법인데, 주로 뒤따르는 else 절이 어떤 if 절에 속하는지가 분명하지 않기 때문이에요.
if test1: if test2: print(x)
또한 이 문맥에서는 세미콜론이 콜론보다 더 강하게 묶이기 때문에, 아래 예제에서는 print() 호출들이 전부 실행되거나 전혀 실행되지 않아요.
if x < y < z: print(x); print(y); print(z)
정리하면 이렇게 돼요.
compound_stmt: if_stmt
| while_stmt
| for_stmt
| try_stmt
| with_stmt
| match_stmt
| funcdef
| classdef
| async_with_stmt
| async_for_stmt
| async_funcdef
suite: stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement: stmt_list NEWLINE | compound_stmt
stmt_list: simple_stmt (";" simple_stmt)* [";"]
문장은 항상 NEWLINE로 끝나고, 그 뒤에 DEDENT가 올 수도 있어요. 또한 선택적인 이어지는 절(continuation clause)들은 항상 문장을 시작할 수 없는 키워드로 시작하기 때문에, 모호함(ambiguity)이 생기지 않아요. (Python은 중첩된 if 문이 들여쓰기되도록 요구하는 방식으로 '매달린 else(dangling else)' 문제를 해결해요.)
아래 절들에서 문법 규칙은 가독성을 위해 각 절을 별도의 줄에 배치했어요.
출처: Python 언어 레퍼런스
8.1. if 문
if 문은 조건부 실행에 쓰여요.
if_stmt: "if" assignment_expression ":" suite
("elif" assignment_expression ":" suite)*
["else" ":" suite]
표현식들을 하나씩 평가하다가 참(true)인 것을 찾으면 그 중 정확히 하나의 수트를 골라요. (참과 거짓의 정의는 '불리언 연산' 절을 참고하세요.) 그러면 그 수트가 실행되고, if 문의 다른 부분은 실행되거나 평가되지 않아요. 모든 표현식이 거짓이면, else 절이 있다면 그 수트가 실행돼요.
8.2. while 문
while 문은 어떤 표현식이 참인 동안 반복 실행하는 데 쓰여요.
while_stmt: "while" assignment_expression ":" suite
["else" ":" suite]
이 문은 표현식을 반복해서 검사하고, 참이면 첫 번째 수트를 실행해요. 표현식이 거짓이면(처음 검사했을 때 거짓일 수도 있어요), else 절이 있다면 그 수트가 실행되고 루프가 끝나요.
첫 번째 수트에서 실행된 break 문은 else 절의 수트를 실행하지 않고 루프를 끝내요. 첫 번째 수트에서 실행된 continue 문은 수트의 나머지를 건너뛰고 표현식 검사로 돌아가요.
8.3. for 문
for 문은 시퀀스(문자열, 튜플, 리스트 같은)의 원소나 다른 이터러블(iterable) 객체를 순회(iterate)하는 데 쓰여요.
for_stmt: "for" target_list "in" starred_expression_list ":" suite
["else" ":" suite]
starred_expression_list 표현식은 한 번 평가돼요. 이 표현식은 이터러블 객체를 내놓아야 해요. 그 이터러블에 대해 이터레이터(iterator)가 만들어져요. 이터레이터가 제공하는 첫 번째 항목이 할당의 표준 규칙(할당 문 참고)에 따라 target 목록에 할당되고, 수트가 실행돼요. 이 과정이 이터레이터가 제공하는 각 항목에 대해 반복돼요. 이터레이터가 다 소진되면, else 절이 있다면 그 수트가 실행되고 루프가 끝나요.
첫 번째 수트에서 실행된 break 문은 else 절의 수트를 실행하지 않고 루프를 끝내요. 첫 번째 수트에서 실행된 continue 문은 수트의 나머지를 건너뛰고 다음 항목으로 진행하거나, 다음 항목이 없다면 else 절로 진행해요.
for 루프는 target 목록의 변수들에 할당을 수행해요. 이 할당은 그 변수들에 대한 이전의 모든 할당을 덮어쓰는데, for 루프의 수트 안에서 만들어진 할당도 포함해요.
for i in range(10):
print(i)
i = 5 # this will not affect the for-loop
# because i will be overwritten with the next
# index in the range
target 목록의 이름들은 루프가 끝나도 삭제되지 않지만, 시퀀스가 비어 있다면 루프가 그 이름들에게 전혀 할당하지 않을 거예요. 팁: 내장 타입 range()는 정수들의 불변 산술 시퀀스를 나타내요. 예를 들어 range(3)을 순회하면 0, 1, 그리고 2가 차례로 나와요.
버전 3.11에서 변경: 표현식 목록에서 starred 요소가 이제 허용돼요.
8.4. try 문
try 문은 문장 묶음에 대한 예외 처리기와/또는 정리 코드를 지정해요.
try_stmt: try1_stmt | try2_stmt | try3_stmt
try1_stmt: "try" ":" suite
("except" [expression ["as" identifier]] ":" suite)+
["else" ":" suite]
["finally" ":" suite]
try2_stmt: "try" ":" suite
("except" "*" expression ["as" identifier] ":" suite)+
["else" ":" suite]
["finally" ":" suite]
try3_stmt: "try" ":" suite
"finally" ":" suite
예외에 대한 추가 정보는 '예외' 절에서, raise 문으로 예외를 발생시키는 방법은 'raise 문' 절에서 찾아볼 수 있어요.
버전 3.14에서 변경: 여러 예외 타입을 사용할 때 그룹화 괄호를 선택적으로 생략하는 것이 지원돼요. PEP 758을 참고하세요.
8.4.1. except 절
except 절은 하나 이상의 예외 처리기를 지정해요. try 절에서 예외가 발생하지 않으면 예외 처리기는 실행되지 않아요. try 수트에서 예외가 발생하면 예외 처리기 탐색이 시작돼요. 이 탐색은 예외와 일치하는 것을 찾을 때까지 except 절들을 차례로 검사해요. 표현식이 없는 except 절은 있다면 반드시 마지막에 와야 하고, 어떤 예외와도 일치해요.
표현식이 있는 except 절의 경우, 그 표현식은 예외 타입 또는 예외 타입들의 튜플로 평가되어야 해요. 여러 예외 타입을 제공하고 as 절을 사용하지 않는다면 괄호를 생략할 수 있어요. 발생한 예외는 그 표현식이 예외 객체의 클래스나 비가상 기본 클래스(non-virtual base class), 또는 그런 클래스를 담은 튜플로 평가되는 except 절과 일치해요.
어떤 except 절도 예외와 일치하지 않으면, 예외 처리기 탐색은 둘러싸는 코드와 호출 스택(invocation stack)에서 계속돼요. [1]
except 절 헤더의 표현식을 평가하는 과정에서 예외가 발생하면, 원래의 처리기 탐색은 취소되고 새 예외에 대한 탐색이 둘러싸는 코드와 호출 스택에서 시작돼요. (마치 전체 try 문이 그 예외를 발생시킨 것처럼 취급돼요.)
일치하는 except 절을 찾으면, as 키워드 뒤에 지정된 대상(target)이 있다면 예외가 그 대상에 할당되고, except 절의 수트가 실행돼요. 모든 except 절은 실행 가능한 블록을 가져야 해요. 이 블록의 끝에 도달하면, 전체 try 문 뒤에서 실행이 정상적으로 계속돼요. (이 말은, 같은 예외에 대해 두 개의 중첩된 처리기가 있고 예외가 안쪽 처리기의 try 절에서 발생했다면, 바깥쪽 처리기가 그 예외를 처리하지 않는다는 뜻이에요.)
as target으로 예외가 할당됐다면, 그 예외는 except 절의 끝에서 지워져요. 이는 다음과 같은 것과 같아요.
except E as N:
foo
이 코드는 이렇게 변환된 것과 같아요.
except E as N:
try:
foo
finally:
del N
이 말은, except 절 이후에도 그 예외를 참조하려면 예외를 다른 이름에 할당해야 한다는 뜻이에요. 예외는 그에 붙어 있는 traceback 때문에 스택 프레임과 순환 참조를 형성해서, 다음 가비지 컬렉션(garbage collection)이 일어날 때까지 그 프레임의 모든 지역 변수를 살아 있게 유지하므로 지워지는 거예요.
except 절의 수트가 실행되기 전에, 예외는 sys 모듈에 저장돼요. except 절의 본문 안에서 sys.exception()을 호출하면 그 예외에 접근할 수 있어요. 예외 처리기를 떠날 때, sys 모듈에 저장된 예외는 이전 값으로 재설정돼요.
>>> print(sys.exception())
None
>>> try:
... raise TypeError
... except:
... print(repr(sys.exception()))
... try:
... raise ValueError
... except:
... print(repr(sys.exception()))
... print(repr(sys.exception()))
...
TypeError()
ValueError()
TypeError()
>>> print(sys.exception())
None
8.4.2. except* 절
except* 절은 예외 그룹(BaseExceptionGroup 인스턴스)에 대해 하나 이상의 처리기를 지정해요. try 문은 except 절이나 except* 절 중 하나만 가질 수 있고, 둘 다는 가질 수 없어요. except*의 경우 일치시킬 예외 타입이 필수라서, except*:는 문법 오류(syntax error)예요. 타입은 except의 경우처럼 해석되지만, 일치는 처리 중인 그룹에 담긴 예외들에 대해 수행돼요. 일치하는 타입이 BaseExceptionGroup의 서브클래스라면 모호한 의미론 때문에 TypeError가 발생해요.
try 블록에서 예외 그룹이 발생하면, 각 except* 절은 그 그룹을 일치하는 예외와 일치하지 않는 예외의 하위 그룹으로 분할(split, split() 참고)해요. 일치하는 하위 그룹이 비어 있지 않으면 그것이 처리된 예외(sys.exception()이 반환하는 값)가 되고, except* 절의 대상(있다면)에 할당돼요. 그런 다음 except* 절의 본문이 실행돼요. 일치하지 않는 하위 그룹이 비어 있지 않으면, 같은 방식으로 다음 except*에 의해 처리돼요. 이 과정은 그룹의 모든 예외가 일치될 때까지, 또는 마지막 except* 절이 실행될 때까지 계속돼요.
모든 except* 절이 실행된 후, 처리되지 않은 예외들의 그룹은 except* 절 안에서 발생하거나 다시 발생한 예외들과 병합돼요. 이 병합된 예외 그룹이 전파돼요.
>>> try:
... raise ExceptionGroup("eg",
... [ValueError(1), TypeError(2), OSError(3), OSError(4)])
... except* TypeError as e:
... print(f'caught {type(e)} with nested {e.exceptions}')
... except* OSError as e:
... print(f'caught {type(e)} with nested {e.exceptions}')
...
caught <class 'ExceptionGroup'> with nested (TypeError(2),)
caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4))
+ Exception Group Traceback (most recent call last):
| File "<doctest default[0]>", line 2, in <module>
| raise ExceptionGroup("eg",
| [ValueError(1), TypeError(2), OSError(3), OSError(4)])
| ExceptionGroup: eg (1 sub-exception)
+-+---------------- 1 ----------------
| ValueError: 1
+------------------------------------
try 블록에서 발생한 예외가 예외 그룹이 아니고 그 타입이 except* 절 중 하나와 일치한다면, 그 예외는 빈 메시지 문자열을 가진 예외 그룹에 의해 잡혀서 감싸져요. 이로써 대상 e의 타입이 일관되게 BaseExceptionGroup이 되도록 보장해요.
>>> try:
... raise BlockingIOError
... except* BlockingIOError as e:
... print(repr(e))
...
ExceptionGroup('', (BlockingIOError(),))
break, continue, return은 except* 절에 나타날 수 없어요.
8.4.3. else 절
선택적인 else 절은 제어 흐름이 try 수트를 떠나고, 예외가 발생하지 않았으며, return, continue, break 문도 실행되지 않았을 때 실행돼요. else 절의 예외는 앞의 except 절들이 처리하지 않아요.
8.4.4. finally 절
finally가 있으면, 그것은 '정리(cleanup)' 처리기를 지정해요. try 절이 실행되는데, 모든 except와 else 절을 포함해요. 어떤 절에서 예외가 발생해서 처리되지 않으면, 그 예외는 임시로 저장돼요. finally 절이 실행돼요. 저장된 예외가 있다면 finally 절의 끝에서 다시 발생돼요. finally 절이 또 다른 예외를 발생시키면, 저장된 예외가 새 예외의 context로 설정돼요. finally 절이 return, break, continue 문을 실행하면, 저장된 예외는 버려져요. 예를 들어, 이 함수는 42를 반환해요.
def f():
try:
1/0
finally:
return 42
finally 절이 실행되는 동안 예외 정보는 프로그램이 사용할 수 없어요.
try…finally 문의 try 수트에서 return, break, continue 문이 실행되면, finally 절도 '나가는 길에' 실행돼요.
함수의 반환 값은 마지막으로 실행된 return 문에 의해 결정돼요. finally 절은 항상 실행되므로, finally 절에서 실행된 return 문이 항상 마지막으로 실행된 것이 돼요. 다음 함수는 'finally'를 반환해요.
def foo():
try:
return 'try'
finally:
return 'finally'
버전 3.8에서 변경: Python 3.8 이전에는 구현 문제 때문에 continue 문이 finally 절에서 불법이었어요.
버전 3.14에서 변경: 컴파일러는 finally 블록에 return, break, continue가 나타나면 SyntaxWarning을 발생시켜요 (PEP 765 참고).
8.5. with 문
with 문은 컨텍스트 매니저(context manager, 'With 문 컨텍스트 매니저' 절 참고)가 정의한 메서드로 블록의 실행을 감싸는 데 쓰여요. 이렇게 하면 흔한 try…except…finally 사용 패턴을 캡슐화해서 편리하게 재사용할 수 있어요.
with_stmt: "with" ( "(" with_stmt_contents ","? ")" | with_stmt_contents ) ":" suite
with_stmt_contents: with_item ("," with_item)*
with_item: expression ["as" target]
하나의 '아이템'을 가진 with 문의 실행은 다음과 같이 진행돼요.
- 컨텍스트 표현식(
with_item에 주어진 표현식)을 평가해서 컨텍스트 매니저를 얻어요. - 컨텍스트 매니저의
__enter__()를 나중에 쓰기 위해 로드해요. - 컨텍스트 매니저의
__exit__()를 나중에 쓰기 위해 로드해요. - 컨텍스트 매니저의
__enter__()메서드를 호출해요. with문에 대상(target)이 포함됐다면,__enter__()의 반환 값이 그 대상에 할당돼요.
참고:
with문은__enter__()메서드가 오류 없이 반환하면__exit__()가 항상 호출된다는 것을 보장해요. 따라서 대상 목록에 할당하는 동안 오류가 발생하면, 그 오류는 수트 안에서 발생한 오류와 동일하게 취급돼요. 아래 7단계를 참고하세요.
- 수트가 실행돼요.
- 컨텍스트 매니저의
__exit__()메서드가 호출돼요. 예외 때문에 수트를 빠져나갔다면 그 타입, 값, traceback이__exit__()의 인자로 전달돼요. 그렇지 않으면 세 개의None인자가 제공돼요. - 예외 때문에 수트를 빠져나갔고
__exit__()메서드의 반환 값이 거짓이면, 예외가 다시 발생해요. 반환 값이 참이면 예외는 억제되고,with문 다음의 문장으로 실행이 계속돼요. - 예외가 아닌 다른 이유로 수트를 빠져나갔다면,
__exit__()의 반환 값은 무시되고, 실행은 발생한 종료의 종류에 해당하는 정상 위치에서 진행돼요.
다음 코드는
with EXPRESSION as TARGET:
SUITE
의미론적으로 다음과 같아요.
manager = (EXPRESSION)
enter = manager.__enter__
exit = manager.__exit__
value = enter()
hit_except = False
try:
TARGET = value
SUITE
except:
hit_except = True
if not exit(*sys.exc_info()):
raise
finally:
if not hit_except:
exit(None, None, None)
단, __enter__()와 __exit__()에 대해 암묵적인 특수 메서드 조회가 사용된다는 점만 달라요.
아이템이 두 개 이상이면, 컨텍스트 매니저들은 여러 개의 with 문이 중첩된 것처럼 처리돼요.
with A() as a, B() as b:
SUITE
이는 의미론적으로 다음과 같아요.
with A() as a:
with B() as b:
SUITE
여러 아이템 컨텍스트 매니저는 아이템이 괄호로 둘러싸여 있으면 여러 줄로도 쓸 수 있어요. 예를 들어:
with (
A() as a,
B() as b,
):
SUITE
버전 3.1에서 변경: 여러 컨텍스트 표현식의 지원이 추가됐어요.
버전 3.10에서 변경: 문장을 여러 줄로 나누기 위해 그룹화 괄호를 쓰는 지원이 추가됐어요.
참고: PEP 343 – The "with" statement — Python with 문의 명세, 배경, 그리고 예시.
8.6. match 문
버전 3.10에서 추가됐어요.
match 문은 패턴 매칭(pattern matching)에 쓰여요. 문법은 이래요.
match_stmt: 'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT
subject_expr: flexible_expression "," [flexible_expression_list [',']]
| assignment_expression
case_block: 'case' patterns [guard] ":" suite
참고: 이 절에서는 소프트 키워드(soft keyword)를 나타내는 데 작은따옴표를 사용해요.
패턴 매칭은 패턴(case 뒤에 오는)과 대상(subject) 값(match 뒤에 오는)을 입력으로 받아요. (서브패턴을 포함할 수 있는) 패턴은 대상 값에 대해 일치가 시도돼요. 결과는 다음과 같아요.
- 일치 성공 또는 실패. (패턴 성공 또는 실패라고도 불러요.)
- 일치된 값을 이름에 바인딩할 가능성. 이 전제 조건은 아래에서 더 자세히 논의해요.
match와 case 키워드는 소프트 키워드예요.
참고: PEP 634 – 구조적 패턴 매칭: 명세 / PEP 636 – 구조적 패턴 매칭: 튜토리얼
8.6.1. 개요
match 문의 논리적 흐름을 개괄하면 이래요.
- 대상 표현식
subject_expr이 평가되고 결과 대상 값이 얻어져요. 대상 표현식에 콤마가 있으면 표준 규칙에 따라 튜플이 구성돼요. case_block의 각 패턴이 대상 값과 일치를 시도해요. 성공·실패의 구체적인 규칙은 아래에 설명돼요. 일치 시도는 패턴 안의 독립 이름 중 일부 또는 전부를 바인딩할 수도 있어요. 정확한 패턴 바인딩 규칙은 패턴 타입마다 다르며 아래에 명시돼요. 성공적인 패턴 일치 중 만들어진 이름 바인딩은 실행된 블록을 지나서까지 유효하며, match 문 이후에도 사용할 수 있어요.
참고: 실패한 패턴 일치 중에도 일부 서브패턴은 성공할 수 있어요. 실패한 일치에 대해 바인딩이 만들어졌다고 믿지 마세요. 반대로 실패한 일치 후에도 변수가 변하지 않았다고 믿지도 마세요. 정확한 동작은 구현에 따라 달라질 수 있어요. 이는 서로 다른 구현이 최적화를 추가할 수 있도록 의도적으로 내린 결정이에요.
- 패턴이 성공하면, 해당 가드(guard, 있다면)가 평가돼요. 이 경우 모든 이름 바인딩이 일어났음이 보장돼요.
- 가드가 참으로 평가되거나 없으면,
case_block안의 블록이 실행돼요. - 그렇지 않으면 위에서 설명한 대로 다음
case_block이 시도돼요. - 더 이상의 case 블록이 없으면, match 문은 완료돼요.
참고: 사용자는 일반적으로 패턴이 평가된다는 것에 의존해서는 안 돼요. 구현에 따라 인터프리터가 값을 캐시하거나 반복 평가를 건너뛰는 다른 최적화를 사용할 수 있어요.
샘플 match 문이에요.
>>> flag = False
>>> match (100, 200):
... case (100, 300): # Mismatch: 200 != 300
... print('Case 1')
... case (100, 200) if flag: # Successful match, but guard fails
... print('Case 2')
... case (100, y): # Matches and binds y to 200
... print(f'Case 3, y: {y}')
... case _: # Pattern not attempted
... print('Case 4, I match anything!')
...
Case 3, y: 200
이 경우 if flag가 가드예요. 이에 대한 자세한 내용은 다음 절에서 다뤄요.
8.6.2. 가드 (Guards)
guard: "if" assignment_expression
가드(case의 일부)는 case 블록 안의 코드가 실행되려면 반드시 성공해야 해요. if 다음에 표현식이 오는 형태를 취해요.
가드를 가진 case 블록의 논리적 흐름은 다음과 같아요.
case블록의 패턴이 성공했는지 확인해요. 패턴이 실패하면 가드는 평가되지 않고 다음case블록이 검사돼요.- 패턴이 성공했다면 가드를 평가해요.
- 가드 조건이 참으로 평가되면
case블록이 선택돼요. - 가드 조건이 거짓으로 평가되면
case블록은 선택되지 않아요. - 가드가 평가 중에 예외를 발생시키면, 그 예외는 위로 전파돼요(bubbles up).
가드는 표현식이므로 부작용(side effects)을 가질 수 있어요. 가드 평가는 첫 번째 case 블록부터 마지막까지, 한 번에 하나씩 진행되어야 해요. 패턴이 모두 성공하지 못한 case 블록은 건너뛰어요. (즉, 가드 평가는 순서대로 일어나야 해요.) 가드 평가는 case 블록이 하나 선택되면 멈춰야 해요.
8.6.3. 반박 불가능한(irrefutable) case 블록
반박 불가능한 case 블록은 모든 것을 매치하는(match-all) case 블록이에요. match 문은 최대 하나의 반박 불가능한 case 블록을 가질 수 있고, 그것은 반드시 마지막이어야 해요.
case 블록은 가드가 없고 그 패턴이 반박 불가능하면 반박 불가능한 것으로 간주돼요. 패턴은 문법만으로 항상 성공한다는 것을 증명할 수 있으면 반박 불가능한 것으로 간주돼요. 다음 패턴만이 반박 불가능해요.
- 좌변이 반박 불가능한 AS 패턴
- 반박 불가능한 패턴을 하나 이상 포함하는 OR 패턴
- 캡처(Capture) 패턴
- 와일드카드(Wildcard) 패턴
- 괄호로 둘러싸인 반박 불가능한 패턴
8.6.4. 패턴 (Patterns)
참고: 이 절은 표준 EBNF를 넘어서는 문법 표기법을 사용해요.
SEP.RULE+표기법은RULE (SEP RULE)*의 약어예요.!RULE표기법은 부정 전방 탐색(negative lookahead assertion)의 약어예요.
패턴의 최상위 문법은 다음과 같아요.
patterns: open_sequence_pattern | pattern
pattern: as_pattern | or_pattern
closed_pattern: | literal_pattern
| capture_pattern
| wildcard_pattern
| value_pattern
| group_pattern
| sequence_pattern
| mapping_pattern
| class_pattern
아래 설명에는 패턴이 무엇을 하는지에 대한 '쉬운 말로' 설명이 포함돼요. (설명의 대부분에 영감을 준 문서에 대한 공은 Raymond Hettinger에게 있어요.) 이 설명들은 순전히 설명을 위한 것이며 실제 구현을 반영하지 않을 수 있어요. 또한 이 설명들이 모든 유효한 형태를 다루지는 않아요.
8.6.4.1. OR 패턴
OR 패턴은 수직 막대 |로 구분된 두 개 이상의 패턴이에요. 문법은 이래요.
or_pattern: "|".closed_pattern+
마지막 서브패턴만이 반박 불가능할 수 있고, 각 서브패턴은 모호함을 피하기 위해 반드시 같은 이름 집합을 바인딩해야 해요.
OR 패턴은 각 서브패턴을 차례로 대상 값에 일치시키다가 하나가 성공하면 성공해요. 그러면 OR 패턴은 성공한 것으로 간주돼요. 그렇지 않고 서브패턴 중 어느 것도 성공하지 않으면, OR 패턴은 실패해요.
쉬운 말로, P1 | P2 | ...는 P1을 매치하려고 시도하고, 실패하면 P2를 매치하려고 시도해요. 어느 하나가 성공하면 즉시 성공하고, 그렇지 않으면 실패해요.
8.6.4.2. AS 패턴
AS 패턴은 as 키워드의 왼쪽에 있는 OR 패턴을 대상과 일치시켜요. 문법은 이래요.
as_pattern: or_pattern "as" capture_pattern
OR 패턴이 실패하면 AS 패턴도 실패해요. 그렇지 않으면 AS 패턴은 대상을 as 키워드 오른쪽의 이름에 바인딩하고 성공해요. capture_pattern은 _가 될 수 없어요.
쉬운 말로 P as NAME은 P와 매치하고, 성공하면 NAME = <subject>를 설정해요.
8.6.4.3. 리터럴 패턴 (Literal Patterns)
리터럴 패턴은 Python의 대부분의 리터럴에 대응해요. 문법은 이래요.
literal_pattern: signed_number
| signed_number "+" NUMBER
| signed_number "-" NUMBER
| strings
| "None"
| "True"
| "False"
signed_number: ["-"] NUMBER
strings 규칙과 NUMBER 토큰은 표준 Python 문법에 정의돼 있어요. 삼중 따옴표 문자열(triple-quoted strings)이 지원되고, raw 문자열과 바이트 문자열도 지원돼요. f-문자열과 t-문자열은 지원되지 않아요.
signed_number '+' NUMBER와 signed_number '-' NUMBER 형태는 복소수를 표현하기 위한 것이에요. 왼쪽에 실수, 오른쪽에 허수가 필요해요. 예: 3 + 4j.
쉬운 말로, LITERAL은 <subject> == LITERAL일 때만 성공해요. 싱글턴 None, True, False에 대해서는 is 연산자가 사용돼요.
8.6.4.4. 캡처 패턴 (Capture Patterns)
캡처 패턴은 대상 값을 이름에 바인딩해요. 문법은 이래요.
capture_pattern: !'_' NAME
단일 밑줄 _는 캡처 패턴이 아니에요. (이것이 !'_'가 표현하는 바예요.) 그것은 대신 와일드카드 패턴으로 취급돼요.
주어진 패턴에서, 주어진 이름은 한 번만 바인딩될 수 있어요. 예를 들어 case x, x: ...는 유효하지 않은 반면, case [x] | x: ...는 허용돼요.
캡처 패턴은 항상 성공해요. 바인딩은 PEP 572의 할당 표현식 연산자가 세운 범위 결정(scope) 규칙을 따르는데, 적용 가능한 global 또는 nonlocal 문이 없다면 그 이름은 가장 가까운 포함 함수 스코프의 지역 변수가 돼요.
쉬운 말로 NAME은 항상 성공하고 NAME = <subject>를 설정해요.
8.6.4.5. 와일드카드 패턴 (Wildcard Patterns)
와일드카드 패턴은 항상 성공하고(무엇이든 매치) 어떤 이름도 바인딩하지 않아요. 문법은 이래요.
wildcard_pattern: '_'
_는 어떤 패턴 안에서도 소프트 키워드지만, 패턴 안에서만 그래요. 그것은 match 대상 표현식, 가드, case 블록 안에서도 평소처럼 식별자(identifier)예요.
쉬운 말로, _는 항상 성공해요.
8.6.4.6. 값 패턴 (Value Patterns)
값 패턴은 Python의 이름 있는 값(named value)을 나타내요. 문법은 이래요.
value_pattern: attr
attr: name_or_attr "." NAME
name_or_attr: attr | NAME
패턴 안의 점으로 구분된 이름은 표준 Python 이름 해석 규칙을 사용해 찾아봐요. 찾은 값이 대상 값과 (== 등가 연산자를 사용해) 같게 비교되면 패턴이 성공해요.
쉬운 말로 NAME1.NAME2는 <subject> == NAME1.NAME2일 때만 성공해요.
참고: 같은 값이 같은 match 문 안에서 여러 번 나타나면, 인터프리터는 첫 번째로 찾은 값을 캐시해서 같은 조회를 반복하는 대신 재사용할 수 있어요. 이 캐시는 주어진 match 문의 주어진 실행에 엄격하게 묶여 있어요.
8.6.4.7. 그룹 패턴 (Group Patterns)
그룹 패턴은 사용자가 패턴 주위에 괄호를 추가해서 의도한 그룹화를 강조할 수 있게 해 줘요. 그 외에는 추가 문법이 없어요. 문법은 이래요.
group_pattern: "(" pattern ")"
쉬운 말로 (P)는 P와 같은 효과가 있어요.
8.6.4.8. 시퀀스 패턴 (Sequence Patterns)
시퀀스 패턴은 시퀀스의 원소들에 대해 매치할 여러 서브패턴을 담아요. 문법은 리스트나 튜플의 언패킹(unpacking)과 비슷해요.
sequence_pattern: "[" [maybe_sequence_pattern] "]"
| "(" [open_sequence_pattern] ")"
open_sequence_pattern: maybe_star_pattern "," [maybe_sequence_pattern]
maybe_sequence_pattern: ",".maybe_star_pattern+ ","?
maybe_star_pattern: star_pattern | pattern
star_pattern: "*" (capture_pattern | wildcard_pattern)
시퀀스 패턴에 괄호를 쓰든 대괄호를 쓰든((...) vs [...]) 차이가 없어요.
참고: 뒤에 오는 콤마 없이 괄호로 둘러싸인 단일 패턴(예:
(3 | 4))은 그룹 패턴이에요. 반면 대괄호로 둘러싸인 단일 패턴(예:[3 | 4])은 여전히 시퀀스 패턴이에요.
시퀀스 패턴에는 최대 하나의 star 서브패턴이 있을 수 있어요. star 서브패턴은 어떤 위치에도 올 수 있어요. star 서브패턴이 없으면 시퀀스 패턴은 고정 길이 시퀀스 패턴이고, 그렇지 않으면 가변 길이 시퀀스 패턴이에요.
시퀀스 패턴을 대상 값에 대해 매치하는 논리적 흐름은 다음과 같아요.
- 대상 값이 시퀀스가 아니면 [2] 시퀀스 패턴은 실패해요.
- 대상 값이
str,bytes,bytearray의 인스턴스이면 시퀀스 패턴은 실패해요. - 이후의 단계는 시퀀스 패턴이 고정 길이인지 가변 길이인지에 따라 달라져요.
시퀀스 패턴이 고정 길이이면:
- 대상 시퀀스의 길이가 서브패턴의 개수와 같지 않으면 시퀀스 패턴은 실패해요.
- 시퀀스 패턴의 서브패턴들은 왼쪽에서 오른쪽으로 대상 시퀀스의 대응 항목에 대해 매치돼요. 서브패턴 하나가 실패하는 즉시 매치가 멈춰요. 모든 서브패턴이 대응 항목과 매치하는 데 성공하면 시퀀스 패턴은 성공해요.
그렇지 않고 시퀀스 패턴이 가변 길이이면:
- 대상 시퀀스의 길이가 star가 아닌 서브패턴의 개수보다 작으면 시퀀스 패턴은 실패해요.
- 앞에 오는 star가 아닌 서브패턴들은 고정 길이 시퀀스에서처럼 대응 항목에 대해 매치돼요.
- 이전 단계가 성공하면, star 서브패턴은 star 서브패턴 뒤에 오는 star가 아닌 서브패턴들에 대응하는 남은 항목들을 제외하고, 남은 대상 항목들로 형성된 리스트와 매치돼요.
- 남은 star가 아닌 서브패턴들은 고정 길이 시퀀스에서처럼 대응 대상 항목들에 대해 매치돼요.
참고: 대상 시퀀스의 길이는
len()(즉__len__()프로토콜)으로 얻어져요. 이 길이는 값 패턴과 비슷한 방식으로 인터프리터가 캐시할 수 있어요.
쉬운 말로 [P1, P2, P3, …, P<N>]은 다음의 모든 조건이 성립할 때만 매치돼요.
<subject>가 시퀀스인지 확인len(subject) == <N>P1이<subject>[0]과 매치 (이 매치는 이름을 바인딩할 수도 있음)P2가<subject>[1]과 매치 (이 매치는 이름을 바인딩할 수도 있음)- … 대응하는 패턴/원소에 대해 이렇게 계속됨
8.6.4.9. 매핑 패턴 (Mapping Patterns)
매핑 패턴은 하나 이상의 키-값 패턴을 담아요. 문법은 딕셔너리 생성과 비슷해요. 문법은 이래요.
mapping_pattern: "{" [items_pattern] "}"
items_pattern: ",".key_value_pattern+ ","?
key_value_pattern: (literal_pattern | value_pattern) ":" pattern
| double_star_pattern
double_star_pattern: "**" capture_pattern
매핑 패턴에는 최대 하나의 double star 패턴이 있을 수 있어요. double star 패턴은 매핑 패턴에서 마지막 서브패턴이어야 해요.
매핑 패턴에서 키의 중복은 허용되지 않아요. 중복된 리터럴 키는 SyntaxError를 발생시켜요. 그 외에 같은 값을 갖는 두 키는 실행 시점에 ValueError를 발생시켜요.
매핑 패턴을 대상 값에 대해 매치하는 논리적 흐름은 다음과 같아요.
- 대상 값이 매핑이 아니면 [3] 매핑 패턴은 실패해요.
- 매핑 패턴에 주어진 모든 키가 대상 매핑에 존재하고, 각 키의 패턴이 대상 매핑의 대응 항목과 매치하면, 매핑 패턴은 성공해요.
- 매핑 패턴에서 중복 키가 감지되면, 그 패턴은 유효하지 않은 것으로 간주돼요. 중복된 리터럴 값에 대해서는
SyntaxError가, 같은 값을 가진 이름 있는 키에 대해서는ValueError가 발생해요.
참고: 키-값 쌍은 매핑 대상의
get()메서드의 두 인자 형태를 사용해 매치돼요. 매치된 키-값 쌍은 이미 매핑에 존재해야 하고,__missing__()이나__getitem__()을 통해 즉석에서 만들어지면 안 돼요.
쉬운 말로 {KEY1: P1, KEY2: P2, ... }은 다음의 모든 조건이 성립할 때만 매치돼요.
<subject>가 매핑인지 확인KEY1 in <subject>P1이<subject>[KEY1]과 매치- … 대응하는 KEY/패턴 쌍에 대해 이렇게 계속됨
8.6.4.10. 클래스 패턴 (Class Patterns)
클래스 패턴은 클래스와 그 위치(위치적) 및 키워드 인자(있다면)를 나타내요. 문법은 이래요.
class_pattern: name_or_attr "(" [pattern_arguments ","?] ")"
pattern_arguments: positional_patterns ["," keyword_patterns]
| keyword_patterns
positional_patterns: ",".pattern+
keyword_patterns: ",".keyword_pattern+
keyword_pattern: NAME "=" pattern
클래스 패턴에서 같은 키워드는 반복되어서는 안 돼요.
클래스 패턴을 대상 값에 대해 매치하는 논리적 흐름은 다음과 같아요.
name_or_attr이 내장 타입이 아니면TypeError를 발생시켜요.- 대상 값이
name_or_attr의 인스턴스가 아니면(isinstance()로 검사), 클래스 패턴은 실패해요. - 패턴 인자가 없으면, 패턴은 성공해요. 그렇지 않으면 이후의 단계는 키워드 또는 위치 패턴 인자가 있는지에 따라 달라져요.
- 아래에 명시된 여러 내장 타입의 경우, 대상 전체와 매치되는 단일 위치 서브패턴이 허용돼요. 이 타입들에 대해서는 키워드 패턴도 다른 타입들에서처럼 동작해요.
키워드 패턴만 있으면, 그것들은 다음과 같이 하나씩 처리돼요.
- 키워드는 대상에서 속성으로 찾아봐요.
AttributeError가 아닌 다른 예외가 발생하면, 그 예외는 위로 전파돼요.AttributeError가 발생하면, 클래스 패턴은 실패했어요.- 그 외에는, 키워드 패턴과 연관된 서브패턴이 대상의 속성 값에 대해 매치돼요. 이것이 실패하면 클래스 패턴은 실패하고, 성공하면 다음 키워드로 진행돼요.
- 모든 키워드 패턴이 성공하면 클래스 패턴은 성공해요.
위치 패턴이 있으면, 그것들은 매치 전에 클래스 name_or_attr의 __match_args__ 속성을 사용해 키워드 패턴으로 변환돼요.
getattr(cls, "__match_args__", ())에 해당하는 호출이 이루어져요.- 이 호출이 예외를 발생시키면, 그 예외는 위로 전파돼요.
- 반환된 값이 튜플이 아니면, 변환은 실패하고
TypeError가 발생해요. - 위치 패턴이
len(cls.__match_args__)보다 많으면,TypeError가 발생해요. - 그 외에는, 위치 패턴 i가
__match_args__[i]를 키워드로 사용해 키워드 패턴으로 변환돼요.__match_args__[i]는 문자열이어야 하며, 아니면TypeError가 발생해요. - 중복 키워드가 있으면,
TypeError가 발생해요.
참고: 클래스 패턴 매칭에서 위치 인자 사용자 정의하기
모든 위치 패턴이 키워드 패턴으로 변환되면, 매치는 키워드 패턴만 있는 것처럼 진행돼요.
다음 내장 타입들에 대해서는 위치 서브패턴의 처리가 달라요.
bool,bytearray,bytes,dict,float,frozenset,int,list,set,str,tuple
이 클래스들은 단일 위치 인자를 받고, 그 패턴은 속성보다는 전체 객체에 대해 매치돼요. 예를 들어 int(0|1)은 값 0과 매치되지만, 값 0.0과는 매치되지 않아요.
쉬운 말로 CLS(P1, attr=P2)은 다음의 조건이 성립할 때만 매치돼요.
isinstance(<subject>, CLS)CLS.__match_args__를 사용해 P1을 키워드 패턴으로 변환- 각 키워드 인자
attr=P2에 대해:hasattr(<subject>, "attr")P2가<subject>.attr과 매치- … 대응하는 키워드 인자/패턴 쌍에 대해 이렇게 계속됨
참고: PEP 634 – 구조적 패턴 매칭: 명세 / PEP 636 – 구조적 패턴 매칭: 튜토리얼
8.7. 함수 정의 (Function definitions)
함수 정의는 사용자 정의 함수 객체를 정의해요('표준 타입 계층' 절 참고).
funcdef: [decorators] "def" funcname [type_params] "(" [parameter_list] ")"
["->" expression] ":" suite
decorators: decorator+
decorator: "@" assignment_expression NEWLINE
parameter_list: defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]]
| parameter_list_no_posonly
parameter_list_no_posonly: defparameter ("," defparameter)* ["," [parameter_list_starargs]]
| parameter_list_starargs
parameter_list_starargs: "*" star_parameter ("," defparameter)* ["," [parameter_star_kwargs]]
| "*" ("," defparameter)+ ["," [parameter_star_kwargs]]
| parameter_star_kwargs
parameter_star_kwargs: "**" parameter [","]
parameter: identifier [":" expression]
star_parameter: identifier [":" ["*"] expression]
defparameter: parameter ["=" expression]
funcname: identifier
함수 정의는 실행 가능한 문장(executable statement)이에요. 그것의 실행은 현재 지역 이름공간에서 함수 이름을 함수 객체(그 함수의 실행 가능한 코드를 감싼 래퍼)에 바인딩해요. 이 함수 객체는 함수가 호출될 때 사용할 전역 이름공간으로서 현재 전역 이름공간에 대한 참조를 담고 있어요.
함수 정의는 함수 본문을 실행하지 않아요. 본문은 함수가 호출될 때만 실행돼요. [4]
함수 정의는 하나 이상의 데코레이터 표현식으로 감쌀 수 있어요. 데코레이터 표현식은 함수가 정의될 때, 함수 정의를 담고 있는 스코프에서 평가돼요. 결과는 callable이어야 하고, 유일한 인자로 함수 객체를 받아 호출돼요. 반환된 값은 함수 객체 대신 함수 이름에 바인딩돼요. 여러 데코레이터는 중첩된 방식으로 적용돼요. 예를 들어, 다음 코드는
@f1(arg)
@f2
def func(): pass
대략 다음과 같아요.
def func(): pass
func = f1(arg)(f2(func))
단, 원래 함수가 이름 func에 임시로 바인딩되지 않는다는 점만 달라요.
버전 3.9에서 변경: 함수는 이제 유효한 assignment_expression으로 데코레이트할 수 있어요. 이전에는 문법이 훨씬 제한적이었어요. 자세한 내용은 PEP 614를 참고하세요.
함수의 이름과 그 파라미터 목록의 여는 괄호 사이의 대괄호 안에 타입 파라미터 목록을 줄 수 있어요. 이것은 정적 타입 검사기에 함수가 제네릭(generic)임을 알려줘요. 실행 시점에는 타입 파라미터를 함수의 __type_params__ 속성에서 얻을 수 있어요. 자세한 내용은 '제네릭 함수'를 참고하세요.
버전 3.12에서 변경: 타입 파라미터 목록은 Python 3.12에서 새로 추가됐어요.
하나 이상의 파라미터가 parameter = expression 형태를 가지면, 함수는 '기본 파라미터 값(default parameter values)'을 가진다고 말해요. 기본 값이 있는 파라미터의 경우, 호출에서 해당 인자를 생략할 수 있고, 그 경우 파라미터의 기본 값이 대체돼요. 파라미터에 기본 값이 있다면 "*"까지의 모든 뒤따르는 파라미터도 반드시 기본 값을 가져야 해요. 이는 문법이 표현하지 않는 구문적 제약이에요.
기본 파라미터 값은 함수 정의가 실행될 때 왼쪽에서 오른쪽으로 평가돼요. 즉, 함수가 정의될 때 표현식이 한 번 평가되고, 그 같은 '미리 계산된' 값이 각 호출에 사용된다는 뜻이에요. 기본 파라미터 값이 리스트나 딕셔너리 같은 가변(mutable) 객체일 때 이 점을 이해하는 것이 특히 중요해요. 함수가 그 객체를 수정하면(예: 리스트에 항목을 추가), 기본 파라미터 값이 실질적으로 수정돼요. 이는 대체로 의도한 바가 아니에요. 이를 우회하는 방법은 기본 값으로 None을 쓰고 함수 본문에서 명시적으로 검사하는 거예요. 예를 들어:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
함수 호출 의미론은 '호출(Calls)' 절에서 더 자세히 설명돼요. 함수 호출은 항상 파라미터 목록에 언급된 모든 파라미터에 값을 할당하는데, 위치 인자, 키워드 인자, 또는 기본 값에서 가져와요. "*identifier" 형태가 있으면, 그것은 초과 위치 파라미터를 받는 튜플로 초기화되고 기본적으로 빈 튜플이 돼요. "**identifier" 형태가 있으면, 그것은 초과 키워드 인자를 받는 새 순서 있는 매핑으로 초기화되고 기본적으로 같은 타입의 새 빈 매핑이 돼요. "*" 또는 "*identifier" 뒤의 파라미터는 키워드 전용(keyword-only) 파라미터이며 키워드 인자로만 전달될 수 있어요. "/" 앞의 파라미터는 위치 전용(positional-only) 파라미터이며 위치 인자로만 전달될 수 있어요.
버전 3.8에서 변경: / 함수 파라미터 문법이 위치 전용 파라미터를 나타내는 데 사용될 수 있게 됐어요. 자세한 내용은 PEP 570을 참고하세요.
파라미터는 파라미터 이름 뒤에 오는 ": expression" 형태의 애너테이션을 가질 수 있어요. 어떤 파라미터든 애너테이션을 가질 수 있는데, *identifier 또는 **identifier 형태의 파라미터조차 그래요. (특수한 경우로, *identifier 형태의 파라미터는 ": *expression" 애너테이션을 가질 수 있어요.) 함수는 파라미터 목록 뒤에 "-> expression" 형태의 '반환' 애너테이션을 가질 수 있어요. 이 애너테이션들은 유효한 Python 표현식이면 무엇이든 될 수 있어요. 애너테이션의 존재는 함수의 의미론을 바꾸지 않아요. 애너테이션에 대한 더 많은 정보는 '애너테이션' 절을 참고하세요.
버전 3.11에서 변경: *identifier 형태의 파라미터는 ": *expression" 애너테이션을 가질 수 있어요. PEP 646을 참고하세요.
표현식에서 즉시 사용하기 위해 익명 함수(이름에 바인딩되지 않은 함수)를 만드는 것도 가능해요. 이것은 '람다(Lambdas)' 절에 설명된 lambda 표현식을 사용해요. lambda 표현식은 단순화된 함수 정의의 축약형에 불과하다는 점에 유의하세요. "def" 문으로 정의된 함수는 lambda 표현식으로 정의된 함수처럼 전달하거나 다른 이름에 할당할 수 있어요. "def" 형태는 여러 문장과 애너테이션의 실행을 허용하므로 실제로 더 강력해요.
프로그래머 주의: 함수는 일급 객체(first-class objects)예요. 함수 정의 안에서 실행된 "def" 문은 반환하거나 전달할 수 있는 지역 함수를 정의해요. 중첩 함수에서 사용된 자유 변수(free variables)는 def를 담고 있는 함수의 지역 변수에 접근할 수 있어요. 자세한 내용은 '이름짓기와 바인딩(Naming and binding)' 절을 참고하세요.
참고:
- PEP 3107 – 함수 애너테이션 — 함수 애너테이션의 원래 명세.
- PEP 484 – 타입 힌트 — 애너테이션의 표준 의미(타입 힌트) 정의.
- PEP 526 – 변수 애너테이션 문법 — 클래스 변수와 인스턴스 변수를 포함해 변수 선언을 타입 힌트로 만들 수 있는 기능.
- PEP 563 – 포스트포닝된 애너테이션 평가 — 열심히 평가하는 대신 애너테이션을 런타임에 문자열 형태로 보존함으로써 애너테이션 안의 전방 참조를 지원.
- PEP 318 – 함수와 메서드용 데코레이터 — 함수와 메서드 데코레이터가 도입됨. 클래스 데코레이터는 PEP 3129에서 도입됨.
8.8. 클래스 정의 (Class definitions)
클래스 정의는 클래스 객체를 정의해요('표준 타입 계층' 절 참고).
classdef: [decorators] "class" classname [type_params] [inheritance] ":" suite
inheritance: "(" [argument_list] ")"
classname: identifier
클래스 정의는 실행 가능한 문장이에요. 상속 목록은 보통 기본 클래스 목록을 제공하므로('메타클래스'를 더 고급 용법으로 참고), 목록의 각 항목은 서브클래싱을 허용하는 클래스 객체로 평가되어야 해요. 상속 목록이 없는 클래스는 기본적으로 기본 클래스 object에서 상속해요. 따라서
class Foo:
pass
는 다음과 같아요.
class Foo(object):
pass
클래스의 수트는 새로 생성된 지역 이름공간과 원래의 전역 이름공간을 사용해, 새 실행 프레임(execution frame, '이름짓기와 바인딩' 참고)에서 실행돼요. (보통 수트는 대부분 함수 정의를 담고 있어요.) 클래스의 수트 실행이 끝나면, 그 실행 프레임은 버려지지만 지역 이름공간은 저장돼요. [5] 그런 다음 상속 목록을 기본 클래스로, 저장된 지역 이름공간을 속성 딕셔너리로 사용해 클래스 객체가 생성돼요. 클래스 이름은 원래 지역 이름공간에서 이 클래스 객체에 바인딩돼요.
클래스 본문에서 속성이 정의된 순서는 새 클래스의 __dict__에 보존돼요. 이 보존은 클래스가 생성된 직후와 정의 문법으로 정의된 클래스에 대해서만 신뢰할 수 있어요.
클래스 생성을 메타클래스로 크게 커스터마이즈할 수 있어요.
클래스도 데코레이트할 수 있어요. 함수를 데코레이트할 때와 마찬가지로,
@f1(arg)
@f2
class Foo: pass
는 대략 다음과 같아요.
class Foo: pass
Foo = f1(arg)(f2(Foo))
데코레이터 표현식의 평가 규칙은 함수 데코레이터와 동일해요. 그 결과가 클래스 이름에 바인딩돼요.
버전 3.9에서 변경: 클래스는 이제 유효한 assignment_expression으로 데코레이트할 수 있어요. 이전에는 문법이 훨씬 제한적이었어요. 자세한 내용은 PEP 614를 참고하세요.
클래스의 이름 바로 뒤의 대괄호 안에 타입 파라미터 목록을 줄 수 있어요. 이것은 정적 타입 검사기에 클래스가 제네릭임을 알려줘요. 실행 시점에는 타입 파라미터를 클래스의 __type_params__ 속성에서 얻을 수 있어요. 자세한 내용은 '제네릭 클래스'를 참고하세요.
버전 3.12에서 변경: 타입 파라미터 목록은 Python 3.12에서 새로 추가됐어요.
프로그래머 주의: 클래스 정의에서 정의된 변수는 클래스 속성(class attributes)이며 인스턴스들이 공유해요. 인스턴스 속성은 메서드 안에서 self.name = value로 설정할 수 있어요. 클래스 속성과 인스턴스 속성 모두 "self.name" 표기법으로 접근할 수 있고, 이런 방식으로 접근하면 인스턴스 속성이 같은 이름의 클래스 속성을 숨겨요. 클래스 속성은 인스턴스 속성의 기본 값으로 사용할 수 있지만, 거기에 가변 값을 쓰면 예상치 못한 결과가 나올 수 있어요. 디스크립터(Descriptor)를 사용하면 다른 구현 세부 사항을 가진 인스턴스 변수를 만들 수 있어요.
참고:
- PEP 3115 – Python 3000의 메타클래스 — 메타클래스의 선언을 현재 문법으로 바꾸고 메타클래스를 가진 클래스가 어떻게 구성되는지의 의미론을 바꾼 제안.
- PEP 3129 – 클래스 데코레이터 — 클래스 데코레이터를 추가한 제안. 함수와 메서드 데코레이터는 PEP 318에서 도입됨.
8.9. 코루틴 (Coroutines)
버전 3.5에서 추가됐어요.
8.9.1. 코루틴 함수 정의
async_funcdef: [decorators] "async" "def" funcname "(" [parameter_list] ")"
["->" expression] ":" suite
Python 코루틴의 실행은 여러 지점에서 중단되고 재개될 수 있어요('코루틴' 참고). await 표현식, async for, async with는 코루틴 함수의 본문에서만 사용할 수 있어요.
async def 문법으로 정의된 함수는 await 또는 async 키워드를 포함하지 않더라도 항상 코루틴 함수예요.
코루틴 함수의 본문 안에서 yield from 표현식을 사용하는 것은 SyntaxError예요.
코루틴 함수의 예시:
async def func(param1, param2):
do_stuff()
await some_coroutine()
버전 3.7에서 변경: await와 async가 이제 키워드가 됐어요. 이전에는 코루틴 함수의 본문 안에서만 그렇게 취급됐어요.
8.9.2. async for 문
async_for_stmt: "async" for_stmt
비동기 이터러블(asynchronous iterable)은 직접 비동기 이터레이터를 반환하는 __aiter__ 메서드를 제공하는데, 그 이터레이터는 __anext__ 메서드 안에서 비동기 코드를 호출할 수 있어요.
async for 문은 비동기 이터러블을 편리하게 순회할 수 있게 해 줘요.
다음 코드는
async for TARGET in ITER:
SUITE
else:
SUITE2
의미론적으로 다음과 같아요.
iter = (ITER).__aiter__()
running = True
while running:
try:
TARGET = await iter.__anext__()
except StopAsyncIteration:
running = False
else:
SUITE
else:
SUITE2
단, __aiter__()와 __anext__()에 대해 암묵적인 특수 메서드 조회가 사용된다는 점만 달라요.
코루틴 함수의 본문 바깥에서 async for 문을 사용하는 것은 SyntaxError예요.
8.9.3. async with 문
async_with_stmt: "async" with_stmt
비동기 컨텍스트 매니저(asynchronous context manager)는 enter와 exit 메서드에서 실행을 중단할 수 있는 컨텍스트 매니저예요.
다음 코드는
async with EXPRESSION as TARGET:
SUITE
의미론적으로 다음과 같아요.
manager = (EXPRESSION)
aenter = manager.__aenter__
aexit = manager.__aexit__
value = await aenter()
hit_except = False
try:
TARGET = value
SUITE
except:
hit_except = True
if not await aexit(*sys.exc_info()):
raise
finally:
if not hit_except:
await aexit(None, None, None)
단, __aenter__()와 __aexit__()에 대해 암묵적인 특수 메서드 조회가 사용된다는 점만 달라요.
코루틴 함수의 본문 바깥에서 async with 문을 사용하는 것은 SyntaxError예요.
참고: PEP 492 – async와 await 문법을 가진 코루틴 — 코루틴을 Python에서 제대로 된 독립 개념으로 만들고 지원 문법을 추가한 제안.
8.10. 타입 파라미터 목록 (Type parameter lists)
버전 3.12에서 추가됐어요.
버전 3.13에서 변경: 기본 값 지원이 추가됐어요 (PEP 696 참고).
type_params: "[" type_param ("," type_param)* "]"
type_param: typevar | typevartuple | paramspec
typevar: identifier (":" expression)? ("=" expression)?
typevartuple: "*" identifier ("=" expression)?
paramspec: "**" identifier ("=" expression)?
함수(코루틴 포함), 클래스, 그리고 타입 별칭은 타입 파라미터 목록을 담을 수 있어요.
def max[T](args: list[T]) -> T:
...
async def amax[T](args: list[T]) -> T:
...
class Bag[T]:
def __iter__(self) -> Iterator[T]:
...
def add(self, arg: T) -> None:
...
type ListOrSet[T] = list[T] | set[T]
의미론적으로, 이것은 함수, 클래스 또는 타입 별칭이 타입 변수에 대해 제네릭임을 나타내요. 이 정보는 주로 정적 타입 검사기가 사용하고, 실행 시점에는 제네릭 객체가 제네릭이 아닌 객체와 거의 비슷하게 동작해요.
타입 파라미터는 함수, 클래스 또는 타입 별칭의 이름 바로 뒤의 대괄호([]) 안에서 선언돼요. 타입 파라미터는 제네릭 객체의 스코프 안에서 접근할 수 있지만, 그 밖에서는 접근할 수 없어요. 따라서 def func[T](): pass 선언 후에는 이름 T가 모듈 스코프에서 사용할 수 없어요. 아래에서 제네릭 객체의 의미론이 더 정밀하게 설명돼요. 타입 파라미터의 스코프는 제네릭 객체의 생성을 감싸는 특수 함수(엄밀히 말하면 애너테이션 스코프)로 모델링돼요.
제네릭 함수, 클래스, 그리고 타입 별칭은 자신의 타입 파라미터를 나열하는 __type_params__ 속성을 가져요.
타입 파라미터는 세 종류가 있어요.
typing.TypeVar— 평범한 이름(예:T)으로 도입돼요. 의미론적으로 타입 검사기에 단일 타입을 나타내요.typing.TypeVarTuple— 단일 별표가 붙은 이름(예:*Ts)으로 도입돼요. 의미론적으로 임의 개수의 타입들의 튜플을 나타내요.typing.ParamSpec— 이중 별표가 붙은 이름(예:**P)으로 도입돼요. 의미론적으로 callable의 파라미터들을 나타내요.
typing.TypeVar 선언은 콜론(:) 뒤에 표현식을 붙여 바운드(bound)와 제약(constraints)을 정의할 수 있어요. 콜론 뒤의 단일 표현식은 바운드를 나타내요(예: T: int). 의미론적으로 이것은 typing.TypeVar이 이 바운드의 서브타입인 타입들만 나타낼 수 있다는 뜻이에요. 콜론 뒤의 괄호로 묶인 표현식 튜플은 제약 집합을 나타내요(예: T: (str, bytes)). 튜플의 각 구성원은 타입이어야 해요(다시 말하지만 이는 런타임에 강제되지 않아요). 제약된 타입 변수는 제약 목록의 타입 중 하나만 가질 수 있어요.
타입 파라미터 목록 문법으로 선언된 typing.TypeVar의 경우, 바운드와 제약은 제네릭 객체가 생성될 때가 아니라 __bound__와 __constraints__ 속성을 통해 값이 명시적으로 접근될 때만 평가돼요. 이를 위해 바운드나 제약은 별도의 애너테이션 스코프에서 평가돼요.
typing.TypeVarTuple과 typing.ParamSpec은 바운드나 제약을 가질 수 없어요.
세 종류의 타입 파라미터 모두 기본 값을 가질 수 있는데, 타입 파라미터가 명시적으로 제공되지 않을 때 사용돼요. 이것은 단일 등호(=) 뒤에 표현식을 붙여 추가해요. 타입 변수의 바운드와 제약처럼, 기본 값은 객체가 생성될 때가 아니라 타입 파라미터의 __default__ 속성이 접근될 때만 평가돼요. 이를 위해 기본 값은 별도의 애너테이션 스코프에서 평가돼요. 타입 파라미터에 기본 값이 지정되지 않으면, __default__ 속성은 특수 센티널 객체 typing.NoDefault로 설정돼요.
다음 예제는 허용된 타입 파라미터 선언들의 전체 집합을 보여줘요.
def overly_generic[
SimpleTypeVar,
TypeVarWithDefault = int,
TypeVarWithBound: int,
TypeVarWithConstraints: (str, bytes),
*SimpleTypeVarTuple = (int, float),
**SimpleParamSpec = (str, bytearray),
](
a: SimpleTypeVar,
b: TypeVarWithDefault,
c: TypeVarWithBound,
d: Callable[SimpleParamSpec, TypeVarWithConstraints],
*e: SimpleTypeVarTuple,
): ...
8.10.1. 제네릭 함수 (Generic functions)
제네릭 함수는 다음과 같이 선언돼요.
def func[T](arg: T): ...
이 문법은 다음과 같아요.
annotation-def TYPE_PARAMS_OF_func():
T = typing.TypeVar("T")
def func(arg: T): ...
func.__type_params__ = (T,)
return func
func = TYPE_PARAMS_OF_func()
여기서 annotation-def는 애너테이션 스코프를 나타내며, 실행 시점에 어떤 이름에도 실제로 바인딩되지 않아요. (변환에서 취해진 또 하나의 자유: 문법이 typing 모듈에 대한 속성 접근을 거치지 않고 typing.TypeVar의 인스턴스를 직접 생성해요.)
제네릭 함수의 애너테이션은 타입 파라미터를 선언하는 데 사용된 애너테이션 스코프 안에서 평가되지만, 함수의 기본 값과 데코레이터는 그렇지 않아요.
다음 예제는 이러한 경우들에 대한 범위 규칙과 타입 파라미터의 추가 종류를 보여줘요.
@decorator
def func[T: int, *Ts, **P](*args: *Ts, arg: Callable[P, T] = some_default):
...
TypeVar 바운드의 지연 평가를 제외하면, 이는 다음과 같아요.
DEFAULT_OF_arg = some_default
annotation-def TYPE_PARAMS_OF_func():
annotation-def BOUND_OF_T():
return int
# In reality, BOUND_OF_T() is evaluated only on demand.
T = typing.TypeVar("T", bound=BOUND_OF_T())
Ts = typing.TypeVarTuple("Ts")
P = typing.ParamSpec("P")
def func(*args: *Ts, arg: Callable[P, T] = DEFAULT_OF_arg):
...
func.__type_params__ = (T, Ts, P)
return func
func = decorator(TYPE_PARAMS_OF_func())
DEFAULT_OF_arg 같은 대문자 이름은 실제로 실행 시점에 바인딩되지 않아요.
8.10.2. 제네릭 클래스 (Generic classes)
제네릭 클래스는 다음과 같이 선언돼요.
class Bag[T]: ...
이 문법은 다음과 같아요.
annotation-def TYPE_PARAMS_OF_Bag():
T = typing.TypeVar("T")
class Bag(typing.Generic[T]):
__type_params__ = (T,)
...
return Bag
Bag = TYPE_PARAMS_OF_Bag()
여기서도 annotation-def(실제 키워드가 아님)가 애너테이션 스코프를 나타내고, TYPE_PARAMS_OF_Bag라는 이름은 실행 시점에 실제로 바인딩되지 않아요.
제네릭 클래스는 typing.Generic에서 암묵적으로 상속해요. 제네릭 클래스의 기본 클래스와 키워드 인자는 타입 파라미터에 대한 타입 스코프 안에서 평가되고, 데코레이터는 그 스코프 밖에서 평가돼요. 이는 다음 예제로 설명돼요.
@decorator
class Bag(Base[T], arg=T): ...
이는 다음과 같아요.
annotation-def TYPE_PARAMS_OF_Bag():
T = typing.TypeVar("T")
class Bag(Base[T], typing.Generic[T], arg=T):
__type_params__ = (T,)
...
return Bag
Bag = decorator(TYPE_PARAMS_OF_Bag())
8.10.3. 제네릭 타입 별칭 (Generic type aliases)
type 문으로 제네릭 타입 별칭을 만들 수도 있어요.
type ListOrSet[T] = list[T] | set[T]
값의 지연 평가를 제외하면, 이는 다음과 같아요.
annotation-def TYPE_PARAMS_OF_ListOrSet():
T = typing.TypeVar("T")
annotation-def VALUE_OF_ListOrSet():
return list[T] | set[T]
# In reality, the value is lazily evaluated
return typing.TypeAliasType("ListOrSet", VALUE_OF_ListOrSet(), type_params=(T,))
ListOrSet = TYPE_PARAMS_OF_ListOrSet()
여기서 annotation-def(실제 키워드가 아님)가 애너테이션 스코프를 나타내요. TYPE_PARAMS_OF_ListOrSet 같은 대문자 이름은 실행 시점에 실제로 바인딩되지 않아요.
8.11. 애너테이션 (Annotations)
버전 3.14에서 변경: 애너테이션이 이제 기본적으로 지연 평가돼요.
변수와 함수 파라미터는 애너테이션을 담을 수 있는데, 이름 뒤에 콜론을 추가하고 그 뒤에 표현식을 붙여 만들 수 있어요.
x: annotation = 1
def f(param: annotation): ...
함수는 화살표 뒤에 오는 반환 애너테이션도 담을 수 있어요.
def f() -> annotation: ...
애너테이션은 관례적으로 타입 힌트에 쓰이지만, 언어가 강제하지는 않아요. 일반적으로 애너테이션은 임의의 표현식을 담을 수 있어요. 애너테이션의 존재는 애너테이션을 조사(introspect)하고 사용하는 어떤 메커니즘(예: dataclasses 또는 @functools.singledispatch)이 쓰이지 않는 한, 코드의 런타임 의미론을 바꾸지 않아요.
기본적으로 애너테이션은 애너테이션 스코프에서 지연 평가돼요. 즉, 애너테이션을 담고 있는 코드가 평가될 때 함께 평가되지 않아요. 대신 인터프리터는 요청되면 나중에 애너테이션을 평가하는 데 사용할 수 있는 정보를 저장해요. annotationlib 모듈은 애너테이션을 평가하기 위한 도구를 제공해요.
from __future__ import annotations 미래 문(future statement)이 있으면, 모든 애너테이션은 대신 문자열로 저장돼요.
>>> from __future__ import annotations
>>> def f(param: annotation): ...
>>> f.__annotations__
{'param': 'annotation'}
이 미래 문은 향후 Python 버전에서 사용 중단되고 제거될 거예요. 하지만 Python 3.13이 수명이 다할 때까지는 제거되지 않아요 (PEP 749 참고). 그것이 사용되면 annotationlib.get_annotations()와 typing.get_type_hints() 같은 조사 도구들이 실행 시점에 애너테이션을 해석하지 못할 가능성이 더 높아져요.
각주
[1] 예외는 호출 스택(invocation stack)으로 전파되며, 다른 예외를 발생시키는 finally 절이 있지 않는 한 그래요. 그 새 예외가 이전 예외를 잃게 만들어요.
[2] 패턴 매칭에서 시퀀스는 다음 중 하나로 정의돼요.
collections.abc.Sequence에서 상속하는 클래스collections.abc.Sequence로 등록된 Python 클래스- (CPython에서)
Py_TPFLAGS_SEQUENCE비트가 설정된 내장 클래스 - 위의 어느 것에서든 상속하는 클래스
다음 표준 라이브러리 클래스들은 시퀀스예요.
array.arraycollections.dequelistmemoryviewrangetuple
참고:
str,bytes,bytearray타입의 대상 값은 시퀀스 패턴과 일치하지 않아요.
[3] 패턴 매칭에서 매핑은 다음 중 하나로 정의돼요.
collections.abc.Mapping에서 상속하는 클래스collections.abc.Mapping으로 등록된 Python 클래스- (CPython에서)
Py_TPFLAGS_MAPPING비트가 설정된 내장 클래스 - 위의 어느 것에서든 상속하는 클래스
표준 라이브러리 클래스 dict와 types.MappingProxyType은 매핑이에요.
[4] 함수 본문의 첫 번째 문장으로 나타나는 문자열 리터럴은 함수의 __doc__ 속성, 즉 함수의 docstring으로 변환돼요.
[5] 클래스 본문의 첫 번째 문장으로 나타나는 문자열 리터럴은 이름공간의 __doc__ 항목, 즉 클래스의 docstring으로 변환돼요.
더 알아보기
- PEP 634 – 구조적 패턴 매칭(Sructural Pattern Matching): 명세
- PEP 636 – 구조적 패턴 매칭: 튜토리얼
- PEP 492 – async와 await 문법을 가진 코루틴
- PEP 343 –
with문 - PEP 570 – 위치 전용 파라미터
- PEP 646 – Variadic 제네릭
- PEP 614 – 완화된 데코레이터 문법
- PEP 3115, PEP 3129 – 메타클래스와 클래스 데코레이터