8. 복합 문장

8. 복합 문장 (Compound statements)

복합 문장(compound statement)은 다른 문장들의 (집합)을 포함하며, 어떤 방식으로든 그 문장들의 실행에 영향을 주거나 제어해요. 일반적으로 복합 문장은 여러 줄에 걸쳐 있지만, 단순한 형태에서는 전체 복합 문장이 한 줄에 포함될 수 있어요.

출처: 8. Compound statements

본문

복합 문장은 다른 문장들의 (집합)을 포함하며, 어떤 방식으로든 그 문장들의 실행에 영향을 주거나 제어해요. 일반적으로 복합 문장은 여러 줄에 걸쳐 있지만, 단순한 형태에서는 전체 복합 문장이 한 줄에 포함될 수 있어요.

if, while, for 문은 전통적인 제어 흐름 구조를 구현해요. try는 문장 그룹에 대한 예외 처리기 및/또는 정리 코드를 지정하고, with 문은 코드 블록 주위에서 초기화와 종료(finalization) 코드의 실행을 허용해요. 함수와 클래스 정의도 문법적으로 복합 문장이에요.

복합 문장은 하나 이상의 '절(clause)'로 구성돼요. 절은 헤더와 '수트(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가 올 수 있다는 점에 유의하세요. 또한 선택적 연속 절은 항상 문장을 시작할 수 없는 키워드로 시작하므로 모호함이 없어요 ('dangling else' 문제는 Python에서 중첩된 if 문을 들여쓰도록 요구함으로써 해결돼요).

다음 섹션들의 문법 규칙 서식은 명확성을 위해 각 절을 별도의 줄에 배치해요.

8.1. if 문장 (The if statement)

if 문장은 조건부 실행에 사용돼요:

if_stmt: "if" assignment_expression ":" suite ("elif" assignment_expression ":" suite)* ["else" ":" suite]

그것은 표현식들을 하나씩 평가해 참인 것이 발견될 때까지 진행해서(참과 거짓의 정의는 Boolean operations 섹션 참조) 수트 중 정확히 하나를 선택해요. 그런 다음 그 수트가 실행돼요 (그리고 if 문장의 다른 부분은 실행되거나 평가되지 않아요). 모든 표현식이 거짓이면, 있다면 else 절의 수트가 실행돼요.

8.2. while 문장 (The while statement)

while 문장은 표현식이 참인 동안 반복 실행에 사용돼요:

while_stmt: "while" assignment_expression ":" suite ["else" ":" suite]

이것은 표현식을 반복적으로 테스트하고, 참이면 첫 번째 수트를 실행해요. 표현식이 거짓이면(첫 번째 테스트일 수 있어요), 있다면 else 절의 수트가 실행되고 루프가 종료돼요.

첫 번째 수트에서 실행된 break 문장은 else 절의 수트를 실행하지 않고 루프를 종료해요. 첫 번째 수트에서 실행된 continue 문장은 수트의 나머지를 건너뛰고 표현식 테스트로 돌아가요.

8.3. for 문장 (The for statement)

for 문장은 시퀀스(문자열, 튜플, 목록 같은)나 다른 iterable 객체의 요소를 반복하는 데 사용돼요:

for_stmt: "for" target_list "in" starred_expression_list ":" suite ["else" ":" suite]

starred_expression_list 표현식은 한 번 평가돼요. 그것은 iterable 객체를 산출해야 해요. 그 iterable에 대해 iterator가 생성돼요. iterator가 제공하는 첫 번째 항목이 할당에 대한 표준 규칙(Assignment statements 참조)을 사용해 대상 목록에 할당되고, 수트가 실행돼요. 이것은 iterator가 제공하는 각 항목에 대해 반복돼요. iterator가 소진되면, 있다면 else 절의 수트가 실행되고 루프가 종료돼요.

첫 번째 수트에서 실행된 break 문장은 else 절의 수트를 실행하지 않고 루프를 종료해요. 첫 번째 수트에서 실행된 continue 문장은 수트의 나머지를 건너뛰고 다음 항목으로 계속하거나, 다음 항목이 없으면 else 절로 계속돼요.

for 루프는 대상 목록의 변수들에 할당을 만들어요. 이것은 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

루프가 끝나도 대상 목록의 이름은 삭제되지 않지만, 시퀀스가 비어 있으면 루프가 전혀 할당하지 않았을 거예요. 힌트: 내장 타입 range()는 정수의 불변 산술 시퀀스를 나타내요. 예를 들어 range(3)을 반복하면 0, 1, 2를 차례로 산출해요.

버전 3.11에서 변경: 표현식 목록에서 starred 요소가 이제 허용돼요.

8.4. try 문장 (The try statement)

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

예외에 대한 추가 정보는 Exceptions 섹션, 예외를 생성하기 위한 raise 문장 사용에 대한 정보는 The raise statement 섹션에서 찾을 수 있어요.

버전 3.14에서 변경: 여러 예외 타입을 사용할 때 그룹화 괄호를 선택적으로 생략하는 지원. PEP 758 참조.

8.4.1. except 절 (except clause)

except 절(들)은 하나 이상의 예외 처리기를 지정해요. try 절에서 예외가 발생하지 않으면 예외 처리기가 실행되지 않아요. try 수트에서 예외가 발생하면 예외 처리기에 대한 검색이 시작돼요. 이 검색은 예외와 일치하는 것이 발견될 때까지 except 절들을 차례로 검사해요. 표현식이 없는 except 절은, 있으면 마지막에 있어야 해요. 그것은 어떤 예외든 일치해요.

표현식이 있는 except 절의 경우, 그 표현식은 예외 타입이나 예외 타입들의 튜플로 평가되어야 해요. 여러 예외 타입이 제공되고 as 절이 사용되지 않으면 괄호를 생략할 수 있어요. 발생된 예외는 표현식이 예외 객체의 클래스나 비가상 기반 클래스, 또는 그러한 클래스를 포함하는 튜플로 평가되는 except 절과 일치해요.

어떤 except 절도 예외와 일치하지 않으면, 예외 처리기 검색은 주변 코드와 호출 스택에서 계속돼요. [1]

except 절 헤더의 표현식 평가가 예외를 발생시키면, 처리기에 대한 원래 검색이 취소되고 새 예외에 대한 검색이 주변 코드와 호출 스택에서 시작돼요 (마치 전체 try 문장이 예외를 발생시킨 것처럼 취급돼요).

일치하는 except 절이 발견되면, 예외는 있다면 그 except 절의 as 키워드 뒤에 지정된 대상에 할당되고, 그 except 절의 수트가 실행돼요. 모든 except 절은 실행 가능한 블록을 가져야 해요. 이 블록의 끝에 도달하면 전체 try 문장 뒤에서 실행이 정상적으로 계속돼요. (이것은 같은 예외에 대해 두 개의 중첩 처리기가 존재하고 예외가 내부 처리기의 try 절에서 발생하면, 외부 처리기가 예외를 처리하지 않는다는 뜻이에요.)

예외가 as target을 사용해 할당되면, except 절의 끝에서 해제돼요. 마치:

except E as N:
    foo

가 다음으로 번역된 것과 같아요:

except E as N:
    try:
        foo
    finally:
        del N

이것은 except 절 이후에 그것을 참조할 수 있으려면 예외를 다른 이름에 할당해야 한다는 뜻이에요. 예외가 해제되는 이유는, 예외에 추적이 붙어 있으면 스택 프레임과 참조 사이클을 형성해, 다음 가비지 컬렉션이 발생할 때까지 그 프레임의 모든 지역 변수를 살아 있게 하기 때문이에요.

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* clause)

except* 절(들)은 예외 그룹(BaseExceptionGroup 인스턴스)에 대한 하나 이상의 처리기를 지정해요. try 문장은 except 또는 except* 절을 가질 수 있지만 둘 다 가질 수는 없어요. except*의 경우 일치시킬 예외 타입이 필수여서, except*:는 문법 오류예요. 타입은 except의 경우처럼 해석되지만, 일치는 처리되는 그룹에 포함된 예외들에 대해 수행돼요. 일치하는 타입이 BaseExceptionGroup의 하위 클래스이면 TypeError가 발생해요. 모호한 의미론을 갖기 때문이에요.

try 블록에서 예외 그룹이 발생하면, 각 except* 절은 그것을(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, returnexcept* 절에 나타날 수 없어요.

8.4.3. else 절 (else clause)

선택적 else 절은 try 수트에서 제어 흐름이 떠나고, 예외가 발생하지 않았고, return, continue, break 문장이 실행되지 않았을 때 실행돼요. else 절의 예외는 앞선 except 절들이 처리하지 않아요.

8.4.4. finally 절 (finally clause)

finally가 있으면, 그것은 '정리(cleanup)' 처리기를 지정해요. try 절이 실행되며, 모든 exceptelse 절을 포함해요. 어떤 절에서 예외가 발생하고 처리되지 않으면, 예외는 일시적으로 저장돼요. finally 절이 실행돼요. 저장된 예외가 있으면 finally 절의 끝에서 다시 발생해요. finally 절이 다른 예외를 발생시키면, 저장된 예외가 새 예외의 컨텍스트로 설정돼요. finally 절이 return, break, continue 문장을 실행하면, 저장된 예외는 폐기돼요. 예를 들어 다음 함수는 42를 반환해요.

def f():
    try:
        1/0
    finally:
        return 42

예외 정보는 finally 절 실행 중에는 프로그램이 사용할 수 없어요.

tryfinally 문장의 try 수트에서 return, break, continue 문장이 실행되면, finally 절도 '나가는 길에' 실행돼요.

함수의 반환 값은 마지막으로 실행된 return 문장에 의해 결정돼요. finally 절이 항상 실행되므로, finally 절에서 실행된 return 문장이 항상 마지막으로 실행되는 것이 될 거예요. 다음 함수는 'finally'를 반환해요.

def foo():
    try:
        return 'try'
    finally:
        return 'finally'

버전 3.8에서 변경: Python 3.8 이전에는 구현의 문제로 finally 절에서 continue 문장이 불법이었어요.

버전 3.14에서 변경: 컴파일러가 finally 블록에 return, break, continue가 나타나면 SyntaxWarning을 발생시켜요 (PEP 765 참조).

8.5. with 문장 (The with statement)

with 문장은 컨텍스트 매니저가 정의한 메서드로 블록의 실행을 감싸는 데 사용돼요 (With Statement Context Managers 섹션 참조). 이것은 일반적인 tryexceptfinally 사용 패턴을 편리한 재사용을 위해 캡슐화할 수 있게 해줘요.

with_stmt: "with" ( "(" with_stmt_contents ","? ")" | with_stmt_contents ) ":" suite with_stmt_contents: with_item ("," with_item)* with_item: expression ["as" target]

하나의 "item"을 가진 with 문장의 실행은 다음과 같이 진행돼요:

  • 컨텍스트 표현식(with_item에 주어진 표현식)이 컨텍스트 매니저를 얻기 위해 평가돼요.
  • 컨텍스트 매니저의 __enter__()가 나중에 사용하기 위해 로드돼요.
  • 컨텍스트 매니저의 __exit__()가 나중에 사용하기 위해 로드돼요.
  • 컨텍스트 매니저의 __enter__() 메서드가 호출돼요.
  • with 문장에 대상이 포함되었으면, __enter__()의 반환 값이 그것에 할당돼요.
    • 참고: with 문장은 __enter__() 메서드가 오류 없이 반환하면 __exit__()가 항상 호출될 것을 보장해요. 따라서 대상 목록에 대한 할당 중에 오류가 발생하면, 그것은 수트 안에서 발생한 오류와 동일하게 취급돼요. 아래 7단계 참조.
  • 수트가 실행돼요.
  • 컨텍스트 매니저의 __exit__() 메서드가 호출돼요. 예외가 수트를 떠나게 했다면, 그 타입, 값, 추적이 __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 문장 (The match statement)

버전 3.10에서 추가.

match 문장은 패턴 매칭에 사용돼요. 문법:

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

참고: 이 섹션은 소프트 키워드를 나타내는 데 작은따옴표를 사용해요.

패턴 매칭은 (case 뒤의) 패턴을 입력으로, (match 뒤의) subject 값을 받아요. 패턴(하위 패턴을 포함할 수 있어요)은 subject 값에 대해 일치돼요. 결과는:

  • 매치 성공 또는 실패(패턴 성공 또는 실패라고도 해요).
  • 일치된 값을 이름에 바인딩할 가능성. 이것의 전제 조건은 아래에서 더 논의돼요.

matchcase 키워드는 소프트 키워드예요.

참고 자료

  • PEP 634 – Structural Pattern Matching: Specification
  • PEP 636 – Structural Pattern Matching: Tutorial

8.6.1. 개요 (Overview)

match 문장의 논리적 흐름에 대한 개요예요:

  • subject 표현식 subject_expr이 평가되고 결과 subject 값이 얻어져요. subject 표현식이 쉼표를 포함하면, 표준 규칙을 사용해 튜플이 구성돼요.
  • case_block의 각 패턴이 subject 값과 일치하도록 시도돼요. 성공 또는 실패의 구체적인 규칙은 아래에서 설명해요. 매치 시도는 패턴 안의 일부 또는 모든 독립 이름을 바인딩할 수도 있어요. 정확한 패턴 바인딩 규칙은 패턴 타입마다 다르며 아래에 지정돼요. 성공적인 패턴 매치 중에 만들어진 이름 바인딩은 실행된 블록을 넘어 살아남고 match 문장 이후에도 사용될 수 있어요.
    • 참고: 실패한 패턴 매치 중에 일부 하위 패턴은 성공할 수 있어요. 실패한 매치에 대해 바인딩이 만들어질 것에 의존하지 마세요. 반대로, 실패한 매치 이후에 변수가 변경되지 않은 채 남아 있을 것에 의존하지도 마세요. 정확한 동작은 구현에 의존하며 달라질 수 있어요. 이것은 서로 다른 구현이 최적화를 추가할 수 있도록 한 의도적인 결정이에요.
  • 패턴이 성공하면, 해당 가드(있다면)가 평가돼요. 이 경우 모든 이름 바인딩이 일어났음이 보장돼요.
  • 가드가 참으로 평가되거나 없으면, case_block 안의 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의 일부인 가드(guard)가 성공해야 해요. 그것은 if 뒤에 표현식이 오는 형태예요.

guard가 있는 case 블록의 논리적 흐름은 다음과 같아요:

  • case 블록의 패턴이 성공했는지 확인해요. 패턴이 실패하면 가드는 평가되지 않고 다음 case 블록이 확인돼요.
  • 패턴이 성공했으면 가드를 평가해요.
  • 가드 조건이 참으로 평가되면 case 블록이 선택돼요.
  • 가드 조건이 거짓으로 평가되면 case 블록이 선택되지 않아요.
  • 가드가 평가 중에 예외를 발생시키면 예외가 위로 전파돼요.

가드는 표현식이므로 부수 효과를 가질 수 있어요. 가드 평가는 첫 번째에서 마지막 case 블록까지, 패턴이 모두 성공하지 못한 case 블록을 건너뛰면서 하나씩 진행해야 해요. (즉, 가드 평가는 순서대로 일어나야 해요.) case 블록이 선택되면 가드 평가는 멈춰야 해요.

8.6.3. 불가반 (Irrefutable) Case 블록

불가반 case 블록은 모두 매치하는(match-all) case 블록이에요. match 문장은 기껏해야 하나의 불가반 case 블록을 가질 수 있고, 그것은 마지막이어야 해요.

case 블록은 가드가 없고 그 패턴이 불가반이면 불가반으로 간주돼요. 패턴은 그 문법만으로 항상 성공할 것임을 증명할 수 있으면 불가반으로 간주돼요. 다음 패턴들만 불가반이에요:

  • 왼쪽이 불가반인 AS 패턴
  • 적어도 하나의 불가반 패턴을 포함하는 OR 패턴
  • 캡처 패턴
  • 와일드카드 패턴
  • 괄호로 둘러싸인 불가반 패턴

8.6.4. 패턴 (Patterns)

참고: 이 섹션은 표준 EBNF를 넘어서는 문법 표기를 사용해요:

  • 표기 SEP.RULE+RULE (SEP RULE)*의 축약이에요
  • 표기 !RULE은 음의 lookahead 단언의 축약이에요

patterns의 최상위 문법은:

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 Patterns)

OR 패턴은 세로 막대 |로 구분된 둘 이상의 패턴이에요. 문법:

or_pattern: "|".closed_pattern+

오직 마지막 하위 패턴만 불가반일 수 있고, 각 하위 패턴은 모호함을 피하기 위해 같은 이름 집합을 바인딩해야 해요.

OR 패턴은 하위 패턴 각각을 차례로 subject 값에 대해 일치시키고, 하나가 성공할 때까지 해요. 그러면 OR 패턴이 성공한 것으로 간주돼요. 그렇지 않고 하위 패턴 중 어느 것도 성공하지 않으면 OR 패턴은 실패해요.

간단한 용어로, P1 | P2 | ...P1을 일치시키려 하고, 실패하면 P2를 일치시키려 하며, 어느 하나라도 성공하면 즉시 성공하고, 그렇지 않으면 실패해요.

8.6.4.2. AS 패턴 (AS Patterns)

AS 패턴은 as 키워드 왼쪽의 OR 패턴을 subject에 대해 일치시켜요. 문법:

as_pattern: or_pattern "as" capture_pattern

OR 패턴이 실패하면 AS 패턴도 실패해요. 그렇지 않으면 AS 패턴은 subject를 as 키워드 오른쪽의 이름에 바인딩하고 성공해요. capture_pattern_일 수 없어요.

간단한 용어로 P as NAMEP로 일치시키고, 성공하면 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 문법에 정의돼 있어요. 삼중 따옴표 문자열이 지원돼요. 원시 문자열과 바이트 문자열이 지원돼요. f-strings과 t-strings은 지원되지 않아요.

signed_number '+' NUMBERsigned_number '-' NUMBER 형태는 복소수를 표현하기 위한 것이에요. 왼쪽에 실수가, 오른쪽에 허수가 필요해요. 예: 3 + 4j.

간단한 용어로 LITERAL<subject> == LITERAL일 때만 성공해요. 싱글턴 None, True, False에 대해서는 is 연산자가 사용돼요.

8.6.4.4. 캡처 패턴 (Capture Patterns)

캡처 패턴은 subject 값을 이름에 바인딩해요. 문법:

capture_pattern: !'_' NAME

단일 밑줄 _은 캡처 패턴이 아니에요 (이것이 !'_'가 표현하는 것이에요). 대신 wildcard_pattern으로 취급돼요.

주어진 패턴에서 주어진 이름은 한 번만 바인딩될 수 있어요. 예: case x, x: ...은 유효하지 않지만 case [x] | x: ...은 허용돼요.

캡처 패턴은 항상 성공해요. 바인딩은 PEP 572의 할당 표현식 연산자가 확립한 스코프 규칙을 따라요. 적용 가능한 global 또는 nonlocal 문장이 없으면 그 이름은 가장 가까운 포함 함수 스코프의 지역 변수가 돼요.

간단한 용어로 NAME은 항상 성공하고 NAME = <subject>를 설정해요.

8.6.4.5. 와일드카드 패턴 (Wildcard Patterns)

와일드카드 패턴은 항상 성공하고(무엇이든 일치) 어떤 이름도 바인딩하지 않아요. 문법:

wildcard_pattern: '_'

_는 어떤 패턴 안에서도 소프트 키워드이지만, 패턴 안에서만 그래요. 그것은 match subject 표현식, guard, case 블록 안에서도 평소와 같이 식별자예요.

간단한 용어로 _는 항상 성공해요.

8.6.4.6. 값 패턴 (Value Patterns)

값 패턴은 Python의 이름 있는 값을 나타내요. 문법:

value_pattern: attr attr: name_or_attr "." NAME name_or_attr: attr | NAME

패턴의 점으로 구분된 이름은 표준 Python 이름 해석 규칙을 사용해 조회돼요. 발견된 값이 (== 동등 연산자를 사용해) subject 값과 비교해 같으면 패턴이 성공해요.

간단한 용어로 NAME1.NAME2<subject> == NAME1.NAME2일 때만 성공해요.

참고: 같은 값이 같은 match 문장에 여러 번 나타나면, 인터프리터가 처음 찾은 값을 캐시하고 같은 조회를 반복하는 대신 재사용할 수 있어요. 이 캐시는 주어진 match 문장의 주어진 실행에 엄격히 묶여 있어요.

8.6.4.7. 그룹 패턴 (Group Patterns)

그룹 패턴은 사용자가 의도된 그룹화를 강조하기 위해 패턴 주위에 괄호를 추가할 수 있게 해줘요. 그 외에는 추가 문법이 없어요. 문법:

group_pattern: "(" pattern ")"

간단한 용어로 (P)P와 같은 효과를 가져요.

8.6.4.8. 시퀀스 패턴 (Sequence Patterns)

시퀀스 패턴은 시퀀스 요소에 대해 일치시킬 여러 하위 패턴을 포함해요. 문법은 목록이나 튜플의 언패킹과 유사해요.

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)

시퀀스 패턴에 괄호를 사용하는지 대괄호를 사용하는지(i.e. (...) vs [...])에는 차이가 없어요.

참고: 뒤따르는 쉼표 없이 괄호로 둘러싸인 단일 패턴(예: (3 | 4))은 그룹 패턴이에요. 반면 대괄호로 둘러싸인 단일 패턴(예: [3 | 4])은 여전히 시퀀스 패턴이에요.

시퀀스 패턴에는 기껏해야 하나의 star 하위 패턴이 있을 수 있어요. star 하위 패턴은 어떤 위치에서든 발생할 수 있어요. star 하위 패턴이 없으면 시퀀스 패턴은 고정 길이 시퀀스 패턴이고, 그렇지 않으면 가변 길이 시퀀스 패턴이에요.

시퀀스 패턴을 subject 값에 대해 일치시키는 논리적 흐름은 다음과 같아요:

  • subject 값이 시퀀스가 아니면 [2], 시퀀스 패턴이 실패해요.
  • subject 값이 str, bytes, bytearray의 인스턴스이면 시퀀스 패턴이 실패해요.
  • 이후 단계는 시퀀스 패턴이 고정 길이인지 가변 길이인지에 따라 달라져요.

시퀀스 패턴이 고정 길이이면:

  • subject 시퀀스의 길이가 하위 패턴의 수와 같지 않으면 시퀀스 패턴이 실패해요.
  • 시퀀스 패턴의 하위 패턴들이 subject 시퀀스의 해당 항목에 왼쪽에서 오른쪽으로 일치돼요. 하위 패턴이 실패하는 즉시 일치가 멈춰요. 모든 하위 패턴이 해당 항목 일치에 성공하면 시퀀스 패턴이 성공해요.

그렇지 않고 시퀀스 패턴이 가변 길이이면:

  • subject 시퀀스의 길이가 star가 아닌 하위 패턴의 수보다 작으면 시퀀스 패턴이 실패해요.
  • 선행 star가 아닌 하위 패턴들이 고정 길이 시퀀스에서처럼 해당 항목에 일치돼요.
  • 이전 단계가 성공하면, star 하위 패턴은 star 하위 패턴을 따르는 star가 아닌 하위 패턴에 해당하는 남은 항목을 제외한, 남은 subject 항목들로 형성된 목록과 일치해요.
  • 남은 star가 아닌 하위 패턴들이 고정 길이 시퀀스에서처럼 해당 subject 항목에 일치돼요.

참고: subject 시퀀스의 길이는 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를 발생시켜요.

매핑 패턴을 subject 값에 대해 일치시키는 논리적 흐름은 다음과 같아요:

  • subject 값이 매핑이 아니면 [3], 매핑 패턴이 실패해요.
  • 매핑 패턴에 주어진 모든 키가 subject 매핑에 존재하고, 각 키의 패턴이 subject 매핑의 해당 항목과 일치하면, 매핑 패턴이 성공해요.
  • 매핑 패턴에서 중복 키가 감지되면, 패턴은 유효하지 않은 것으로 간주돼요. 중복 리터럴 값에 대해 SyntaxError가 발생하고, 같은 값의 이름 있는 키에 대해서는 ValueError가 발생해요.

참고: 키-값 쌍은 매핑 subject의 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

클래스 패턴에서 같은 키워드는 반복되지 않아야 해요.

클래스 패턴을 subject 값에 대해 일치시키는 논리적 흐름은 다음과 같아요:

  • name_or_attr이 내장 type의 인스턴스가 아니면 TypeError를 발생시켜요.
  • subject 값이 name_or_attr의 인스턴스가 아니면(isinstance()로 테스트), 클래스 패턴이 실패해요.
  • 패턴 인자가 없으면 패턴이 성공해요. 그렇지 않으면 이후 단계는 키워드 또는 위치 인자 패턴이 있느냐에 따라 달라져요.
  • 아래에 지정된 여러 내장 타입에 대해 단일 위치 하위 패턴이 허용되며, 그것은 전체 subject와 일치해요. 이런 타입에 대해서는 키워드 패턴도 다른 타입에서처럼 작동해요.

키워드 패턴만 있으면, 하나씩 다음과 같이 처리돼요:

  • 키워드가 subject의 속성으로 조회돼요.
  • 이것이 AttributeError 이외의 예외를 발생시키면 예외가 위로 전파돼요.
  • 이것이 AttributeError를 발생시키면 클래스 패턴이 실패했어요.
  • 그렇지 않으면, 키워드 패턴과 연관된 하위 패턴이 subject의 속성 값과 일치돼요. 실패하면 클래스 패턴이 실패하고, 성공하면 다음 키워드로 진행돼요.
  • 모든 키워드 패턴이 성공하면 클래스 패턴이 성공해요.

위치 패턴이 있으면, 일치 전에 클래스 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 – Structural Pattern Matching: Specification
  • PEP 636 – Structural Pattern Matching: Tutorial

8.7. 함수 정의 (Function definitions)

함수 정의는 사용자 정의 함수 객체를 정의해요 (The standard type hierarchy 섹션 참조):

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

함수 정의는 실행 가능한 문장이에요. 그것의 실행은 현재 지역 네임스페이스의 함수 이름을 함수 객체(그 함수의 실행 가능한 코드를 감싸는 래퍼)에 바인딩해요. 이 함수 객체는 함수가 호출될 때 사용될 전역 네임스페이스로서 현재 전역 네임스페이스에 대한 참조를 포함해요.

함수 정의는 함수 본문을 실행하지 않아요. 이것은 함수가 호출될 때만 실행돼요. [4]

함수 정의는 하나 이상의 데코레이터 표현식으로 감싸질 수 있어요. 데코레이터 표현식은 함수가 정의될 때, 함수 정의를 포함하는 스코프에서 평가돼요. 결과는 callable이어야 하며, 함수 객체를 유일한 인자로 호출돼요. 반환된 값은 함수 객체 대신 함수 이름에 바인딩돼요. 여러 데코레이터는 중첩된 방식으로 적용돼요. 예를 들어 다음 코드

@f1(arg)
@f2
def func(): pass

는 대략 다음과 동등해요

def func(): pass
func = f1(arg)(f2(func))

단, 원래 함수가 func 이름에 일시적으로 바인딩되지 않는다는 점이 달라요.

버전 3.9에서 변경: 함수는 어떤 유효한 assignment_expression으로도 데코레이트될 수 있어요. 이전에는 문법이 훨씬 더 제한적이었어요. 자세한 내용은 PEP 614 참조.

함수의 이름과 매개변수 목록의 여는 괄호 사이의 대괄호에 타입 매개변수 목록이 주어질 수 있어요. 이것은 정적 타입 검사기에 그 함수가 제네릭임을 나타내요. 런타임에는 타입 매개변수를 함수의 __type_params__ 속성에서 검색할 수 있어요. 자세한 내용은 Generic functions 참조.

버전 3.12에서 변경: 타입 매개변수 목록은 Python 3.12의 새로운 기능이에요.

하나 이상의 매개변수가 parameter = expression 형태이면, 그 함수는 "기본 매개변수 값(default parameter values)"을 가진다고 해요. 기본 값을 가진 매개변수에 대해서는 호출에서 해당 인자를 생략할 수 있고, 그 경우 매개변수의 기본 값이 대체돼요. 매개변수가 기본 값을 가지면, "*"까지의 모든 뒤따르는 매개변수도 기본 값을 가져야 해요 — 이것은 문법으로 표현되지 않는 구문상 제한이에요.

기본 매개변수 값은 함수 정의가 실행될 때 왼쪽에서 오른쪽으로 평가돼요. 이는 그 표현식이 함수가 정의될 때 한 번 평가되고, 각 호출에 대해 같은 "미리 계산된" 값이 사용된다는 뜻이에요. 이것은 기본 매개변수 값이 목록이나 사전 같은 가변 객체일 때 이해하는 것이 특히 중요해요: 함수가 그 객체를 수정하면(예: 목록에 항목을 추가), 기본 매개변수 값이 사실상 수정돼요. 이것은 일반적으로 의도된 것이 아니에요. 우회 방법은 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" 뒤의 매개변수는 키워드 전용 매개변수이고 키워드 인자로만 전달될 수 있어요. "/" 앞의 매개변수는 위치 전용 매개변수이고 위치 인자로만 전달될 수 있어요.

버전 3.8에서 변경: / 함수 매개변수 문법이 위치 전용 매개변수를 나타내는 데 사용될 수 있어요. 자세한 내용은 PEP 570 참조.

매개변수는 매개변수 이름 뒤에 ": expression" 형태의 어노테이션을 가질 수 있어요. 어떤 매개변수든 어노테이션을 가질 수 있고, *identifier 또는 **identifier 형태조차도요. (특수한 경우로, *identifier 형태의 매개변수는 ": *expression" 어노테이션을 가질 수 있어요.) 함수는 매개변수 목록 뒤에 "-> expression" 형태의 "return" 어노테이션을 가질 수 있어요. 이 어노테이션들은 어떤 유효한 Python 표현식이든 될 수 있어요. 어노테이션의 존재는 함수의 의미론을 바꾸지 않아요. 어노테이션에 대한 자세한 내용은 Annotations을 참고하세요.

버전 3.11에서 변경: "*identifier" 형태의 매개변수가 ": *expression" 어노테이션을 가질 수 있어요. PEP 646 참조.

표현식에서 즉시 사용하기 위해 (이름에 바인딩되지 않은) 익명 함수를 만드는 것도 가능해요. 이것은 Lambdas 섹션에서 설명하는 lambda 표현식을 사용해요. lambda 표현식은 단순화된 함수 정의의 축약일 뿐이라는 점에 유의하세요. "def" 문장에 정의된 함수는 lambda 표현식으로 정의된 함수처럼 전달되거나 다른 이름에 할당될 수 있어요. "def" 형태가 실제로 더 강력한데, 여러 문장과 어노테이션의 실행을 허용하기 때문이에요.

프로그래머 참고: 함수는 일급(first-class) 객체예요. 함수 정의 안에서 실행되는 "def" 문장은 반환되거나 전달될 수 있는 지역 함수를 정의해요. 중첩 함수에서 사용되는 자유 변수는 def를 포함하는 함수의 지역 변수에 접근할 수 있어요. 자세한 내용은 Naming and binding 섹션 참조.

참고 자료

PEP 3107 - Function Annotations: 함수 어노테이션의 원래 명세.

PEP 484 - Type Hints: 어노테이션의 표준 의미인 타입 힌트의 정의.

PEP 526 - Syntax for Variable Annotations: 클래스 변수와 인스턴스 변수를 포함한 변수 선언의 타입 힌트 능력.

PEP 563 - Postponed Evaluation of Annotations: 즉시 평가 대신 런타임에 어노테이션을 문자열 형태로 보존함으로써 어노테이션 안의 전방 참조에 대한 지원.

PEP 318 - Decorators for Functions and Methods: 함수와 메서드 데코레이터가 도입되었어요. 클래스 데코레이터는 PEP 3129에서 도입되었어요.

8.8. 클래스 정의 (Class definitions)

클래스 정의는 클래스 객체를 정의해요 (The standard type hierarchy 섹션 참조):

classdef: [decorators] "class" classname [type_params] [inheritance] ":" suite inheritance: "(" [argument_list] ")" classname: identifier

클래스 정의는 실행 가능한 문장이에요. 상속 목록은 보통 기본 클래스들의 목록을 제공해요 (더 고급 사용법은 Metaclasses 참조). 그래서 목록의 각 항목은 하위 클래싱을 허용하는 클래스 객체로 평가되어야 해요. 상속 목록이 없는 클래스는 기본적으로 기본 클래스 object에서 상속해요. 따라서

class Foo:
    pass

는 다음과 동등해요

class Foo(object):
    pass

그런 다음 클래스의 수트가 새 실행 프레임에서(Naming and binding 참조), 새로 생성된 지역 네임스페이스와 원래 전역 네임스페이스를 사용해 실행돼요. (보통 수트는 대부분 함수 정의를 포함해요.) 클래스의 수트가 실행을 끝내면, 그것의 실행 프레임은 폐기되지만 지역 네임스페이스는 저장돼요. [5] 그런 다음 기본 클래스용 상속 목록과 속성 사전용 저장된 지역 네임스페이스를 사용해 클래스 객체가 생성돼요. 클래스 이름은 원래 지역 네임스페이스에서 이 클래스 객체에 바인딩돼요.

클래스 본문에서 속성이 정의되는 순서는 새 클래스의 __dict__에 보존돼요. 이것은 클래스가 생성된 직후에만, 그리고 정의 문법으로 정의된 클래스에 대해서만 신뢰할 수 있어요.

클래스 생성을 메타클래스를 사용해 크게 사용자 지정할 수 있어요.

클래스도 데코레이트될 수 있어요. 함수를 데코레이트할 때처럼,

@f1(arg)
@f2
class Foo: pass

는 대략 다음과 동등해요

class Foo: pass
Foo = f1(arg)(f2(Foo))

데코레이터 표현식의 평가 규칙은 함수 데코레이터와 동일해요. 그 결과는 클래스 이름에 바인딩돼요.

버전 3.9에서 변경: 클래스는 어떤 유효한 assignment_expression으로도 데코레이트될 수 있어요. 이전에는 문법이 훨씬 더 제한적이었어요. 자세한 내용은 PEP 614 참조.

클래스 이름 바로 뒤의 대괄호에 타입 매개변수 목록이 주어질 수 있어요. 이것은 정적 타입 검사기에 그 클래스가 제네릭임을 나타내요. 런타임에는 타입 매개변수를 클래스의 __type_params__ 속성에서 검색할 수 있어요. 자세한 내용은 Generic classes 참조.

버전 3.12에서 변경: 타입 매개변수 목록은 Python 3.12의 새로운 기능이에요.

프로그래머 참고: 클래스 정의에서 정의된 변수는 클래스 속성이에요. 인스턴스가 공유해요. 인스턴스 속성은 메서드에서 self.name = value로 설정할 수 있어요. 클래스와 인스턴스 속성 모두 "self.name" 표기법으로 접근할 수 있고, 이렇게 접근할 때 인스턴스 속성은 같은 이름의 클래스 속성을 숨겨요. 클래스 속성은 인스턴스 속성의 기본값으로 사용될 수 있지만, 거기에 가변 값을 사용하면 예상치 못한 결과를 초래할 수 있어요. 디스크립터를 사용해 다른 구현 세부 사항을 가진 인스턴스 변수를 만들 수 있어요.

참고 자료

PEP 3115 - Metaclasses in Python 3000: 메타클래스의 선언을 현재 문법으로 바꾸고, 메타클래스가 있는 클래스가 어떻게 구성되는지에 대한 의미론을 바꾼 제안.

PEP 3129 - Class Decorators: 클래스 데코레이터를 추가한 제안. 함수와 메서드 데코레이터는 PEP 318에서 도입되었어요.

8.9. 코루틴 (Coroutines)

버전 3.5에서 추가.

8.9.1. 코루틴 함수 정의 (Coroutine function definition)

async_funcdef: [decorators] "async" "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite

Python 코루틴의 실행은 여러 지점에서 일시 중단되고 재개될 수 있어요 (coroutine 참조). await 표현식, async for, async with는 코루틴 함수의 본문에서만 사용될 수 있어요.

async def 문법으로 정의된 함수는 await 또는 async 키워드를 포함하지 않아도 항상 코루틴 함수예요.

코루틴 함수 본문 안에 yield from 표현식을 사용하는 것은 SyntaxError예요.

코루틴 함수의 예시:

async def func(param1, param2):
    do_stuff()
    await some_coroutine()

버전 3.7에서 변경: awaitasync가 이제 키워드예요. 이전에는 코루틴 함수 본문 안에서만 그렇게 취급되었어요.

8.9.2. async for 문장 (The async for statement)

async_for_stmt: "async" for_stmt

비동기 iterable은 __aiter__ 메서드를 제공하며, 그것은 __anext__ 메서드에서 비동기 코드를 호출할 수 있는 비동기 iterator를 직접 반환해요.

async for 문장은 비동기 iterable에 대한 편리한 반복을 허용해요.

다음 코드:

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 문장 (The async with statement)

async_with_stmt: "async" with_stmt

비동기 컨텍스트 매니저는 enterexit 메서드에서 실행을 일시 중단할 수 있는 컨텍스트 매니저예요.

다음 코드:

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 - Coroutines with async and await syntax: 코루틴을 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 선언은 콜론(:)과 표현식으로 *경계(bounds)*와 *제약(constraints)*을 정의할 수 있어요. 콜론 뒤의 단일 표현식은 경계를 나타내요 (예: T: int). 의미상, 이것은 typing.TypeVar가 이 경계의 하위 타입인 타입만 나타낼 수 있다는 뜻이에요. 콜론 뒤의 괄호로 묶인 표현식들의 튜플은 제약 집합을 나타내요 (예: T: (str, bytes)). 튜플의 각 구성원은 타입이어야 해요 (이것도 런타임에서 강제되지 않아요). 제약된 타입 변수는 제약 목록의 타입 중 하나만 취할 수 있어요.

타입 매개변수 목록 문법으로 선언된 typing.TypeVar의 경우, 경계와 제약은 제네릭 객체가 생성될 때가 아니라 값이 __bound____constraints__ 속성을 통해 명시적으로 접근될 때만 평가돼요. 이를 위해 경계나 제약이 별도의 어노테이션 스코프에서 평가돼요.

typing.TypeVarTupletyping.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): ...

함수는 화살표 뒤에 오는 return 어노테이션도 전달할 수 있어요:

def f() -> annotation: ...

어노테이션은 관례적으로 타입 힌트에 사용되지만, 이것은 언어가 강제하지 않고, 일반적으로 어노테이션은 임의의 표현식을 포함할 수 있어요. 어노테이션의 존재는 어노테이션을 검사하고 사용하는 어떤 메커니즘(예: dataclasses 또는 @functools.singledispatch)이 사용되지 않는 한 코드의 런타임 의미론을 바꾸지 않아요.

기본적으로 어노테이션은 어노테이션 스코프에서 지연 평가돼요. 이는 어노테이션을 포함하는 코드가 평가될 때 어노테이션이 평가되지 않는다는 뜻이에요. 대신 인터프리터는 요청되면 나중에 어노테이션을 평가하는 데 사용할 수 있는 정보를 저장해요. annotationlib 모듈은 어노테이션을 평가하기 위한 도구를 제공해요.

future 문장 from __future__ import annotations가 있으면, 모든 어노테이션은 대신 문자열로 저장돼요:

>>> from __future__ import annotations
>>> def f(param: annotation): ...
>>> f.__annotations__
{'param': 'annotation'}

이 future 문장은 미래의 Python 버전에서 비추천되고 제거될 거예요. Python 3.13이 수명 종료에 도달하기 전에는 아니지만요 (PEP 749 참조). 그것이 사용되면 annotationlib.get_annotations()typing.get_type_hints() 같은 검사 도구가 런타임에 어노테이션을 해석할 가능성이 더 낮아져요.

각주

[1] 예외는, 우연히 다른 예외를 발생시키는 finally 절이 없는 한, 호출 스택으로 전파돼요. 그 새 예외는 이전 것을 잃게 해요.

[2] 패턴 매칭에서 시퀀스는 다음 중 하나로 정의돼요:

  • collections.abc.Sequence에서 상속하는 클래스
  • collections.abc.Sequence로 등록된 Python 클래스
  • (CPython) Py_TPFLAGS_SEQUENCE 비트가 설정된 내장 클래스
  • 위의 어느 것에서든 상속하는 클래스
  • 다음 표준 라이브러리 클래스들은 시퀀스예요:
    • array.array
    • collections.deque
    • list
    • memoryview
    • range
    • tuple
  • 참고: str, bytes, bytearray 타입의 subject 값은 시퀀스 패턴과 일치하지 않아요.

[3] 패턴 매칭에서 매핑은 다음 중 하나로 정의돼요:

  • collections.abc.Mapping에서 상속하는 클래스
  • collections.abc.Mapping으로 등록된 Python 클래스
  • (CPython) Py_TPFLAGS_MAPPING 비트가 설정된 내장 클래스
  • 위의 어느 것에서든 상속하는 클래스
  • 표준 라이브러리 클래스 dicttypes.MappingProxyType은 매핑이에요.

[4] 함수 본문의 첫 번째 문장으로 나타나는 문자열 리터럴은 함수의 __doc__ 속성, 즉 함수의 docstring으로 변환돼요.

[5] 클래스 본문의 첫 번째 문장으로 나타나는 문자열 리터럴은 네임스페이스의 __doc__ 항목, 즉 클래스의 docstring으로 변환돼요.

더 알아보기 (Learn more)