기본 지식.

Python typing이 무엇인지 모르시는 분들은 이 글을 읽어보시는 걸 추천합니다.

빙글빙글.

때는 신나게 Slack bot을 개발하던 시점이었습니다.1 복잡도가 상승하면서 별도 클래스나 모듈로 분리하는 요소가 많아지기 시작했죠. 이로 인해 typing도 점점 복잡해지기 시작했습니다. 대부분은 alias로 해결할 수 있지만, 순환 참조가 발생해버리니 머리가 아파지기 시작했습니다.

상황 1. 같은 모듈 내에서의 순환참조.

문제 설명.

from typing import List


class Teacher:

    students: List[Student]


class Student:

    teachers: List[Teacher]

실행하면 다음과 같은 상태가 됩니다.

Traceback (most recent call last):
  File "typ.py", line 4, in <module>
    class Teacher:
  File "typ.py", line 6, in Teacher
    students: List[Student]
NameError: name 'Student' is not defined

지금 저 코드 상태로는 정의 순서를 바꾼다고 해서 이 문제가 해결되진 않습니다.

해결책.

이럴땐 이렇게 해야합니다.

from typing import List


class Teacher:

    students: List['Student']


class Student:

    teachers: List[Teacher]

먼저 나온 Student를 문자열로 이름만 넣었습니다. 하지만 mypy는 이 정도만 해도 해당하는 이름을 찾아서 처리해줍니다.

상황 2. 다른 모듈 내에서의 순환참조.

상황 1은 간단히 해결할 수 있습니다. 하지만 서로 다른 모듈에 있는 상태라면 어떨까요?

문제 설명.

# mod1.py
from typing import List

from mod2 import Student


class School:

    students: List[Student]
# mod2.py
from mod1 import School


class Student:

    def register_school(school: School):
        pass

mod2.py에서 from mod1 import School을 해버리는 경우 다음과 같은 상황이 벌어집니다.

Traceback (most recent call last):
  File "mod1.py", line 3, in <module>
    from mod2 import Student
  File "/Users/item4/mod2.py", line 1, in <module>
    from mod1 import School
  File "/Users/item4/mod1.py", line 3, in <module>
    from mod2 import Student
ImportError: cannot import name 'Student'

해결책.

이런 경우엔 이렇게 해야 합니다.

# mod2.py
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from mod1 import School


class Student:

    def register_school(school: 'School'):
        pass

typing 모듈의 TYPE_CHECKING 상수는 runtime에는 False 값을 갖고 있습니다. mypy 등의 정적 타입 검사기를 돌릴 때만 True가 됩니다. 이렇게 하면 순환 참조 문제를 해결할 수 있습니다.

각주.

  1. YUI 라고 불리는 프로젝트인데 이건 기회가 올 때마다 하나씩 소개할 생각입니다. 원래 위치로