Python

8. 복합문(Compound statements)

복합문은 다른 문장들(의 그룹들)을 포함합니다; 어떤 방법으로 그 다른 문장들의 실행에 영향을 주거나 제어합니다. 간단하게 표현할 때, 전체 복합문을 한 줄로 쓸 수 있기는 하지만, 일반적으로 복합문은 여러 줄에 걸칩니다.

if, while, for 문장은 전통적인 제어 흐름 구조를 구현합니다. 문장들의 그룹에 대해 try 는 예외 처리기나 정리(cleanup) 코드 또는 그 둘 모두를 지정하는 반면, with 문은 코드 블록 주변으로 초기화와 파이널리제이션 코드를 실행할 수 있도록 합니다. 함수와 클래스 정의 또한 문법적으로 복합문입니다.

복합문은 하나나 그 이상의 ‘절’로 구성됩니다. 절은 헤더와 ‘스위트(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 가 그 뒤를 따를 수 있음에 주목해야 합니다. 또한, 생략 가능한 연결 절들이 항상 문장을 시작시킬 수 없는 키워드로 시작하기 때문에, 모호함이 없다는 것도 주목하세요 (파이썬에서는 중첩된 if 문이 들여쓰기 되는 것을 요구함으로써 ‘매달린(dangling) else’ 문제를 해결합니다).

명확함을 위해 다음에 오는 절들에서 나오는 문법 규칙들은 각 절을 별도의 줄에 놓도록 포매팅합니다.

8.1. if

if 문은 조건부 실행에 사용됩니다:

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

참이 되는 것을 발견할 때까지 표현식들의 값을 하나씩 차례대로 구해서 정확히 하나의 스위트를 선택합니다 (참과 거짓의 정의는 논리 연산(Boolean operations) 섹션을 보세요); 그런 다음 그 스위트를 실행합니다 (그리고는 if 문의 다른 어떤 부분도 실행되거나 값이 구해지지 않습니다). 모든 표현식들이 거짓이면 else 절의 스위트가 (있다면) 실행됩니다.

8.2. while

while 문은 표현식이 참인 동안 실행을 반복하는 데 사용됩니다:

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

이것은 표현식을 반복적으로 검사하고, 참이면, 첫 번째 스위트를 실행합니다; 표현식이 거짓이면 (처음부터 거짓일 수도 있습니다) else 절의 스위트가 (있다면) 실행되고 루프를 종료합니다.

첫 번째 스위트에서 실행되는 break 문은 else 절을 실행하지 않고 루프를 종료합니다. 첫 번째 스위트에서 실행되는 continue 문은 스위트의 나머지 부분을 건너뛰고 표현식의 검사로 돌아갑니다.

8.3. for

for 문은 (문자열, 튜플, 리스트 같은) 시퀀스 나 다른 이터러블 객체의 요소들을 이터레이트하는데 사용됩니다:

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

The starred_expression_list expression is evaluated once; it should yield an iterable object. An iterator is created for that iterable. The first item provided by the iterator is then assigned to the target list using the standard rules for assignments (see 대입문), and the suite is executed. This repeats for each item provided by the iterator. When the iterator is exhausted, the suite in the else clause, if present, is executed, and the loop terminates.

첫 번째 스위트에서 실행되는 break 문은 else 절을 실행하지 않고 루프를 종료합니다. 첫 번째 스위트에서 실행되는 continue 문은 스위트의 나머지 부분을 건너뛰고 다음 항목으로 넘어가거나, 다음 항목이 없으면 else 절로 갑니다.

for-루프는 타깃 목록의 변수들에 대입합니다. for-루프의 스위트에서 이루어진 것들도 포함해서, 그 변수에 앞서 대입된 값들을 모두 덮어씁니다:

for i in range(10):
    print(i)
    i = 5             # 이 코드는 for 루프에 영향을 주지 않습니다.
                      # 범위 내의 다음 인덱스로 i가 덮어씌워지기 때문입니다.

루프가 종료될 때 대상 리스트의 이름이 삭제되지는 않으나, 시퀀스가 비어 있는 경우 해당 이름들이 루프에 의해 할당되지 않습니다. 도움말: 내장형 range() 타입은 불변 정수 산술 시퀀스를 나타냅니다. 예를 들어, range(3) 를 반복하면 0, 1, 2가 차례대로 반환됩니다.

버전 3.11에서 변경: 표현식 리스트에서 별표(*) 요소 사용이 허용됩니다.

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 절을 사용하지 않는 경우 괄호를 생략할 수 있습니다. 발생한 예외는 해당 예외 객체의 클래스 또는 비 가상 기본 클래스 를 평가하는 결과를 가진 except 절과 일치하거나, 그러한 클래스를 포함하는 튜플과 일치합니다.

except 절이 예외와 일치하지 않는 경우, 예외 처리기 검색은 주변 코드와 호출 스택에서 계속됩니다. [1]

except 절의 헤더에 있는 표현식을 평가하는 도중 예외가 발생하면, 원래의 처리기 검색이 취소되고 주변 코드와 호출 스택에서 새 예외에 대한 검색이 시작됩니다(이 경우 전체 try 문에서 예외가 발생한 것으로 간주됩니다).

일치하는 except 절이 발견되면, 해당 예외는 그들 중 존재하는 경우 as 키워드 뒤에 지정된 대상에 할당되고, 해당 except 절의 블록이 실행됩니다. 모든 except 절은 실행 가능한 블록을 가져야 합니다. 이 블록이 끝나면 전체 try 문 이후부터 정상적으로 실행이 계속됩니다. (이는 동일한 예외에 대해 중첩된 두 개의 처리기가 존재하는 경우, 내부 처리기의 try 절에서 예외가 발생하면 외부 처리기는 해당 예외를 처리하지 않음을 의미합니다.)

as target 을 사용하여 예외를 할당한 경우, 해당 예외는 except 절이 끝날 때 초기화됩니다. 이는 마치 다음과 같은 상황과 같습니다.

except E as N:
    foo

가 이렇게 변환되는 것과 같습니다

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

이는 except 절 이후에도 해당 예외를 참조할 수 있도록 예외에 다른 이름을 할당해야 함을 의미합니다. 트레이스백이 연결된 경우 예외는 스택 프레임과 참조 사이클을 형성하여 다음 가비지 컬렉션이 발생할 때까지 해당 프레임의 모든 로컬 변수를 유지하게 되므로, 예외를 클리어해야 합니다.

예:keyword:!except 절의 스위트가 실행되기 전에 예외는 sys 모듈에 저장되며, 이 예외는 sys.exception`을 호출하여 :keyword:()!except` 절 내부에서 접근할 수 있습니다. 예외 처리기를 벗어날 때 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*

예:keyword:!except* 절은 한 그룹 이상의 예외(BaseExceptionGroup 인스턴스)에 대한 하나 이상의 처리기를 지정합니다. try 문은 except 또는 except* 절 중 하나만 가질 수 있으며, 둘 다 가질 수는 없습니다. except* 의 경우 일치 여부를 확인하기 위한 예외 유형이 필수적이므로 except*: 는 구문 오류입니다. 유형은 except 와 동일하게 해석되지만, 일치 여부는 처리되는 그룹에 포함된 예외들에 대해 수행됩니다. 일치하는 유형이 BaseExceptionGroup 의 하위 클래스인 경우 의미가 모호해질 수 있으므로 TypeError 가 발생합니다.

try 블록에서 예외 그룹이 발생하면, 각 except* 절은 이를 일치하는 예외와 일치하지 않는 예분의 하위 그룹으로 분할합니다(참고: split()). 일치하는 하위 그룹이 비어 있지 않으면 해당 그룹이 처리되는 예외가 되고(sys.exception`에서 반환된 값), 대상이 있는 경우 :keyword:()!except*` 절의 대상에 할당됩니다. 그 후, except* 절의 본문이 실행됩니다. 일치하지 않는 하위 그룹이 비어 있지 않으면 동일한 방식으로 다음 except*`에서 처리됩니다. 과정은 그룹 내의 모든 예외가 매칭되거나 마지막 :keyword:!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
    +------------------------------------

If the exception raised from the try block is not an exception group and its type matches one of the except* clauses, it is caught and wrapped by an exception group with an empty message string. This ensures that the type of the target e is consistently BaseExceptionGroup:

>>> try:
...     raise BlockingIOError
... except* BlockingIOError as e:
...     print(repr(e))
...
ExceptionGroup('', (BlockingIOError(),))

break, continuereturnexcept* 절에 나타날 수 없습니다.

8.4.3. else

생략 가능한 else 절은 제어 흐름이 try 스위트를 빠져나가고, 예외가 발생하지 않았고, return, continue 또는 break 문이 실행되지 않으면 실행됩니다. else 절에서 발생하는 예외는 앞에 나오는 except 절에서 처리되지 않습니다.

8.4.4. finally

finally 가 존재하는 경우, 이는 ‘정리(cleanup)’ 처리기를 지정합니다. try 절은 모든 exceptelse 를 포함하여 실행됩니다. 여러 절 중 하나에서 예외가 발생하고 처리되지 않으면, 해당 예외는 임시로 저장됩니다. 그 후 finally 절이 실행됩니다. 만약 저장된 예시가 있다면 finally 절 끝에서 다시 발생합니다. 만약 finally 절이 다른 예외를 발생시키면, 저장된 예외가 새 예외의 컨텍스트로 설정됩니다. 만약 finally 절이 return, break 또는 continue 문을 실행하면, 저장된 예외는 버려집니다. 예를 들어, 이 함수는 42를 반환합니다.

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

예외 정보는 finally 절이 실행되는 동안 프로그램에서 사용할 수 없습니다.

try…:keyword:!finally 문 내의 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

with 문은 블록의 실행을 컨텍스트 관리자 (with 문 컨텍스트 관리자 섹션을 보세요) 가 정의한 메서드들로 감싸는 데 사용됩니다. 이것은 흔한 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 문의 실행은 다음과 같이 진행됩니다:

  1. 컨텍스트 표현식(with_item 에 주어진 표현식)이 평가되어 컨텍스트 관리자를 얻습니다.

  2. 컨텍스트 관리자의 __enter__() 가 나중에 사용하기 위해 로드됩니다.

  3. 컨텍스트 관리자의 __exit__() 가 나중에 사용하기 위해 로드됩니다.

  4. 컨텍스트 관리자의 __enter__() 메서드가 호출됩니다.

  5. with 문에 대상이 포함된 경우, __enter__() 의 반환 값이 해당 대상에 할당됩니다.

    참고

    with 문은 __enter__() 메서드가 오류 없이 반환되면 __exit__() 가 항상 호출됨을 보장합니다. 따라서, 대상 리스트에 할당하는 동안 오류가 발생하면 스위트 내에서 오류가 발생하는 것과 동일하게 취급됩니다. 아래의 7단계를 참조하십시오.

  6. 스위트가 실행됩니다.

  7. 컨텍스트 관리자의 __exit__() 메서드가 호출됩니다. 예외로 인해 스위트가 종료된 경우, 해당 예외의 유형, 값 및 트레이스백이 __exit__() 에 인자로 전달됩니다. 그렇지 않은 경우에는 세 개의 None 인자가 제공됩니다.

    예외로 인해 스위트가 종료되었고 __exit__() 메서드의 반환 값이 False인 경우, 예외가 다시 발생합니다. 만약 반환 값이 True라면 예외가 억제되고 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__() 을 위해 암시적인 special method lookup 가 사용됩니다.

하나 보다 많은 항목을 주면, 컨텍스트 관리자는 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 - “with” 문

파이썬 with 문의 규격, 배경, 예.

8.6. match

Added in version 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

참고

이 섹션에서는 soft keywords 를 나타내기 위해 작은따옴표를 사용합니다.

패턴 매칭은 입력으로 패턴(case 뒤)과 대상 값(match 뒤)을 받습니다. 패턴(하위 패턴 포함 가능)이 대상 값과 일치하는지 확인합니다. 결과는 다음과 같습니다:

  • 매칭 성공 또는 실패(패턴 성공 또는 실패라고도 함).

  • 일치하는 값을 이름에 바인딩할 수 있는 가능성. 이에 대한 전제 조건은 아래에서 더 자세히 논의합니다.

matchcase 키워드는 soft keywords 입니다.

더 보기

  • PEP 634 – 구조적 패턴 매칭: 사양

  • PEP 636 – Structural Pattern Matching: Tutorial

8.6.1. 개요

match 문의 논리적 흐름에 대한 개요는 다음과 같습니다:

  1. 주체 표현식 subject_expr 이 평가되고 결과로 나온 주체 값이 확보됩니다. 주체 표현식에 쉼표가 포함된 경우, the standard rules 를 사용하여 튜플이 생성됩니다.

  2. case_block 의 각 패턴은 주체 값과 일치하는지 시도됩니다. 성공 또는 실패에 대한 구체적인 규칙은 아래에 기술되어 있습니다. 매칭 시도 과정에서 패턴 내에 있는 독립된 이름 중 일부 또는 전부가 바인딩될 수도 있습니다. 정확한 패턴 바인딩 규칙은 패턴 유형에 따라 다르며 아래에 명시되어 있습니다. 성공적인 패턴 매치 중에 수행된 이름 바인딩은 실행된 블록이 끝난 후에도 유지되며 match 문 이후에도 사용할 수 있습니다.

    참고

    실패한 패턴 매칭 시, 일부 하위 패턴이 성공할 수 있습니다. 실패한 매치에서 발생한 바인딩에 의존하지 마십시오. 반대로, 실패한 매치 후에 변수가 변경되지 않은 상태로 남아 있을 것이라고도 신뢰해서는 안 됩니다. 정확한 동작은 구현 방식에 따라 다르며 다를 수 있습니다. 이는 다양한 구현이 최적화를 추가할 수 있도록 하기 위한 의도적인 결정입니다.

  3. 패턴이 성공하면 해당하는 가드(있는 경우)가 평가됩니다. 이 경우 모든 이름 바인딩이 수행되었음이 보장됩니다.

    • 가드가 true로 평가되거나 생략된 경우, case_block 내부의 block 이 실행됩니다.

    • 그렇지 않으면 위의 설명과 같이 다음 case_block 을 시도합니다.

    • 더 이상 케이스 블록이 없으면 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 내의 guard``(case의 일부)는 성공해야 ``case 블록 내부의 코드가 실행됩니다. 이는 if 뒤에 표현식이 오는 형태를 취합니다.

가드가 포함된 case 블록의 논리적 흐름은 다음과 같습니다:

  1. case 블록 내의 패턴이 성공했는지 확인합니다. 패턴이 실패하면 guard``는 평가되지 않고 다음 ``case 블록을 확인합니다.

  2. 패턴이 성공한 경우, guard 를 평가합니다.

    • guard 조건이 true로 평가되면 해당 케이스 블록이 선택됩니다.

    • guard 조건이 false로 평가되면 해당 케이스 블록은 선택되지 않습니다.

    • guard 가 평가되는 동안 예외가 발생하면, 해당 예외는 위로 전파됩니다.

가드는 표현식이므로 부수 효과(side effects)를 가질 수 있습니다. 가드 평가는 첫 번째부터 마지막 케이스 블록까지 하나씩 진행되며, 패턴이 모두 성공하지 않는 케이스 블록은 건너뜁니다. (즉, 가드 평가는 순서대로 발생해야 합니다.) 가드 평가는 케이스 블록이 선택되면 중단됩니다.

8.6.3. 확정할 수 없는 케이스 블록(Irrefutable Case Blocks)

irrefutable case block은 모든 경우에 매치되는 케이스 블록입니다. match 문은 최대 하나의 irrefutable case 블록을 가질 수 있으며, 이는 반드시 마지막에 위치해야 합니다.

가드가 없고 패턴이 irrefutable한 경우 해당 케이스 블록은 irrefutable한 것으로 간주됩니다. 패턴이 syntax만으로도 항상 성공할 것이라고 증명될 수 있는 경우 irrefutable하다고 간주합니다. 오직 다음의 패턴들만이 irrefutable합니다:

8.6.4. 패턴(Patterns)

참고

이 섹션에서는 표준 EBNF를 넘어서는 문법 표기법을 사용합니다:

  • SEP.RULE+ 표기법은 RULE (SEP RULE)* 의 약어입니다.

  • !RULE 표기법은 부정형 미리 보기(negative 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 패턴은 수직 바 | 로 구분된 두 개 이상의 패턴입니다. 구문:

or_pattern: "|".closed_pattern+

마지막 하위 패턴만 irrefutable 일 수 있으며, 모호성을 피하기 위해 각 하위 패턴은 동일한 이름 세트를 바인딩해야 합니다.

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 패턴은 대상(subject)을 as 키워드 오른쪽에 있는 이름에 바인딩하고 성공합니다. capture_pattern_ 가 될 수 없습니다.

[msgid] In simple terms P as NAME will match with P, and on success it will set NAME = <subject>.

8.6.4.3. 리터럴 패턴

리터럴 패턴은 파이썬의 대부분의 literals 와 일치합니다. 구문:

literal_pattern: signed_number
                 | signed_number "+" NUMBER
                 | signed_number "-" NUMBER
                 | strings
                 | "None"
                 | "True"
                 | "False"
signed_number:   ["+" | "-"] NUMBER

strings 규칙과 NUMBER 토큰은 standard Python grammar 에 정의되어 있습니다. 삼중 따옴표 문자열이 지원됩니다. Raw 문자열과 바이트 문자열도 지원됩니다. 포맷 문자열 리터럴t-문자열 는 지원되지 않습니다.

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

간단히 말해, LITERAL<subject> == LITERAL 인 경우에만 성공합니다. 싱글톤 값인 None, True, False 의 경우 is 연산자가 사용됩니다.

8.6.4.4. 캡처 패턴

캡처 패턴은 대상 값을 이름에 바인딩합니다. 구문:

capture_pattern: !'_' NAME

단일 언더스코어 _ 는 캡처 패턴이 아닙니다(이는 !'_' 가 의미하는 바입니다). 대신 이는 wildcard_pattern 으로 처리됩니다.

주어진 패턴 내에서 특정 이름은 한 번만 바인딩될 수 있습니다. 예: case x, x: ... 는 유효하지 않지만, case [x] | x: ... 는 허용됩니다.

캡처 패턴은 항상 성공합니다. 바인딩은 PEP 572 에서 정의된 할당 표현 연산자의 스코프 규칙을 따릅니다; 적절한 global 또는 nonlocal 문이 없는 한, 이름은 가장 가까운 상위 함수 스코프의 지역 변수가 됩니다.

[msgid] In simple terms NAME will always succeed and it will set NAME = <subject>.

8.6.4.5. 와일드카드 패턴

와일드카드 패턴은 항상 성공하며(모든 항목과 일치), 아무 이름도 바인딩하지 않습니다. 구문:

wildcard_pattern: '_'

_ 는 모든 패턴 내에서 soft keyword 이나, 패턴 내부에서만 해당됩니다. 이는 match 대상 표현식, guard s 및 case 블록 내에서도 통상적인 식별자로 취급됩니다.

간단히 말해, _ 는 항상 성공합니다.

8.6.4.6. 값 패턴

값 패턴은 파이썬의 명명된 값을 나타냅니다. 구문:

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

패턴의 점(.)으로 구분된 이름은 표준 파이썬 name resolution rules 를 사용하여 조회됩니다. 발견된 값이 대상 값과 동일할 때(== 비교 연산자 사용) 패턴은 성공합니다.

간단히 말해 NAME1.NAME2<subject> == NAME1.NAME2 인 경우에만 성공합니다.

참고

동일한 매치 문에서 동일한 값이 여러 번 나타나는 경우, 인터프리터는 해당 조회를 반복하는 대신 처음 발견된 값을 캐싱하여 재사용할 수 있습니다. 이 캐시는 특정 매치 문의 실행 시점에만 엄격하게 적용됩니다.

8.6.4.7. 그룹 패턴

그룹 패턴은 사용자가 의도한 그룹화를 강조하기 위해 패턴 주위에 괄호를 추가할 수 있도록 합니다. 그 외의 경우에는 특별한 문법이 없습니다. 구문:

group_pattern: "(" pattern ")"

간단히 말해 (P)P 와 동일한 효과를 가집니다.

8.6.4.8. 시퀀스 패턴

시퀀스 패턴은 시퀀스 요소와 매칭될 여러 하위 패턴을 포함합니다. 구문은 리스트나 튜플의 언패킹과 유사합니다.

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)

시퀀스 패턴에 괄호나 대괄호를 사용하는 것은 차이가 없습니다(즉, (...)[...] 는 동일합니다).

참고

뒤에 쉼표가 없는 괄호로 둘러싸인 단일 패턴(예: (3 | 4))은 group pattern 입니다. 대괄호로 둘러싸인 단일 패턴(예: [3 | 4])은 여전히 시퀀스 패턴입니다.

시퀀스 패턴 내에는 최대 하나의 별표(*) 하위 패턴만 포함될 수 있습니다. 별표 하위 패턴은 어떤 위치에나 올 수 있습니다. 별표 하위 패턴이 없으면 시퀀스 패턴은 고정 길이(fixed-length) 시퀀스 패턴이며, 그렇지 않으면 가변 길이(variable-length) 시퀀스 패턴입니다.

시퀀스 패턴을 대상 값에 대해 매칭할 때의 논리적 흐름은 다음과 같습니다:

  1. 대상 값이 시퀀스가 아니면 [2], 시퀀스 패턴은 실패합니다.

  2. 대상 값이 str, bytes 또는 bytearray 인스턴스인 경우 시퀀스 패턴은 실패합니다.

  3. 이후 단계는 시퀀스 패턴이 고정 길이인지 가변 길이인지에 따라 달라집니다.

    시퀀스 패턴이 고정 길이인 경우:

    1. 대상 시퀀스의 길이가 하위 패턴의 수와 일치하지 않으면, 시퀀스 패턴은 실패합니다.

    2. 시퀀스 패턴의 하위 패턴은 대상 시퀀스의 해당 항목과 왼쪽에서 오른쪽으로 매칭됩니다. 하위 패턴이 실패하는 즉시 매칭이 중단됩니다. 모든 하위 패턴이 해당 항목과 일치하면 시퀀스 패턴이 성공합니다.

    그렇지 않고, 시퀀스 패턴이 가변 길이인 경우:

    1. 대상 시퀀스의 길이가 별표가 없는 하위 패턴의 수보다 적으면 시퀀스 패턴은 실패합니다.

    2. 앞부분의 별표 없는 하위 패턴들은 고정 길이 시퀀스와 마찬가지로 해당 항목과 매칭됩니다.

    3. 이전 단계가 성공하면, 별표 하위 패턴은 별표 뒤에 오는 별표 없는 하위 패턴에 대응하는 나머지 항목들을 제외한 대상의 남은 항목들로 구성된 리스트와 일치합니다.

    4. 남은 별표 없는 하위 패턴들은 고정 길이 시퀀스와 마찬가지로 해당 대상 항목과 매칭됩니다.

    참고

    대상 시퀀스의 길이는 len() 을 통해 가져옵니다(즉, __len__() 프로토콜을 통해 가져옴). 이 길이는 value patterns 와 유사한 방식으로 인터프리터에 의해 캐싱될 수 있습니다.

간단히 말해 [P1, P2, P3,, P<N>] 은 다음 사항이 모두 충족될 때만 일치합니다:

  • <subject> 가 시퀀스인지 확인

  • len(subject) == <N>

  • P1<subject>[0] 과 일치하는지 확인(이 매칭은 이름을 바인딩할 수도 있음)

  • P2<subject>[1] 과 일치하는지 확인(이 매칭은 이름을 바인딩할 수도 있음)

  • … 그리고 해당 패턴/요소에 대해 이와 같은 방식으로 진행합니다.

8.6.4.9. 매핑 패턴

매핑 패턴은 하나 이상의 키-값 패턴을 포함합니다. 구문은 딕셔너리 생성과 유사합니다. 구문:

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) 패턴만 포함될 수 있습니다. 더블 스타 패턴은 매핑 패턴의 마지막 하위 패턴이어야 합니다.

매핑 패턴에서 중복된 키는 허용되지 않습니다. 중복된 리터럴 키는 SyntaxError 를 발생시키며, 그 외에 값이 같은 두 개의 키는 실행 시(runtime)에 ValueError 를 발생시킵니다.

매핑 패턴을 대상 값에 대해 매칭할 때의 논리적 흐름은 다음과 같습니다:

  1. 대상 값이 매핑이 아니면 [3], 매핑 패턴은 실패합니다.

  2. 매핑 패턴에 제시된 모든 키가 대상 매핑에 존재하고, 각 키에 대한 패턴이 대상 매핑의 해당 항목과 일치하면 매핑 패턴은 성공합니다.

  3. 매핑 패턴에서 중복된 키가 감지되면 해당 패턴은 무효한 것으로 간주됩니다. 중복된 리터럴 값의 경우 SyntaxError 가 발생하고, 동일한 값을 가진 이름이 있는 키의 경우 ValueError 가 발생합니다.

참고

키-값 쌍은 매핑 대상의 get() 메서드 2인자 형태를 사용하여 매칭됩니다. 매칭된 키-값 쌍은 반드시 이미 매핑에 존재해야 하며, __missing__() 또는 __getitem__() 을 통해 실행 중에 생성되어서는 안 됩니다.

간단히 말해 {KEY1: P1, KEY2: P2, ... } 는 다음 사항이 모두 충족될 때만 일치합니다:

  • <subject> 가 매핑인지 확인

  • KEY1 in <subject>

  • P1<subject>[KEY1] 과 일치하는지 확인

  • … 그리고 대응하는 KEY/패턴 쌍에 대해 이와 같은 방식으로 진행합니다.

8.6.4.10. 클래스 패턴

클래스 패턴은 클래스와 그 위치 및 키워드 인자(있는 경우)를 나타냅니다. 구문:

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

클래스 패턴에서 동일한 키워드가 반복되어서는 안 됩니다.

클래스 패턴을 대상 값에 대해 매칭할 때의 논리적 흐름은 다음과 같습니다:

  1. name_or_attr 이 내장된 type 의 인스턴스가 아니면 TypeError 를 발생시킵니다.

  2. 대상(subject) 값이 name_or_attr 의 인스턴스가 아니면(isinstance() 를 통해 확인), 클래스 패턴이 실패합니다.

  3. 패턴 인자가 없으면 패턴은 성공합니다. 그 외의 경우, 이후 단계는 키워드 또는 위치 인자 패턴이 존재하는지 여부에 따라 달라집니다.

    일부 내장 타입(아래에 명시됨)의 경우, 대상 전체와 일치하는 단일 위치 서브패턴을 허용하며, 이러한 유형의 경우 다른 타입과 마찬가지로 키워드 패턴도 작동합니다.

    키워드 패턴만 존재하는 경우, 다음과 같이 하나씩 처리됩니다:

    1. 키워드를 대상의 어트리뷰트로 조회합니다.

      • 이 과정에서 AttributeError 이외의 예외가 발생하면, 해당 예외가 상위로 전파됩니다.

      • 이 과정에서 AttributeError 가 발생하면 클래스 패턴이 실패한 것입니다.

      • 그렇지 않은 경우, 키워드 패턴과 연관된 서브패턴을 대상의 어트리뷰트 값과 비교합니다. 이 과정이 실패하면 클래스 패턴이 실패하고, 성공하면 다음 키워드로 넘어갑니다.

    2. 모든 키워드 패턴이 성공하면 클래스 패턴도 성공합니다.

    위치 패턴이 하나라도 존재하는 경우, 매칭 전에 클래스 name_or_attr__match_args__ 어트리뷰트를 사용하여 키워드 패턴으로 변환합니다:

    1. getattr(cls, "__match_args__", ()) 와 동일한 기능이 호출됩니다.

      • 이 과정에서 예외가 발생하면 상위로 전파됩니다.

      • 반환된 값이 튜플이 아니면 변환에 실패하고 TypeError 가 발생합니다.

      • 위치 패턴의 개수가 len(cls.__match_args__) 보다 많으면 TypeError 가 발생합니다.

      • 그렇지 않으면, 위치 패턴 i__match_args__[i] 를 키워드로 사용하는 키워드 패턴으로 변환합니다. __match_args__[i] 는 반드시 문자열이어야 하며, 그렇지 않으면 TypeError 가 발생합니다.

      • 중복된 키워드가 있는 경우 TypeError 가 발생합니다.

    2. 모든 위치 패턴이 키워드 패턴으로 변환되면, 키워드 패턴만 존재하는 것처럼 매칭이 진행됩니다.

    다음의 내장 타입의 경우 위치 서브패턴을 처리하는 방식이 다릅니다:

    이 클래스들은 단일 위치 인자를 허용하며, 이때 패턴은 어트리뷰트가 아닌 객체 전체와 비교됩니다. 예를 들어 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 – Structural Pattern Matching: Tutorial

8.7. 함수 정의

함수 정의는 사용자 정의 함수 객체 (표준형 계층 섹션을 보세요) 를 정의합니다:

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

함수 정의는 실행할 수 있는 문장입니다. 실행하면 현재 지역 이름 공간의 함수 이름을 함수 객체 (함수의 실행 가능한 코드를 둘러싼 래퍼(wrapper)). 이 함수 객체는 현재의 이름 공간에 대한 참조를 포함하는데, 함수가 호출될 때 전역 이름 공간으로 사용됩니다.

함수 정의는 함수의 바디를 실행하지 않습니다. 함수가 호출될 때 실행됩니다. [4]

함수 정의는 하나나 그 이상의 데코레이터 표현식으로 감싸질 수 있습니다. 데코레이터 표현식은 함수가 정의될 때, 함수 정의를 포함하는 스코프에서 값을 구합니다. 그 결과는 콜러블이어야 하는데, 함수 객체만을 인자로 사용해서 호출됩니다. 반환 값이 함수 객체 대신 함수의 이름에 연결됩니다. 여러 개의 데코레이터는 중첩되는 방식으로 적용됩니다. 예를 들어, 다음과 같은 코드

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

는 대략 다음과 동등합니다

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

원래의 함수가 임시로 이름 func 에 연결되지 않는다는 점만 다릅니다.

버전 3.9에서 변경: 함수는 유효한 모든 assignment_expression 으로 데코레이션될 수 있습니다. 이전의 문법은 훨씬 더 제한적이었습니다. 자세한 내용은 PEP 614 를 참조하십시오.

함수 이름과 매개 변수 목록의 여는 괄호 사이에 대괄호를 사용하여 type parameters 목록을 지정할 수 있습니다. 이는 정적 타입 검사기에게 해당 함수가 제네릭임을 나타냅니다. 실행 시(runtime)에는 함수의 __type_params__ 어트리뷰트에서 타입 파라미터를 가져올 수 있습니다. 자세한 내용은 제네릭 함수 를 참조하십시오.

버전 3.12에서 변경: 타입 파라미터 리스트는 Python 3.12에서 새로 도입되었습니다.

하나나 그 이상의 매개변수 들이 parameter = expression 형태를 가질 때, 함수가 “기본 매개변수 값”을 갖는다고 말합니다. 기본값이 있는 매개변수의 경우, 호출할 때 대응하는 인자 를 생략할 수 있고, 그럴 때 매개변수의 기본값이 적용됩니다. 만약 매개변수가 기본값을 가지면, “*” 까지 그 뒤를 따르는 모든 매개변수도 기본값을 가져야 합니다 — 이것은 문법 규칙에서 표현되지 않는 문법적 제약입니다.

함수 정의가 실행될 때 기본 매개 변수 값은 왼쪽에서 오른쪽 방향으로 평가됩니다. 이는 함수가 정의되는 시점에 해당 표현식이 단 한 번만 평가되며, 매 호출마다 동일한 “미리 계산된” 값이 사용됨을 의미합니다. 특히 리스트나 딕셔너리와 같은 가변 객체가 기본 매개 변수 값인 경우 이를 이해하는 것이 중요합니다. 만약 함수가 해당 객체를 수정하면(예: 리스트에 항목 추가), 사실상 기본 매개 변수 값도 변경된 상태로 남게 됩니다. 이는 일반적으로 의도한 동작이 아닙니다. 이를 해결하는 방법은 기본값을 None 으로 설정하고, 함수의 본문 내에서 명시적으로 체크하는 것입니다. 예:

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin

함수 호출의 의미는 호출 섹션에서 더 자세히 설명합니다. 함수 호출은 항상 매개 변수 목록에 나열된 모든 매개 변수에 위치 인자, 키워드 인자 또는 기본값 중 하나로부터 값을 할당합니다. “*identifier” 형태가 있는 경우, 이는 초과하는 모든 위치 인자를 포함하는 튜플로 초기화되며, 없을 경우 빈 튜플이 기본값이 됩니다. “**identifier” 형태가 있는 경우, 이는 초과하는 모든 키워드 인자를 수용하는 새로운 순서 매핑으로 초기화되며, 없을 경우 동일한 타입의 빈 매핑이 기본값이 됩니다. “*” 또는 “*identifier” 뒤에 오는 매개 변수는 키워드 전용 매개 변수이며 오직 키워드 인자로만 전달될 수 있습니다. “/” 앞에 있는 매개 변수는 위치 전용 매개 변수이며 오직 위치 인자로만 전달될 수 있습니다.

버전 3.8에서 변경: / 함수 매개 변수 구문은 위치 전용 매개 변수를 나타내는 데 사용될 수 있습니다. 자세한 내용은 PEP 570 을 참조하십시오.

매개 변수 뒤에는 “: 표현식 “ 형태의 annotation 이 올 수 있습니다. 모든 매개 변수는 어노테이션을 가질 수 있으며, 이는 *identifier 또는 **identifier 형태를 포함합니다 (특수한 경우로, *identifier 형식의 매개 변수는 “: *표현식 “ 형태의 어노테이션을 가질 수 있습니다). 함수는 매개 변수 목록 뒤에 “-> 표현식 “ 형태의 “반환(return)” 어노테이션을 가질 수 있습니다. 이러한 어노테이션은 유효한 모든 Python 표현식이 될 수 있습니다. 어노테이션의 존재가 함수의 의미를 변경하지는 않습니다. 어노테이션에 대한 자세한 정보는 어노테이션 를 참조하십시오.

버전 3.11에서 변경: *identifier “ 형태의 매개 변수는 “: *expression “ 어노테이션을 가질 수 있습니다. PEP 646 를 참조하십시오.

표현식에서 즉시 사용하기 위해, 이름 없는 함수(이름에 연결되지 않은 함수)를 만드는 것도 가능합니다. 이것은 람다 표현식을 사용하는데, 람다(Lambdas) 섹션에서 설명합니다. 람다 표현식은 단순화된 함수 정의를 위한 줄임 표현에 지나지 않는다는 것에 주의하세요; “def” 문장에서 정의된 함수는 람다 표현식으로 정의된 함수처럼 전달되거나 다른 이름에 대입될 수 있습니다. 여러 개의 문장을 실행하는 것과 어노테이션을 허락하기 때문에, “def” 형태가 사실 더 강력합니다.

프로그래머 유의 사항: 함수는 퍼스트 클래스(first-class) 객체다. 함수 정의 안에서 실행되는 “def” 문은 돌려주거나 전달할 수 있는 지역 함수를 정의합니다. 중첩된 함수에서 사용되는 자유 변수들은 그 def 를 포함하는 함수의 지역 변수들을 액세스할 수 있습니다. 더 자세한 내용은 이름과 연결(binding) 섹션을 보세요.

더 보기

PEP 3107 - 함수 어노테이션

함수 어노테이션의 최초 규격.

PEP 484 - 형 힌트

어노테이션에 대한 표준 의미 정의: 형 힌트.

PEP 526 - 변수 어노테이션 문법

클래스 변수 및 인스턴스 변수를 포함한 변수 선언에 대한 타입 힌트 기능.

PEP 563 - 어노테이션의 지연된 평가

즉시 평가하는 대신 실행시간에 어노테이션을 문자열 형식으로 보존하여 어노테이션 내에서의 전방 참조를 지원합니다.

PEP 318 - 함수 및 메서드용 데코레이터

함수와 메서드 데코레이터가 도입되었습니다. 클래스 데코레이터는 PEP 3129 에서 도입되었습니다.

8.8. 클래스 정의

클래스 정의는 클래스 객체(표준형 계층 섹션을 보세요)를 정의합니다:

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

클래스 정의는 실행 가능한 문장입니다. 계승(inheritance) 목록은 보통 베이스 클래스들의 목록을 제공하는데 (더 고급 사용에 대해서는 메타 클래스 를 보세요), 목록의 각 항목은 값을 구할 때 서브 클래싱을 허락하는 클래스 객체가 되어야 합니다. 계승 목록이 없는 클래스는, 기본적으로, 베이스 클래스 object 를 계승합니다; 그래서

class Foo:
    pass

는 다음과 동등합니다

class Foo(object):
    pass

하나 이상의 베이스 클래스가 있을 수 있습니다. 자세한 내용은 아래의 다중 상속 를 참조하십시오.

클래스의 스위트는 새로 만들어진 지역 이름 공간과 원래의 전역 이름 공간을 사용하는 새 실행 프레임 (이름과 연결(binding) 을 보세요)에서 실행됩니다. (보통, 스위트는 대부분 함수 정의들을 포함합니다.) 클래스의 스위트가 실행을 마치면, 실행 프레임은 파기하지만, 그것의 지역 이름 공간은 보존합니다. [5] 그런 다음, 계승 목록을 베이스 클래스들로, 보존된 지역 이름 공간을 어트리뷰트 딕셔너리로 사용해서 새 클래스 객체를 만듭니다. 클래스의 이름은 원래의 지역 이름 공간에서 이 클래스 객체와 연결됩니다.

클래스 본문에서 어트리뷰스가 정의된 순서가 새 클래스의 __dict__ 속성에 유지됩니다. 이 정보는 클래스가 생성된 직후에만 신뢰할 수 있으며, 정의 구문을 사용하여 정의된 클래스에 대해서만 유효합니다.

클래스 생성은 메타 클래스 를 사용해서 심하게 커스터마이즈할 수 있습니다.

클래스 역시 함수를 데코레이팅할 때처럼 테코레이트할 수 있습니다,

@f1(arg)
@f2
class Foo: pass

는 대략 다음과 동등합니다

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

데코레이터 표현식의 값을 구하는 규칙은 함수 데코레이터와 같습니다. 그런 다음 그 결과가 클래스 이름에 연결됩니다.

버전 3.9에서 변경: 클래스는 유효한 모든 assignment_expression 으로 데코레이션될 수 있습니다. 이전의 문법은 훨씬 더 제한적이었습니다. 자세한 내용은 PEP 614 를 참조하십시오.

클래스 이름 바로 뒤에 대괄호를 사용하여 type parameters 목록을 지정할 수 있습니다. 이는 정적 타입 검사기에게 해당 클래스가 제네릭임을 나타냅니다. 실행 시에는 클래스의 __type_params__ 어트리뷰트에서 타입 파라미터를 가져올 수 있습니다. 자세한 내용은 제네릭 클래스 를 참조하십시오.

버전 3.12에서 변경: 타입 파라미터 리스트는 Python 3.12에서 새로 도입되었습니다.

프로그래머 유의 사항: 클래스 정의에서 정의되는 변수들은 클래스 어트리뷰트입니다; 이것들은 인스턴스 간에 공유됩니다. 인스턴스 어트리뷰트는 메서드에서 self.name = value 로 설정될 수 있습니다. 클래스와 인스턴스 어트리뷰트 모두 “self.name” 표기법으로 액세스할 수 있고, 이런 식으로 액세스할 때 인스턴스 어트리뷰트는 같은 이름의 클래스 어트리뷰트를 가립니다. 클래스 어트리뷰트는 인스턴스 어트리뷰트의 기본값으로 사용될 수 있지만, 가변 값을 사용하는 것은 예상하지 않은 결과를 줄 수 있습니다. 디스크립터 를 다른 구현 상세를 갖는 인스턴스 변수를 만드는데 사용할 수 있습니다.

더 보기

PEP 3115 - 파이썬 3000의 메타 클래스

메타 클래스 선언을 현재 문법으로 변경하고, 메타 클래스가 있는 클래스를 구성하는 방법의 의미를 변경하는 제안.

PEP 3129 - 클래스 데코레이터

클래스 데코레이터를 추가하는 제안. 함수와 메서드 데코레이터는 PEP 318에서 도입되었습니다.

8.8.1. 다중 상속

Python 클래스는 여러 개의 베이스 클래스를 가질 수 있으며, 이는 다중 상속 이라고 알려진 기술입니다. 베이스 클래스는 클래스 정의 시 클래스 이름 뒤 괄호 안에 쉼표로 구분하여 나열함으로써 지정합니다. 예를 들어, 다음 클래스 정의는:

>>> class A: pass
>>> class B: pass
>>> class C(A, B): pass

클래스 AB 로부터 상속받는 클래스 C 를 정의합니다.

method resolution order (MRO)는 클래스에서 어트리뷰스를 찾을 때 베이스 클래스를 검색하는 순서입니다. Python이 클래스의 MRO를 결정하는 방식에 대한 설명은 Python 2.3 메서드 결정 순서 를 참조하십시오.

다중 상속이 항상 허용되는 것은 아닙니다. 다중 상속을 사용하여 클래스를 정의하려고 할 때, 베이스 중 하나가 서브클래싱을 허용하지 않거나, 일관된 MRO를 생성할 수 없거나, 유효한 메타클래스를 결정할 수 없거나, 인스턴스 레이아웃 충돌이 발생하는 경우 오류가 발생합니다. 각각의 상황에 대해 차례로 설명하겠습니다.

첫째, 모든 베이스 클래스는 서브클래싱을 허용해야 합니다. 대부분의 클래스가 서브클래싱을 허용하지만, bool 과 같은 일부 내장 클래스는 그렇지 않습니다.

>>> class SubBool(bool):  # TypeError
...    pass
Traceback (most recent call last):
   ...
TypeError: type 'bool' is not an acceptable base type

해결된 클래스의 MRO에서, 클래스의 베이스는 클래스의 베이스 리스트에 명시된 순서대로 나타납니다. 또한, MRO는 항상 자식 클래스를 그 어떤 베이스보다 먼저 나열합니다. 제공된 베이스 목록으로부터 이러한 규칙을 충족하는 일관된 MRO를 도출할 수 없는 경우 클래스 정의는 실패하게 됩니다:

>>> class Base: pass
>>> class Child(Base): pass
>>> class Grandchild(Base, Child): pass  # TypeError
Traceback (most recent call last):
   ...
TypeError: Cannot create a consistent method resolution order (MRO) for bases Base, Child

Grandchild 의 MRO에서 Base 는 베이스 클래스 리스트의 첫 번째 항목이므로 Child 보다 먼저 나타나야 하지만, 동시에 Child 의 부모이므로 Child 보다 나중에 나타나야 합니다. 이는 모순이므로 해당 클래스를 정의할 수 없습니다.

일부 베이스가 사용자 정의 metaclass 를 갖는 경우, 결과 클래스의 메타클래스는 베이스들의 메타클래스와 자식 클래스에 명시적으로 지정된 메타클래스 중에서 선택됩니다. 이 메타클래스는 다른 모든 후보 메타클래스의 서브클래스여야 합니다. 후보들 중에서 그러한 메타클래스가 존재하지 않으면, 적절한 메타 클래스 선택하기 에서 설명하는 바와 같이 클래스를 생성할 수 없습니다.

마지막으로, 베이스들의 인스턴스 레이아웃이 호환되어야 합니다. 이는 클래스에 대한 solid base 를 계산할 수 있어야 함을 의미합니다. 정확히 어떤 클래스가 solid base인지는 파이썬 구현에 따라 달라집니다.

CPython에서 클래스가 비어 있지 않은 __slots__ 정의를 가지면 solid base로 간주됩니다. C로 정의된 많은 클래스들이 solid base에 해당하며, 여기에는 대부분의 내장 객체(예: int 또는 BaseException)가 포함되지만, 대부분의 구체적인 Exception 클래스는 제외됩니다. 일반적으로 C 클래스의 기초 구조체가 베이스 클래스와 크기가 다르면 해당 클래스는 solid base입니다.

모든 클래스는 solid base를 갖습니다. 기본 클래스인 object 은 자기 자신을 solid base로 가집니다. 베이스가 하나인 경우, 자식 클래스의 solid base는 그 기반이 되는 클래스가 solid base라면 해당 클래스이고, 그렇지 않으면 베이스 클래스의 solid base가 됩니다. 여러 개의 베이스가 있는 경우, 먼저 각 베이스 클래스에 대한 solid base를 찾아 후보 solid base 리스트를 생성합니다. 만약 다른 모든 것들의 서브클래스인 유일한 solid base가 존재하면 그 클래스가 solid base가 됩니다. 그렇지 않으면 클래스 생성이 실패합니다.

예제:

>>> class Solid1:
...    __slots__ = ("solid1",)
>>>
>>> class Solid2:
...    __slots__ = ("solid2",)
>>>
>>> class SolidChild(Solid1):
...    __slots__ = ("solid_child",)
>>>
>>> class C1:  # solid base is `object`
...    pass
>>>
>>> # OK: solid bases are `Solid1` and `object`, and `Solid1` is a subclass of `object`.
>>> class C2(Solid1, C1):  # solid base is `Solid1`
...    pass
>>>
>>> # OK: solid bases are `SolidChild` and `Solid1`, and `SolidChild` is a subclass of `Solid1`.
>>> class C3(SolidChild, Solid1):  # solid base is `SolidChild`
...    pass
>>>
>>> # Error: solid bases are `Solid1` and `Solid2`, but neither is a subclass of the other.
>>> class C4(Solid1, Solid2):  # error: no single solid base
...    pass
Traceback (most recent call last):
  ...
TypeError: multiple bases have instance lay-out conflict

8.9. 코루틴

Added in version 3.5.

8.9.1. 코루틴 함수 정의

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

파이썬 코루틴은 여러 지점에서 일시 중단되고 재개될 수 있습니다(coroutine 참조). await 표현식, async for, 그리고 async with 는 코루틴 함수 본문 내에서만 사용할 수 있습니다.

async def 문법으로 정의된 함수는 항상 코루틴 함수인데, awaitasync 키워드를 포함하지 않는 경우도 그렇습니다.

코루틴 함수의 바디 안에서 yield from 표현식을 사용하는 것은 SyntaxError 입니다.

코루틴 함수의 예:

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

버전 3.7에서 변경: awaitasync 는 이제 키워드입니다. 이전에는 코루틴 함수의 본문 내에서만 그렇게 취급되었습니다.

8.9.2. async for

async_for_stmt: "async" for_stmt

비동기 이터러블비동기 이터레이터 를 직접 반환하는 __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__() 를 위해 암시적인 special method lookup 이 사용되는 경우는 제외합니다.

코루틴 함수의 바디 밖에서 async for 문을 사용하는 것은 SyntaxError 입니다.

8.9.3. async with

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__() 를 위해 암시적인 special method lookup 이 사용되는 경우는 제외합니다.

코루틴 함수의 바디 밖에서 async with 문을 사용하는 것은 SyntaxError 입니다.

더 보기

PEP 492 - async 와 await 문법을 사용하는 코루틴

코루틴을 파이썬에서 적절한 독립적인 개념으로 만들고, 문법 지원을 추가한 제안.

8.10. 타입 매개변수 목록

Added in version 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)?

Functions ( coroutines 포함), classes, 그리고 type aliases 는 타입 매개변수 목록을 포함할 수 있습니다:

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]

의미론적으로 이는 해당 함수, 클래스 또는 타입 에일리어스가 타입 변수에 대해 제네릭임을 나타냅니다. 이 정보는 주로 정적 타입 검사기에서 사용되며, 실행 시(runtime)에 제네릭 객체는 비제네릭 대응 객체와 매우 유사하게 동작합니다.

타입 매개변수는 함수, 클래스 또는 타입 에일리어스 이름 바로 뒤에 대괄호([]) 안에 선언됩니다. 타입 매개변수는 제네릭 객체의 범위 내에서는 접근 가능하지만 그 외의 곳에서는 접근할 수 없습니다. 따라서 def func[T](): pass 와 같은 선언 이후, 이름 T 는 모듈 스코프에서 사용할 수 없습니다. 아래에서는 제네릭 객체의 의미론을 더 정확하게 설명합니다. 타입 매개변수의 범위는 제네릭 객체의 생성을 래핑하는 특수 기능(기술적으로는 annotation scope)으로 모델링됩니다.

제네릭 함수, 클래스, 그리고 타입 에일리어스는 해당 타입 매개변수를 나열하는 __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__ 를 통해 값이 명시적으로 접근될 때만 제한 범위와 제약 조건이 평가됩니다. 이를 위해 제한 범위 또는 제약 조건은 별도의 annotation scope 에서 평가됩니다.

typing.TypeVarTupletyping.ParamSpec 은 제한 범위나 제약 조건을 가질 수 없습니다.

세 가지 종류의 타입 매개변수 모두는 기본값 을 가질 수 있으며, 이는 타입 매개변수가 명시적으로 제공되지 않을 때 사용됩니다. 이는 등호(=) 뒤에 표현을 추가함으로써 추가됩니다. 타입 변수의 제한 범위 및 제약 조건과 마찬가지로 기본값은 객체가 생성될 때 평가되지 않고, 해당 타입 매개변수의 __default__ 어트리뷰트에 접근할 때만 평가됩니다. 이를 위해 기본값은 별도의 annotation scope 에서 평가됩니다. 타입 매개변스에 대한 기본값이 지정되지 않은 경우, __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. 제네릭 함수

제네릭 함수는 다음과 같이 선언됩니다:

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 는 실행 시 어떤 이름에도 바인딩되지 않는 annotation scope 를 나타냅니다. (번역 과정에서 한 가지 자유가 허용되었습니다. 이 구문은 typing 모듈의 속성에 접근하지 않고, typing.TypeVar 인스턴스를 직접 생성합니다.)

제네릭 함수의 어노테이션은 타입 매개변수를 선언하는 데 사용되는 annotation scope 내에서 평가되지만, 함수의 기본값과 데코레이터는 해당 범위 내에서 평가되지 않습니다.

다음 예제는 이러한 경우의 스코프 규칙과 추가적인 타입 매개변수 형태들에 대한 것을 설명합니다:

@decorator
def func[T: int, *Ts, **P](*args: *Ts, arg: Callable[P, T] = some_default):
    ...

TypeVar 범위의 lazy evaluation 을 제외하고, 이는 다음과 동등합니다:

DEFAULT_OF_arg = some_default

annotation-def TYPE_PARAMS_OF_func():

    annotation-def BOUND_OF_T():
        return int
    # 실제로 BOUND_OF_T()는 필요할 때만 평가됩니다.
    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. 제네릭 클래스

제네릭 클래스는 다음과 같이 선언됩니다:

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 (실제 키워드가 아님)는 annotation scope 를 나타내며, 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. 제네릭 타입 에일리어스

type 문을 사용하여 제네릭 타입 에일리어스를 생성할 수도 있습니다:

type ListOrSet[T] = list[T] | set[T]

값의 lazy evaluation 를 제외하고, 이는 다음과 동등합니다:

annotation-def TYPE_PARAMS_OF_ListOrSet():
    T = typing.TypeVar("T")

    annotation-def VALUE_OF_ListOrSet():
        return list[T] | set[T]
    # 실제로 값은 지연 평가됩니다
    return typing.TypeAliasType("ListOrSet", VALUE_OF_ListOrSet(), type_params=(T,))
ListOrSet = TYPE_PARAMS_OF_ListOrSet()

여기서 annotation-def (실제 키워드 아님)는 annotation scope 를 나타냅니다. TYPE_PARAMS_OF_ListOrSet 과 같이 대문자로 시작하는 이름은 실행 시 실제로 바인딩되지 않습니다.

8.11. 어노테이션

버전 3.14에서 변경: 어노테이션은 이제 기본적으로 지연 평가됩니다.

변수와 함수 매개 변수는 이름 뒤에 콜론을 추가하고 표현을 덧붙여 annotations 를 가질 수 있습니다:

x: annotation = 1
def f(param: annotation): ...

함수는 화살표 뒤에 반환 어노테이션을 포함할 수도 있습니다:

def f() -> annotation: ...

어노테이션은 관례적으로 type hints 를 위해 사용되지만, 언어 차원에서 강제되지 않으며 일반적으로 어노테이션은 임의의 표현을 포함할 수 있습니다. 어노테이션의 존재는 어노테이션을 조사하고 사용하는 메커니즘(예: dataclasses 또는 @functools.singledispatch)이 사용되는 경우가 아니라면 코드의 런타임 의미를 변경하지 않습니다.

기본적으로 어노테이션은 annotation scope 에서 지연 평가됩니다. 이는 어노테이션을 포함하는 코드가 평가될 때 어노테이션이 즉시 평가되지 않음을 의미합니다. 대신, 인터프리터는 요청 시 나중에 어노테이션을 평가하는 데 사용할 수 있는 정보를 저장합니다. annotationlib 모듈은 어노테이션을 평가하기 위한 도구들을 제공합니다.

만약 future statement from __future__ import annotations 가 있으면, 모든 어노테이션은 대신 문자열로 저장됩니다:

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

이 future 문은 향후 Python 버전에서 폐기되고 제거될 예정이지만, Python 3.13의 수명이 다하기 전까지는 그렇게 되지 않습니다(see PEP 749). 이 기능이 사용되는 경우, annotationlib.get_annotations()typing.get_type_hints() 와 같은 조사 도구들이 실행 중에 어노테이션을 해석할 가능성이 낮아집니다.

각주

분실물 보관소