HOWTO · Python

Python에서 알려진 상위 패키지 없이 시도된 상대 가져오기 해결

이 튜토리얼은 Python에서 ImportError를 해결하고 상대 가져오기를 처리하는 방법을 설명합니다.

이 페이지의 내용

ImportError: attempted relative import with no known parent package는 Python이 점으로 시작하는 가져오기에 필요한 패키지 문맥 없이 모듈을 실행하고 있다는 뜻입니다. 보통은 해당 모듈을 패키지의 일부로 실행하거나 프로젝트 구조에 맞는 절대 가져오기를 사용하면 해결할 수 있습니다. __init__.py를 추가하는 것만으로는 파일을 직접 실행할 때 부모 패키지가 생기지 않습니다.

Python에서 이 ImportError가 발생하는 이유

from .helpers import format_name과 같은 상대 가져오기는 현재 모듈의 패키지를 기준으로 합니다. 모듈을 패키지의 일부로 가져오면 Python은 그 패키지를 알 수 있지만, 파일을 직접 실행하면 모듈 이름은 __main__이 되며 일반적으로 패키지 이름이 없습니다. 따라서 패키지 디렉터리와 가져올 파일이 모두 있어도 python path/to/module.py를 실행하면 실패할 수 있습니다.

가져오기 구문 자체도 올바른 from 구문이어야 합니다. import .module_name은 유효한 Python 구문이 아닙니다. from . import module_name 또는 from .module_name import function_name처럼 작성하세요. 앞의 점은 “이 패키지를 기준으로 상대 경로를 지정한다”는 뜻이지, “이 파일을 어떻게 실행했든 같은 디렉터리에서 찾는다”는 뜻이 아닙니다.

모듈을 패키지 문맥에서 실행하기

myPackage라는 패키지를 예로 들면, 파일을 하나의 프로젝트 디렉터리 아래에 둡니다. IError/myPackage/__init__.py, IError/myPackage/myNewPackage.py, IError/myPackage/main.py가 그 예입니다. main.py에서는 from .myNewPackage import createSingleDict처럼 상대 가져오기를 작성할 수 있습니다. myPackage가 들어 있는 디렉터리인 IError에서 python -m myPackage.main을 실행하세요. -m 옵션은 Python이 모듈을 패키지의 일부로 찾아 실행하게 하므로 상대 가져오기에 알려진 부모 패키지가 생깁니다.

상대 가져오기에 의존하는 경우 myPackage/main.py를 파일 경로로 직접 실행하지 마세요. __init__.py도 직접 실행하지 마세요. 이 파일은 Python이 패키지를 가져올 때 패키지를 초기화하는 파일이며, 패키지의 진입점이 아닙니다.

프로젝트에 적합하다면 절대 가져오기 사용하기

절대 가져오기는 가져오기 검색 경로를 기준으로 모듈을 지정합니다. 예를 들면 from myPackage.myNewPackage import createSingleDict와 같습니다. 프로젝트에서 이미 패키지 루트 기준 가져오기를 사용하고, 패키지 루트가 sys.path에서 검색 가능하다면 좋은 선택이 될 수 있습니다. 프로젝트 루트에서 실행하거나 환경에 패키지를 설치한 경우가 이에 해당합니다.

애플리케이션과 실행 방식에 맞는 가져오기 방식을 선택하세요. 절대 가져오기로 바꿔도 Python이 찾을 수 없는 패키지 이름 문제가 해결되지는 않습니다. 지정한 패키지를 현재 환경에서 가져올 수 있는지 확인하세요.

패키지 구조와 작업 디렉터리 확인하기

가져오기에서 지정한 디렉터리가 의도한 패키지인지, 모듈 파일 이름이 가져오기 구문과 일치하는지, 예상한 프로젝트 루트에서 Python을 실행하는지 확인하세요. 앞의 예에서는 작업 디렉터리가 IError/myPackage가 아니라 IError이며, 실행 명령은 python -m myPackage.main입니다. 패키지가 중첩되어 있다면 project가 있는 디렉터리에서 python -m project.tools.runner처럼 모듈 경로에 패키지 구성 요소를 모두 포함하세요.

일반 패키지에는 대개 __init__.py가 포함되지만, 적절한 구조에서는 Python의 네임스페이스 패키지도 사용할 수 있습니다. 핵심적으로 확인할 것은 모듈의 실행 방식입니다. 파일이 패키지 디렉터리 안에 있더라도 직접 실행하면 패키지 문맥이 없을 수 있습니다. 터미널, IDE, 테스트 실행기에서 패키지 가져오기 결과가 다르다면 활성 인터프리터와 환경도 확인하세요.

최소 예제로 수정 사항 확인하기

이 독립 실행형 예제는 임시 디렉터리에 작은 패키지를 만든 다음, 진입 모듈을 python -m으로 실행합니다. Python으로 예제를 실행하면 가져온 함수가 반환한 값이 출력됩니다.

from pathlib import Path
import subprocess
import sys
import tempfile

with tempfile.TemporaryDirectory() as temporary_directory:
    project = Path(temporary_directory)
    package = project / "myPackage"
    package.mkdir()
    (package / "__init__.py").write_text("", encoding="utf-8")
    (package / "myNewPackage.py").write_text(
        'def createSingleDict(name, value):\n'
        '    return {"name": name, "value": value}\n',
        encoding="utf-8",
    )
    (package / "main.py").write_text(
        "from .myNewPackage import createSingleDict\n"
        'print(createSingleDict("Jacob", 25))\n',
        encoding="utf-8",
    )
    result = subprocess.run(
        [sys.executable, "-m", "myPackage.main"],
        cwd=project,
        check=True,
        capture_output=True,
        text=True,
    )
    print(result.stdout, end="")

명령의 작업 디렉터리는 임시 프로젝트 루트이므로 Python이 myPackage를 찾고 main을 패키지 모듈로 불러올 수 있습니다. 이 방식을 응용할 때는 -m 뒤의 모듈 경로가 Python을 실행하는 디렉터리 아래의 폴더 구조와 일치하도록 하세요.

필요한 경우 init.py에서 하위 모듈 이름 내보내기

패키지 수준에서 이름을 내보내는 것은 선택적인 편의 기능입니다. 호출자가 myPackage.createSingleDict처럼 선택한 이름에 접근하게 하려면 __init__.py에 from .myNewPackage import createSingleDict를 작성하세요. 그러면 myPackage를 가져올 때 해당 함수도 사용할 수 있습니다. 모듈이 여러 개라면 __init__.py에서 각 모듈의 이름을 선택해 가져올 수 있습니다. 예를 들어 from .otherPackage import printName과 같이 작성합니다.

이러한 내보내기는 패키지 이름 공간에서 어떤 이름을 편리하게 사용할 수 있는지 정합니다. 파일을 직접 실행할 때 패키지 문맥을 제공하지는 않습니다. 그 문제는 패키지로 실행하거나 적절한 절대 가져오기를 사용해 해결하세요.