7. 단순문(Simple statements)

7. 단순문(Simple statements)

단순문(simple statement)은 하나의 논리적 줄 안에 들어가는 문장이에요. 세미콜론으로 구분하면 한 줄에 여러 단순문을 쓸 수도 있고요. 이 장에서는 대입, 반환, 예외 발생, 반복문 제어, import 등 파이썬이 제공하는 모든 단순문을 하나씩 살펴볼게요. 각 문장은 문법 생성규칙과 함께 설명하니, 규칙을 어떻게 읽는지 확인하면서 따라와 주세요.

출처: Python 언어 레퍼런스

본문

단순문의 문법은 이렇게 생겼어요.

simple_stmt: expression_stmt
             | assert_stmt
             | assignment_stmt
             | augmented_assignment_stmt
             | annotated_assignment_stmt
             | pass_stmt
             | del_stmt
             | return_stmt
             | yield_stmt
             | raise_stmt
             | break_stmt
             | continue_stmt
             | import_stmt
             | future_stmt
             | global_stmt
             | nonlocal_stmt
             | type_stmt

즉 단순문은 어떤 문장이든 그 종류에 따라 이 규칙들 중 하나로 확장돼요. 이제 각 문장을 차례로 볼게요.

7.1. 표현식문(Expression statements)

표현식문은 대부분 인터랙티브 모드에서 값을 계산하고 출력하거나, 보통은 프로시저(의미 있는 결과를 돌려주지 않는 함수)를 호출할 때 써요. 파이썬에서 프로시저는 None을 돌려주죠. 다른 용도로도 쓸 수 있고, 가끔은 유용할 때도 있어요. 표현식문의 문법은 이렇습니다.

expression_stmt: starred_expression

표현식문은 표현식 리스트(단일 표현식일 수도 있어요)를 평가해요. 인터랙티브 모드에서는 값이 None이 아니면 내장 repr() 함수로 문자열로 변환한 뒤, 그 문자열을 표준 출력에 한 줄로 출력해요. 결과가 None이면 아무 출력도 없어서, 프로시저 호출 때문에 화면이 지저분해지지 않아요.

7.2. 대입문(Assignment statements)

대입문은 이름에 값을 (다시) 바인딩하거나, 가변 객체의 애트리뷰트나 항목을 수정할 때 써요.

assignment_stmt: (target_list "=")+ (starred_expression | yield_expression)
target_list:     target ("," target)* [","]
target:          identifier
                 | "(" [target_list] ")"
                 | "[" [target_list] "]"
                 | attributeref
                 | subscription
                 | "*" target

(attributeref와 subscription의 문법 정의는 'Primaries' 절에서 확인할 수 있어요.)

대입문은 표현식 리스트를 평가해요. 기억해 두세요. 표현식 리스트는 단일 표현식일 수도 있고, 콤마로 구분된 리스트(이 경우 튜플이 됩니다)일 수도 있어요. 그리고 그 결과로 나온 단일 객체를 각 대상 리스트에 왼쪽에서 오른쪽으로 대입해요.

대입은 대상(리스트)의 형태에 따라 재귀적으로 정의돼요. 대상이 가변 객체의 일부(애트리뷰트 참조나 구독)라면, 그 가변 객체가 최종적으로 대입을 수행하고 타당성을 판단해요. 대입이 받아들여질 수 없으면 예외를 던질 수도 있어요. 다양한 타입이 지키는 규칙과 던지는 예외는 객체 타입 정의('표준 타입 계층' 절)에서 다룹니다.

선택적으로 괄호나 대괄호로 감싼 대상 리스트에 객체를 대입하는 동작은 다음과 같이 재귀적으로 정의돼요.

  • 대상 리스트가 끝에 콤마가 없는 단일 대상(선택적으로 괄호로 감싸인)이라면, 그 객체를 그 대상에 대입해요.
  • 그 외에, 대상 리스트에 별표(*)가 붙은 대상, 즉 'starred' 대상이 하나 포함되어 있다면: 객체는 대상 리스트의 대상 개수보다 하나 적은 만큼 이상의 항목을 가진 이터러블이어야 해요. 이터러블의 앞쪽 항목들은 starred 대상 앞에 있는 대상들에 왼쪽에서 오른쪽으로 대입되고, 마지막 항목들은 starred 대상 뒤에 있는 대상들에 대입돼요. 그러고 나서 남은 항목들의 리스트(빌 수 있어요)가 starred 대상에 대입됩니다.
  • 그 외에는, 객체는 대상 리스트의 대상 수와 같은 개수의 항목을 가진 이터러블이어야 하고, 항목들이 왼쪽에서 오른쪽으로 각각의 대상에 대입돼요.

단일 대상에 객체를 대입하는 것도 다음과 같이 재귀적으로 정의돼요.

  • 대상이 식별자(이름)라면: 그 이름이 현재 코드 블록의 global이나 nonlocal문에 없다면, 현재 로컬 네임스페이스에 이름을 바인딩해요. 그렇지 않다면, 각각 전역 네임스페이스나 nonlocal이 결정한 바깥 네임스페이스에 바인딩합니다. 이미 바인딩되어 있었다면 이름을 다시 바인딩해요. 이 과정에서 이전에 그 이름에 바인딩됐던 객체의 참조 횟수가 0이 될 수 있고, 그러면 객체가 할당 해제되고 소멸자(있다면)가 호출됩니다.
  • 대상이 애트리뷰트 참조라면: 참조 안의 기본 표현식(primary expression)을 평가해요. 이것은 대입 가능한 애트리뷰트를 가진 객체를 내놓아야 해요. 그렇지 않으면 TypeError가 발생합니다. 그런 다음 그 객체에게 주어진 애트리뷰트에 대입할 객체를 대입하도록 요청해요. 수행할 수 없으면 예외를 던지는데, 보통은 AttributeError지만 항상 그런 건 아니에요. 여기서 한 가지 주의할 점이 있어요. 객체가 클래스 인스턴스이고 애트리뷰트 참조가 대입 연산자의 양쪽에 나타나면, 오른쪽 표현식 a.x는 인스턴스 애트리뷰트나 (인스턴스 애트리뷰트가 없다면) 클래스 애트리뷰트에 접근할 수 있어요. 그런데 왼쪽 대상 a.x는 항상 인스턴스 애트리뷰트로 설정되며, 필요하면 새로 만들어집니다. 그래서 두 a.x가 반드시 같은 애트리뷰트를 가리키는 건 아니에요. 오른쪽이 클래스 애트리뷰트를 가리켰다면, 왼쪽은 대입 대상으로 새로운 인스턴스 애트리뷰트를 만듭니다.
class Cls:
    x = 3             # class variable
inst = Cls()
inst.x = inst.x + 1   # writes inst.x as 4 leaving Cls.x as 3

다만 이 설명이 @property로 만든 속성 같은 디스크립터 애트리뷰트에는 그대로 적용되지는 않아요.

  • 대상이 구독(subscription)이라면: 참조 안의 기본 표현식을 평가하고, 그다음 구독 표현식을 평가해요. 그리고 기본 객체의 __setitem__() 메서드를 구독 값과 대입할 객체 두 인자로 호출합니다. 보통 __setitem__()은 리스트 같은 가변 시퀀스 객체와 딕셔너리 같은 매핑 객체에 정의되어 있고, 다음과 같이 동작해요. 기본 객체가 가변 시퀀스 객체(예: 리스트)라면, 구독 값은 정수가 되어야 해요. 음수라면 시퀀스의 길이를 더하고, 결과값은 0 이상이고 시퀀스 길이보다 작은 정수여야 해요. 그러면 시퀀스에게 그 인덱스의 항목에 대입할 객체를 대입하도록 요청해요. 인덱스가 범위를 벗어나면 IndexError가 발생합니다(구독된 시퀀스에 대입해도 리스트에 새 항목을 추가할 수는 없어요). 기본 객체가 매핑 객체(예: 딕셔너리)라면, 구독 값은 매핑의 키 타입과 호환되는 타입이어야 해요. 그러면 매핑에게 구독 값을 대입할 객체로 매핑하는 키/값 쌍을 만들도록 요청해요. 같은 키 값을 가진 기존 키/값 쌍을 교체하거나, 같은 값의 키가 없었다면 새 키/값 쌍을 삽입할 수 있어요.
  • 대상이 슬라이싱(slicing)이라면: 기본 표현식은 가변 시퀀스 객체(예: 리스트)로 평가되어야 해요. 대입할 객체는 이터러블이어야 하고요. 슬라이싱의 하한과 상한은 정수여야 해요. None이거나 없으면 기본값은 0과 시퀀스의 길이입니다. 어느 쪽 경계든 음수라면 시퀀스의 길이를 더하고, 결과 경계는 0과 시퀀스 길이 사이(포함)로 잘려요. 마지막으로 시퀀스 객체에게 슬라이스를 대입된 시퀀스의 항목들로 교체하도록 요청해요. 슬라이스의 길이가 대입된 시퀀스의 길이와 달라도 돼요. 대상 시퀀스가 허용한다면 그만큼 길이가 바뀌는 거예요.

대입 정의가 왼쪽과 오른쪽 사이의 겹침을 '동시에' 일어난다고 가정하지만(예: a, b = b, a는 두 변수를 교환해요), 대입되는 변수들끼리의 겹침은 왼쪽에서 오른쪽으로 일어나서 가끔 혼란을 주기도 해요. 예를 들어 다음 프로그램은 [0, 2]를 출력합니다.

x = [0, 1]
i = 0
i, x[i] = 1, 2         # i is updated, then x[i] is updated
print(x)

참고: PEP 3132 - Extended Iterable Unpacking — * 대상 기능의 명세.

7.2.1. 증강 대입문(Augmented assignment statements)

증강 대입은 하나의 문장에서 이항 연산과 대입을 결합한 거예요.

augmented_assignment_stmt: augtarget augop (expression_list | yield_expression)
augtarget:                 identifier | attributeref | subscription
augop:                     "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**="
                           | ">>=" | "<<=" | "&=" | "^=" | "|="

(마지막 세 기호에 대한 문법 정의는 'Primaries' 절에서 확인할 수 있어요.)

증강 대입은 대상(일반 대입문과 달리 언패킹이 될 수 없어요)과 표현식 리스트를 평가하고, 두 피연산자에 그 대입 타입에 특정한 이항 연산을 수행한 뒤 결과를 원래 대상에 대입해요. 대상은 딱 한 번만 평가됩니다.

x += 1 같은 증강 대입문은 x = x + 1로 다시 써서 비슷하지만 정확히 같지는 않은 효과를 얻을 수 있어요. 증강 버전에서는 x가 한 번만 평가돼요. 또 가능하면 실제 연산을 제자리(in-place)에서 수행해서, 새 객체를 만들어 대상에 대입하는 대신 기존 객체를 수정합니다.

일반 대입과 달리, 증강 대입은 오른쪽보다 왼쪽을 먼저 평가해요. 예를 들어 a[i] += f(x)는 먼저 a[i]를 찾아본 다음 f(x)를 평가하고 덧셈을 수행한 뒤, 마지막으로 결과를 a[i]에 다시 써요.

한 문장에서 튜플이나 여러 대상에 대입하는 경우를 빼면, 증강 대입이 수행하는 대입은 일반 대입과 같은 방식으로 처리돼요. 마찬가지로 가능한 제자리 동작을 빼면, 증강 대입이 수행하는 이항 연산은 일반 이항 연산과 같아요. 애트리뷰트 참조인 대상에 대해서는 일반 대입과 같은 클래스/인스턴스 애트리뷰트 관련 주의사항이 적용됩니다.

7.2.2. 애노테이션 대입문(Annotated assignment statements)

애노테이션 대입은 하나의 문장에서 변수나 애트리뷰트 애노테이션과 선택적인 대입문을 결합한 거예요.

annotated_assignment_stmt: augtarget ":" expression
                           ["=" (starred_expression | yield_expression)]

일반 대입문과의 차이는 단일 대상만 허용된다는 점이에요.

대입 대상이 괄호로 감싸지지 않은 단일 이름으로 이루어져 있으면 그 대상을 '단순(simple)'하다고 해요. 단순 대입 대상인데 클래스나 모듈 스코프라면, 애노테이션들은 지연 평가되는 애노테이션 스코프(annotation scope)에 모여요. 이 애노테이션들은 클래스나 모듈의 __annotations__ 애트리뷰트나 annotationlib 모듈의 기능으로 평가할 수 있습니다.

대입 대상이 단순하지 않으면(애트리뷰트, 구독 노드, 괄호로 감싼 이름 등), 애노테이션은 절대 평가되지 않아요. 함수 스코프에서 이름이 애노테이션되면 그 이름은 그 스코프에서 로컬이 됩니다. 함수 스코프에서는 애노테이션이 평가되거나 저장되지 않아요.

오른쪽이 있으면, 애노테이션 대입은 애노테이션이 없는 것처럼 실제 대입을 수행해요. 표현식 대상에 오른쪽이 없다면, 인터프리터는 마지막 __setitem__()이나 __setattr__() 호출을 빼고 대상을 평가합니다.

참고:

  • PEP 526 - Syntax for Variable Annotations — 변수 타입을 주석으로 표현하는 대신 애노테이션하는 문법을 추가한 제안.
  • PEP 484 - Type hints — 정적 분석 도구와 IDE에서 쓸 수 있는 표준 타입 애노테이션 문법을 제공하는 typing 모듈을 추가한 제안.

버전 변경 사항:

  • 버전 3.8 변경: 이제 애노테이션 대입이 일반 대입과 같은 표현식을 오른쪽에 허용해요. 이전에는 일부 표현식(괄호로 감싸지 않은 튜플 표현식 등)이 문법 오류를 냈어요.
  • 버전 3.14 변경: 애노테이션은 이제 별도의 애노테이션 스코프에서 지연 평가돼요. 대입 대상이 단순하지 않으면 애노테이션은 절대 평가되지 않습니다.

7.3. assert 문

assert문은 프로그램에 디버깅용 단언(assertion)을 넣는 편리한 방법이에요.

assert_stmt: "assert" expression ["," expression]

단순 형태 assert expression은 다음과 같아요.

if __debug__:
    if not expression: raise AssertionError

확장 형태인 assert expression1, expression2는 이렇게 되고요.

if __debug__:
    if not expression1: raise AssertionError(expression2)

이 두 동등 변환은 __debug__AssertionError가 그 이름을 가진 내장 변수를 가리킨다고 가정해요. 현재 구현에서 내장 변수 __debug__는 일반적인 상황에서는 True이고, 최적화를 요청하면(명령줄 옵션 -O) False예요. 컴파일 시점에 최적화가 요청되면 현재 코드 생성기는 assert문에 아무 코드도 만들지 않아요. 실패한 표현식의 소스 코드를 오류 메시지에 넣을 필요는 없어요. 그건 스택 트레이스의 일부로 자동 표시되니까요. __debug__에 대입하는 것은 불법이에요. 이 내장 변수의 값은 인터프리터가 시작될 때 결정됩니다.

7.4. pass 문

pass_stmt: "pass"

pass는 널 연산이에요. 실행되어도 아무 일도 일어나지 않죠. 문법적으로 문장이 필요한데 실행할 코드가 없을 때의 자리 표시자로 유용해요. 예를 들면 이런 경우예요.

def f(arg): pass    # a function that does nothing (yet)

class C: pass       # a class with no methods (yet)

7.5. del 문

del_stmt: "del" target_list

삭제는 대입과 매우 비슷하게 재귀적으로 정의돼요. 전부 자세히 설명하는 대신, 몇 가지 힌트만 드릴게요.

  • 대상 리스트의 삭제는 각 대상을 왼쪽에서 오른쪽으로 재귀적으로 삭제해요.
  • 이름의 삭제는 그 이름이 같은 코드 블록의 global문에 등장하는지에 따라 로컬 또는 전역 네임스페이스에서 그 이름의 바인딩을 제거해요. 바인딩되지 않은 이름을 삭제하려 하면 NameError 예외가 발생합니다.
  • 애트리뷰트 참조와 구독의 삭제는 관련된 기본 객체에 넘겨지고, 슬라이싱의 삭제는 일반적으로 올바른 타입의 빈 슬라이스에 대입하는 것과 같아요(이것도 슬라이스 대상 객체가 결정합니다).

버전 3.2 변경: 이전에는 이름이 중첩 블록에서 자유 변수로 나타나면 로컬 네임스페이스에서 그 이름을 삭제하는 것이 불법이었어요.

7.6. return 문

return_stmt: "return" [expression_list]

return은 문법적으로 함수 정의 안에 중첩된 곳에서만 나타날 수 있어요. 함수 정의 안에 중첩된 클래스 정의에서는 쓸 수 없습니다. 표현식 리스트가 있으면 그것을 평가하고, 없으면 None이 대신 사용돼요. return은 현재 함수 호출을 표현식 리스트(또는 None)를 반환값으로 남기고 떠나요.

return이 finally 절이 있는 try문 밖으로 제어를 넘기는 경우, 그 finally 절은 함수를 실제로 떠나기 전에 실행됩니다. 제너레이터 함수에서는 return문이 제너레이터가 끝났음을 나타내고 StopIteration이 발생하게 해요. 반환값(있다면)은 StopIteration을 만드는 인자로 쓰이고 StopIteration.value 애트리뷰트가 됩니다. 비동기 제너레이터 함수에서는 빈 return문이 비동기 제너레이터가 끝났음을 나타내고 StopAsyncIteration이 발생하게 해요. 비동기 제너레이터 함수에서 내용이 있는 return문은 문법 오류입니다.

7.7. yield 문

yield_stmt: yield_expression

yield문은 의미상 yield 표현식과 같아요. yield문은 그에 해당하는 yield 표현식문에서 괄호를 생략할 수 있게 해줍니다. 예를 들어 yield문

yield <expr>
yield from <expr>

은 yield 표현식문

(yield <expr>)
(yield from <expr>)

과 같아요. yield 표현식과 yield문은 제너레이터 함수를 정의할 때만, 그리고 제너레이터 함수 본문에서만 사용돼요. 함수 정의에서 yield를 쓰는 것만으로도 그 정의는 일반 함수 대신 제너레이터 함수를 만들게 됩니다. yield의 의미에 대한 자세한 내용은 'Yield 표현식' 절을 참고하세요.

7.8. raise 문

raise_stmt: "raise" [expression ["from" expression]]

표현식이 없으면 raise는 현재 처리 중인 예외(활성 예외라고도 해요)를 다시 발생시켜요. 현재 활성 예외가 없다면, 오류임을 나타내는 RuntimeError 예외가 발생합니다. 그 외에는 raise가 첫 번째 표현식을 예외 객체로 평가해요. 그것은 BaseException의 서브클래스나 인스턴스여야 해요. 클래스라면, 필요할 때 인자 없이 인스턴스화해서 예외 인스턴스를 얻습니다. 예외의 타입은 예외 인스턴스의 클래스이고, 값은 인스턴스 자체예요.

트레이스백 객체는 보통 예외가 발생할 때 자동으로 만들어져서 __traceback__ 애트리뷰트로 붙어요. 예외를 만들고 한 단계에서 자신만의 트레이스백을 설정하고 싶다면 with_traceback() 예외 메서드(같은 예외 인스턴스를 반환하고, 트레이스백을 인자로 설정해요)를 쓸 수 있어요. 이렇게요.

raise Exception("foo occurred").with_traceback(tracebackobj)

from 절은 예외 체이닝에 쓰여요. 주어지면 두 번째 표현식은 또 다른 예외 클래스나 인스턴스여야 해요. 두 번째 표현식이 예외 인스턴스라면 발생한 예외의 __cause__ 애트리뷰트(쓰기 가능해요)로 붙어요. 예외 클래스라면 클래스를 인스턴스화하고 그 결과 인스턴스가 발생한 예외의 __cause__ 애트리뷰트로 붙게 됩니다. 발생한 예외가 처리되지 않으면 두 예외가 모두 출력돼요.

>>> try:
...     print(1 / 0)
... except Exception as exc:
...     raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    print(1 / 0)
          ~~^~~
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError("Something bad happened") from exc
RuntimeError: Something bad happened

이미 예외가 처리되고 있을 때 새 예외가 발생하면 비슷한 메커니즘이 암묵적으로 동작해요. 예외는 except나 finally 절, 또는 with문을 사용할 때 처리될 수 있어요. 그러면 이전 예외가 새 예외의 __context__ 애트리뷰트로 붙어요.

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    print(1 / 0)
          ~~^~~
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError("Something bad happened")
RuntimeError: Something bad happened

from 절에 None을 지정하면 예외 체이닝을 명시적으로 억제할 수 있어요.

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened") from None
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

예외에 대한 추가 정보는 '예외(Exceptions)' 절에서, 예외 처리는 'try 문' 절에서 확인할 수 있어요.

버전 변경 사항:

  • 버전 3.3 변경: 이제 raise X from Y에서 Y에 None이 허용돼요. 예외 컨텍스트의 자동 표시를 억제하는 __suppress_context__ 애트리뷰트가 추가됐어요.
  • 버전 3.11 변경: except 절에서 활성 예외의 트레이스백이 수정되면, 이후의 raise문은 수정된 트레이스백으로 예외를 다시 발생시켜요. 이전에는 예외가 잡힐 당시의 트레이스백으로 다시 발생했어요.

7.9. break 문

break_stmt: "break"

break는 문법적으로 for나 while 반복문 안에 중첩된 곳에서만 나타날 수 있고, 그 반복문 안의 함수나 클래스 정의에는 중첩될 수 없어요. break는 가장 가까운 바깥쪽 반복문을 종료하고, 반복문에 else 절이 있다면 그것을 건너뜁니다. for 반복문이 break로 종료되면 반복 제어 대상은 현재 값을 유지해요. break가 finally 절이 있는 try문 밖으로 제어를 넘기면, 그 finally 절은 반복문을 실제로 떠나기 전에 실행됩니다.

7.10. continue 문

continue_stmt: "continue"

continue는 문법적으로 for나 while 반복문 안에 중첩된 곳에서만 나타날 수 있고, 그 반복문 안의 함수나 클래스 정의에는 중첩될 수 없어요. 가장 가까운 바깥쪽 반복문의 다음 순환으로 넘어갑니다. continue가 finally 절이 있는 try문 밖으로 제어를 넘기면, 그 finally 절은 다음 반복 순환을 실제로 시작하기 전에 실행됩니다.

7.11. import 문

import_stmt:     "import" module ["as" identifier] ("," module ["as" identifier])*
                 | "from" relative_module "import" identifier ["as" identifier]
                 ("," identifier ["as" identifier])*
                 | "from" relative_module "import" "(" identifier ["as" identifier]
                 ("," identifier ["as" identifier])* [","] ")"
                 | "from" relative_module "import" "*"
module:          (identifier ".")* identifier
relative_module: "."* module | "."+

기본 import문(from 절 없음)은 두 단계로 실행돼요.

  1. 모듈을 찾고, 필요하면 로드하고 초기화해요.
  2. import문이 있는 스코프의 현재 네임스페이스에 대입문이 그러하듯(global/nonlocal 의미를 포함해서) 이름을 정의해요.

문장에 콤마로 구분된 여러 절이 있으면, 두 단계가 각 절마다 따로 수행돼요. 마치 각 절을 개별 import문으로 나눈 것처럼요. 첫 단계의 세부사항, 즉 모듈을 찾고 로드하는 방법은 'import 시스템' 절에 자세히 설명돼 있고, import할 수 있는 다양한 패키지·모듈 종류와 import 시스템을 커스터마이즈하는 데 쓰는 모든 훅도 거기서 다뤄요. 이 단계에서 실패하면 모듈을 찾을 수 없거나, 모듈 코드 실행을 포함한 초기화 중 오류가 발생했다는 뜻일 수 있어요.

요청한 모듈을 성공적으로 가져오면 다음 세 가지 방법 중 하나로 로컬 네임스페이스에서 쓸 수 있게 돼요.

  • 모듈 이름 뒤에 as가 오면, as 뒤의 이름이 import된 모듈에 직접 바인딩돼요.
  • 다른 이름을 지정하지 않고 import하는 모듈이 최상위 모듈이라면, 모듈의 이름이 로컬 네임스페이스에서 import된 모듈에 대한 참조로 바인딩돼요.
  • import하는 모듈이 최상위 모듈이 아니라면, 그 모듈을 담고 있는 최상위 패키지의 이름이 로컬 네임스페이스에서 최상위 패키지에 대한 참조로 바인딩돼요. import된 모듈은 직접 접근하지 말고 완전한 정규화된 이름으로 접근해야 해요.

from 형태는 조금 더 복잡한 과정을 거쳐요.

  • from 절에 지정된 모듈을 찾고, 필요하면 로드하고 초기화해요.
  • import 절에 지정된 각 식별자에 대해: import된 모듈에 그 이름의 애트리뷰트가 있는지 확인해요. 없으면 그 이름의 서브모듈을 import하려 시도하고, import된 모듈을 다시 확인해요. 그래도 애트리뷰트를 찾지 못하면 ImportError가 발생해요. 찾으면 그 값에 대한 참조를 현재 네임스페이스에 저장하는데, as 절이 있으면 그 이름을, 없으면 애트리뷰트 이름을 사용해요.

예를 들면 이렇게요.

import foo                 # foo imported and bound locally
import foo.bar.baz         # foo, foo.bar, and foo.bar.baz imported, foo bound locally
import foo.bar.baz as fbb  # foo, foo.bar, and foo.bar.baz imported, foo.bar.baz bound as fbb
from foo.bar import baz    # foo, foo.bar, and foo.bar.baz imported, foo.bar.baz bound as baz
from foo import attr       # foo imported and foo.attr bound as attr

식별자 리스트가 별표(*)로 대체되면, 모듈에 정의된 모든 공개 이름이 import문이 있는 스코프의 로컬 네임스페이스에 바인딩돼요. 모듈이 정의하는 공개 이름은 모듈의 네임스페이스에서 __all__이라는 변수를 찾아 결정돼요. 정의되어 있다면, 그 모듈이 정의하거나 import한 이름들의 문자열 시퀀스여야 합니다. 비-ASCII 문자를 포함한 이름은 NFKC 정규화 형태여야 해요(자세한 내용은 '이름의 비-ASCII 문자' 참고). __all__에 주어진 이름들은 모두 공개로 간주되며 존재해야 해요. __all__이 정의되어 있지 않으면, 공개 이름 집합에는 모듈 네임스페이스에서 밑줄 문자(_)로 시작하지 않는 모든 이름이 포함됩니다. __all__은 전체 공개 API를 담아야 해요. API에 속하지 않는 항목(모듈 안에서 import되어 사용된 라이브러리 모듈 등)을 실수로 내보내는 것을 막기 위한 것이죠.

import의 와일드카드 형태 — from module import * — 는 모듈 레벨에서만 허용돼요. 클래스나 함수 정의에서 쓰려 하면 SyntaxError가 발생합니다.

import할 모듈을 지정할 때 모듈의 절대 이름을 지정할 필요는 없어요. 모듈이나 패키지가 다른 패키지 안에 들어 있을 때, 패키지 이름을 언급하지 않고 같은 최상위 패키지 안에서 상대 import를 할 수 있습니다. from 뒤에 지정한 모듈이나 패키지에서 앞에 오는 점을 사용해서 정확한 이름을 지정하지 않아도 현재 패키지 계층을 얼마나 올라갈지 지정할 수 있어요. 점 하나는 import를 하는 모듈이 존재하는 현재 패키지를 뜻하고, 점 두 개는 한 패키지 레벨 위, 점 세 개는 두 레벨 위를 뜻해요. 그래서 pkg 패키지의 모듈에서 from . import mod를 실행하면 pkg.mod를 import하게 돼요. pkg.subpkg1 안에서 from ..subpkg2 import mod를 실행하면 pkg.subpkg2.mod를 import합니다. 상대 import의 명세는 '패키지 상대 import' 절에 들어 있어요.

importlib.import_module()은 동적으로 로드할 모듈을 결정하는 애플리케이션을 지원하기 위해 제공돼요. import 감사 이벤트를 인자 module, filename, sys.path, sys.meta_path, sys.path_hooks와 함께 발생시킵니다.

7.11.1. future 문(Future statements)

future 문은 특정 모듈을, 그 기능이 표준이 되는 향후 파이썬 릴리스에서 사용할 수 있게 되는 문법이나 의미를 사용해 컴파일하라는 컴파일러 지시어예요. future 문은 언어에 호환되지 않는 변경을 도입하는 향후 파이썬 버전으로의 마이그레이션을 쉽게 하기 위한 거예요. 기능이 표준이 되는 릴리스 전에 모듈 단위로 새 기능을 쓸 수 있게 해줍니다.

future_stmt: "from" "__future__" "import" feature ["as" identifier]
             ("," feature ["as" identifier])*
             | "from" "__future__" "import" "(" feature ["as" identifier]
             ("," feature ["as" identifier])* [","] ")"
feature:     identifier

future 문은 모듈의 위쪽에 가까이 있어야 해요. future 문 앞에 올 수 있는 줄은 모듈 독스트링(있다면), 주석, 빈 줄, 그리고 다른 future 문뿐입니다. future 문을 써야 하는 유일한 기능은 애노테이션이에요(PEP 563 참고). future 문으로 활성화됐던 모든 역사적 기능은 여전히 파이썬 3에서 인식됩니다. 그 목록에는 absolute_import, division, generators, generator_stop, unicode_literals, print_function, nested_scopes, with_statement가 있어요. 이것들은 모두 항상 활성화되어 있어서 중복되며, 하위 호환성을 위해서만 유지됩니다.

future 문은 컴파일 시점에 특별히 인식되고 처리돼요. 핵심 구조물의 의미 변경은 종종 다른 코드를 생성해서 구현되요. 새 기능이 새 호환되지 않는 문법(새 예약어 같은)을 도입하는 경우도 있어서, 컴파일러가 모듈을 다르게 파싱해야 할 수도 있어요. 이런 결정은 런타임까지 미룰 수 없죠. 어떤 릴리스든 컴파일러는 어떤 기능 이름이 정의됐는지 알고 있고, future 문에 자신이 모르는 기능이 있으면 컴파일 시점 오류를 일으켜요.

직접적인 런타임 의미는 다른 import문과 같아요. 나중에 설명할 표준 모듈 __future__가 있고, future 문이 실행될 때 보통의 방식으로 import됩니다. 흥미로운 런타임 의미는 future 문이 활성화하는 특정 기능에 따라 달라져요.

이 문장에는 특별한 게 없다는 점을 기억하세요.

import __future__ [as name]

이건 future 문이 아니에요. 특별한 의미나 문법 제한이 없는 평범한 import문입니다. future 문을 포함한 모듈 M에서 발생하는 내장 함수 exec()compile() 호출이 컴파일하는 코드는, 기본적으로 그 future 문과 연관된 새 문법이나 의미를 사용해요. 이건 compile()의 선택적 인자로 제어할 수 있어요 — 자세한 내용은 그 함수 문서를 보세요.

인터랙티브 프롬프트에 입력된 future 문은 나머지 인터프리터 세션 동안 효력을 가집니다. 인터프리터가 -i 옵션으로 시작되고, 스크립트 이름을 받아 실행하며, 그 스크립트가 future 문을 포함하고 있다면, 그 future 문은 스크립트 실행 후 시작되는 인터랙티브 세션에서 효력을 가져요.

참고: PEP 236 - Back to the __future____future__ 메커니즘의 원래 제안.

7.12. global 문

global_stmt: "global" identifier ("," identifier)*

global 문은 나열된 식별자들을 전역으로 해석하게 해요. global 없이는 전역 변수에 대입하는 것이 불가능해요. 다만 자유 변수는 global로 선언하지 않아도 전역을 참조할 수 있어요. global 문은 현재 전체 스코프(모듈, 함수 본문, 클래스 정의)에 적용됩니다. 변수가 그 스코프에서 global 선언 전에 사용되거나 대입되면 SyntaxError가 발생해요.

모듈 레벨에서는 모든 변수가 전역이라 global 문은 효과가 없어요. 그래도 변수는 여전히 global 선언 전에 사용되거나 대입되면 안 됩니다. 이 요구사항은 인터랙티브 프롬프트(REPL)에서는 완화돼요.

프로그래머 참고: global은 파서에 대한 지시어예요. global 문과 동시에 파싱된 코드에만 적용됩니다. 특히 내장 exec() 함수에 제공된 문자열이나 코드 객체에 들어 있는 global 문은 그 함수 호출을 담은 코드 블록에 영향을 주지 않고, 그런 문자열 안의 코드도 함수 호출을 담은 코드의 global 문에 영향받지 않아요. eval()compile() 함수에도 마찬가지가 적용됩니다.

7.13. nonlocal 문

nonlocal_stmt: "nonlocal" identifier ("," identifier)*

함수나 클래스의 정의가 다른 함수의 정의 안에 중첩(둘러싸여)되어 있으면, 그 nonlocal 스코프는 바깥쪽 함수들의 로컬 스코프예요. nonlocal 문은 나열된 식별자들이 nonlocal 스코프에서 이전에 바인딩된 이름을 참조하게 해요. 캡슐화된 코드가 그런 nonlocal 식별자를 다시 바인딩할 수 있게 해줍니다. 이름이 하나 이상의 nonlocal 스코프에 바인딩되어 있으면 가장 가까운 바인딩을 사용해요. 어떤 nonlocal 스코프에도 바인딩되지 않았거나 nonlocal 스코프가 없으면 SyntaxError가 발생합니다.

nonlocal 문은 함수나 클래스 본문의 전체 스코프에 적용돼요. 변수가 그 스코프에서 nonlocal 선언 전에 사용되거나 대입되면 SyntaxError가 발생합니다.

참고: PEP 3104 - Access to Names in Outer Scopes — nonlocal 문의 명세.

프로그래머 참고: nonlocal은 파서에 대한 지시어이고 함께 파싱된 코드에만 적용돼요. global 문의 참고 사항을 보세요.

7.14. type 문

type_stmt: 'type' identifier [type_params] "=" expression

type 문은 타입 별칭을 선언하는데, 이것은 typing.TypeAliasType의 인스턴스예요. 예를 들어 다음 문장은 타입 별칭을 만들어요.

type Point = tuple[float, float]

이 코드는 대략 이렇게 동등해요.

annotation-def VALUE_OF_Point():
    return tuple[float, float]
Point = typing.TypeAliasType("Point", VALUE_OF_Point())

annotation-def는 애노테이션 스코프를 나타내는데, 대부분 함수처럼 동작하지만 몇 가지 작은 차이가 있어요. 타입 별칭의 값은 애노테이션 스코프에서 평가됩니다. 타입 별칭을 만들 때가 아니라, 타입 별칭의 __value__ 애트리뷰트로 값에 접근할 때만 평가돼요(지연 평가 참고). 그래서 타입 별칭이 아직 정의되지 않은 이름을 참조할 수 있어요. 이름 뒤에 타입 매개변수 리스트를 추가해서 타입 별칭을 제네릭으로 만들 수도 있어요(자세한 내용은 '제네릭 타입 별칭' 참고).

type은 소프트 키워드예요.

버전 3.12에 추가.

참고: PEP 695 - Type Parameter Syntax — type 문과 제네릭 클래스·함수를 위한 문법을 도입.

더 알아보기