Python 상대 임포트 에러 ‘attempted relative import with no known parent package’ 완전 해결

1. 문제 정의

개발을 시작한 지 얼마 안 된 Python 개발자라면 아래 에러를 한 번쯤 마주친다.

1
ImportError: attempted relative import with no known parent package

대표적인 상황은 다음과 같은 패키지 구조에서 moduleX.py를 직접 실행하는 경우다.

1
2
3
4
5
6
7
repro/                       <- 패키지 루트 (작업 디렉터리)
├── package/
│   ├── __init__.py
│   ├── moduleA.py
│   └── subpackage1/
│       ├── __init__.py
│       └── moduleX.py

moduleX.py 안에서 상대 임포트를 사용했다. moduleA.py는 상위 패키지 package/에 있으므로 from .. import moduleA가 올바른 상대 경로다.

1
2
# package/subpackage1/moduleX.py
from .. import moduleA   # 상위 패키지의 moduleA를 상대 임포트

그리고 터미널에서 repro/ 루트에 있으면서 이 파일을 경로로 직접 실행했다.

1
2
cd repro
python3 package/subpackage1/moduleX.py

결과는 위의 ImportError: attempted relative import with no known parent package다.

이 에러는 Python 버전을 가리지 않고 상대 임포트가 도입된 이래 계속 반복해서 등장하는 질문이며, StackOverflow에서도 약 78만 회 이상 조회된 유명한 이슈다(출처: 하단 링크, view_count 785,190).

2. 표면 증상

  • 에러 메시지가 ImportError: attempted relative import with no known parent package로 표시됨
  • 상대 임포트(from .. import x)를 쓴 줄에서 발생
  • 같은 파일을 다른 파일에서 import하면 동작하는데, 직접 python 파일명.py로 실행하면 터지는 경우가 많음
  • 프로젝트를 통째로 돌리면 정상인데 개별 파일만 실행하면 실패
증상관찰 포인트
에러 메시지attempted relative import with no known parent package
발생 단계상대 임포트 문장이 실행되는 순간 (런타임)
관련 프레임워크Python 표준 임포트 시스템 (PEP 328)
전형적 오해“파일 경로가 틀렸다”, “import 문법이 틀렸다"로 착각
핵심 판단 기준파일을 python 파일경로.py로 실행했는가, python -m 패키지.모듈로 실행했는가

3. 원인 추적 — 스크립트 실행 vs 모듈 임포트

이 에러의 본질은 파일을 어떻게 로드하느냐에 있다. Python은 파일을 두 가지 방식으로 로드한다.

  1. 최상위 스크립트(top-level script) — python package/subpackage1/moduleX.py처럼 명령줄에서 직접 실행하는 방식
  2. 모듈(module) — 다른 파일의 import 문에 의해 로드되는 방식

핵심은 이것이다. 파일이 어느 디렉터리에 있는지는, Python이 그 파일을 어떤 ‘패키지’에 속한다고 보는지를 결정하지 않는다. 어떤 패키지에 속하는지는 파일을 실행했는지 임포트했는지에 따라 추가로 결정된다.

파일이 로드될 때 그 파일에는 __name__ 속성에 이름이 부여된다.

  • 최상위 스크립트로 실행되면 → __name__이 __main__
  • 모듈로 임포트되면 → __name__이 파일명 앞에 패키지 이름이 점(.)으로 붙은 값

실제 실행으로 확인한 차이

본 편집자가 위 구조를 Python 3.10.12와 3.11.15에서 실제로 재현해 두 로드 방식이 __name__/__package__를 어떻게 세팅하는지 측정했다(도구: Reproducible: 재현 디렉터리 + 터미널 실행).

python -c로 import한 뒤의 실제 출력은 다음과 같다.

1
2
$ python3 -c "import package.subpackage1.moduleX as m; print(m.__name__, '|', m.__package__, '|', m.moduleA.VALUE_A)"
package.subpackage1.moduleX | package.subpackage1 | moduleA-value

즉 import로 로드하면 __name__은 package.subpackage1.moduleX, __package__는 package.subpackage1이 되어 상대 임포트의 기준(parent package)이 살아 있다.

반면 명령줄에서 moduleX.py를 경로로 직접 실행하면 __name__은 __main__, __package__는 None이 된다 (재현 결과는 아래 4절·5절 참고).

상대 임포트(from .. import ...)는 현재 모듈의 __package__ 값을 기준으로 움직인다. 그런데 최상위 스크립트로 실행된 __main__에는 부모 패키지(__package__)가 존재하지 않는다. 따라서 from .. import ...의 ..이 가리킬 부모 패키지가 없어서 “no known parent package"라는 에러가 나는 것이다.

즉 상대 임포트 자체가 틀린 것이 아니라, 상대 임포트가 유효하려면 해당 파일이 모듈로(패키지의 일부로) 로드되어야 하는데, 스크립트로 직접 실행했기 때문에 실패한다.

4. 근본 원인과 실제 재현 결과

에러의 근본 원인은 다음 한 문장으로 압축된다.

파일을 ‘모듈’이 아닌 ‘최상위 스크립트’로 직접 실행했기 때문에, 상대 임포트가 참조할 부모 패키지(__package__)가 설정되지 않았다.

재현에 사용한 검증 환경

항목값
Python 13.10.12 (/usr/bin/python3.10)
Python 23.11.15 (/usr/bin/python3.11)
OSUbuntu (Linux, 커널 6.8.0)
아키텍처arm64 / aarch64
실행 워크스페이스 구조repro/package/__init__.py, repro/package/moduleA.py, repro/package/subpackage1/__init__.py, repro/package/subpackage1/moduleX.py
실행 시 작업 디렉터리repro/ (패키지 루트)

원인 후보 매트릭스는 다음과 같다.

원인 후보확인 방법일치 시 증상해결 방향
상대 임포트 문법 오류사용 중인 구문이 유효한지 확인문법 자체가 해석 불가문법 수정 (일반적으로 아님)
파일을 스크립트로 직접 실행python 파일경로.py로 실행했는지 확인__name__ == '__main__', __package__ 없음python -m package.module로 실행
실행 경로가 패키지 루트가 아님어느 디렉터리에서 실행했는지 확인모듈을 패키지로 인식 못함패키지 루트에서 -m으로 실행
상대 임포트가 불필요한 설계구조를 재검토다른 해결책으로 제거 가능절대 임포트로 변경하거나 구조 개선

이 중 “파일을 스크립트로 직접 실행” 이 이번 재현에서 관찰된 실제 원인이다. 위 구조에서 명령을 실행한 실제 출력은 다음과 같다.

1
2
3
4
5
6
$ python3 package/subpackage1/moduleX.py          # (a) 스크립트로 직접 실행 → 실패
Traceback (most recent call last):
  File ".../repro/package/subpackage1/moduleX.py", line 2, in <module>
    from .. import moduleA   # relative import: parent package's moduleA
ImportError: attempted relative import with no known parent package
exit=1

같은 파일을 python -m으로 모듈처럼 실행하면 정상 동작한다.

1
2
3
4
5
6
$ python3 -m package.subpackage1.moduleX          # (b) 모듈로 실행 → 성공
moduleX entry (as script/module top-level)
__name__ = __main__
__package__ = package.subpackage1
moduleA.VALUE_A = moduleA-value (imported OK)
exit=0

python -m으로 실행해도 __name__은 여전히 __main__이지만, __package__는 package.subpackage1로 올바르게 세팅되어 상대 임포트가 동작한다. 이는 “에러의 원인은 __main__이라는 이름이 아니라 __package__가 설정되지 않았다는 것"을 실측으로 보여준다.

5. 해결책과 해결책 선택 이유

해결책 A: python -m으로 모듈처럼 실행하기 (권장)

파일을 직접 실행하는 대신, 패키지 루트(repro/)에서 모듈 경로로 실행한다.

1
2
3
4
5
6
# 잘못된 예 — 스크립트로 직접 실행 (실패)
python3 package/subpackage1/moduleX.py

# 올바른 예 — 패키지의 모듈로 실행 (성공)
cd path/to/repro
python3 -m package.subpackage1.moduleX

이렇게 하면 Python이 moduleX를 package.subpackage1.moduleX라는 모듈로 로드하므로 __package__가 올바르게 설정되고, 상대 임포트가 제대로 동작한다(위 4절의 실제 출력 참고).

이 방법을 권장하는 이유: 재현 실측 결과 python -m이 다른 환경(테스트 러너·CI·패키징)과 가장 일관된 __package__ 값을 만들어냈고, 추가로 sys.path나 __package__를 건드릴 필요가 없다(아래 “실패한 해결책” 참고).

만약 moduleX.py가 프로그램의 진입점(엔트리포인트) 이라면, 진입점 역할은 최상위 스크립트 한 파일로 분리하고 나머지는 모듈로 두는 것이 깔끔하다.

1
2
3
4
5
6
7
8
9
project/
├── __init__.py
├── main.py          # 진입점 — from package.subpackage1 import moduleX
└── package/
    ├── __init__.py
    ├── moduleA.py
    └── subpackage1/
        ├── __init__.py
        └── moduleX.py

main.py 안에서 상대 임포트가 아닌 절대 임포트로 진입하는 구조다.

1
2
# main.py
from package.subpackage1 import moduleX

그리고 실행은 진입점인 main.py만 직접 실행한다.

1
python3 main.py

해결책 B: 상대 임포트를 제거하고 구조 변경 (대체)

moduleX.py가 꼭 패키지 밖에서 직접 실행되어야 하는 상황이라면, 상대 임포트를 포기하고 절대 임포트로 바꾸거나 프로젝트 자체를 설치 가능한 패키지(pip install -e .)로 구성하는 방식을 쓸 수 있다. 다만 아래 실패한 해결책에서 보듯 sys.path 조작에 의존하는 방식은 실행 위치에 따라 깨지므로, 가능하면 python -m 방식(해결책 A)을 우선 고려한다.

실패한 해결책 — 실제 시도 결과

일부 해설에서 권장하는 다음 방식들을 직접 시도해 왜 권장하지 않는지 관찰했다.

(가) __package__를 수동으로 강제 설정 — 파일 최상단에 __package__ = "package.subpackage1"을 넣고 실행했다.

1
2
3
4
5
6
$ python3 package/subpackage1/moduleX_pkg.py
Traceback (most recent call last):
  File ".../package/subpackage1/moduleX_pkg.py", line 2, in <module>
    from .. import moduleA
ModuleNotFoundError: No module named 'package'
exit=1

__package__는 “no known parent package” 에러를 피하게 해주지만, 실제로는 package 상위 패키지를 sys.path에서 찾을 수 없어 ModuleNotFoundError: No module named 'package'로 실패한다. 즉 에러 종류만 바뀔 뿐 해결되지 않으며, 프레임워크·테스트·타 도구와의 호환성이 깨질 수 있는 임시방편이라는 점이 실측으로 확인된다.

(나) sys.path에 현재 디렉터리를 추가 — sys.path.insert(0, os.path.abspath(".")) 후 절대 임포트로 바꿨다.

  • 패키지 루트(repro/)에서 실행하면 동작한다.
    1
    2
    3
    
    $ python3 package/subpackage1/moduleX_syspath.py
    moduleA.VALUE_A = moduleA-value
    exit=0
    
  • 하지만 같은 파일을 다른 작업 디렉터리(예: /tmp)에서 실행하면 실패한다.
    1
    2
    3
    4
    5
    6
    
    $ cd /tmp && python3 .../repro/package/subpackage1/moduleX_syspath.py
    Traceback (most recent call last):
      File ".../package/subpackage1/moduleX_syspath.py", line 3, in <module>
        import package.moduleA as m
    ModuleNotFoundError: No module named 'package'
    exit=1
    

os.path.abspath(".")는 실행 시점의 작업 디렉터리를 기준으로 잡으므로, 어느 디렉터리에서 실행하느냐에 따라 결과가 달라진다. 패키지 루트가 어디인지 모호해져 다른 환경에서 재현이 어렵고 CI 등에서 실행 위치가 바뀌면 같은 코드가 실패하는 문제가 실측으로 확인된다.

요약: __package__ 강제 설정은 에러 종류만 바꾸고, sys.path 조작은 실행 위치에 의존해 불안정하다. 둘 다 근본 원인(파일을 스크립트로 직접 실행)을 해결하지 못하므로 권장하지 않는다.

6. 검증 명령 — 실제 실행 결과

해결 후 아래 명령으로 실제로 동작하는지 확인한다. 각 명령의 실제 출력은 다음과 같다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1) 스크립트로 직접 실행하면 여전히 실패하는지 (원인 재확인)
cd repro
python3 package/subpackage1/moduleX.py
# 실제 출력:
# Traceback (most recent call last):
#   File ".../moduleX.py", line 2, in <module>
#     from .. import moduleA
# ImportError: attempted relative import with no known parent package
# exit=1

# 2) 모듈로 실행했을 때 오류가 사라지는지 (해결 검증)
python3 -m package.subpackage1.moduleX
# 실제 출력:
# moduleX entry (as script/module top-level)
# __name__ = __main__
# __package__ = package.subpackage1
# moduleA.VALUE_A = moduleA-value (imported OK)
# exit=0

# 3) import 관점에서 __name__ / __package__ 상태 점검 (디버깅용)
python3 -c "import package.subpackage1.moduleX as m; print(m.__name__, '|', m.__package__, '|', m.moduleA.VALUE_A)"
# 실제 출력:
# package.subpackage1.moduleX | package.subpackage1 | moduleA-value

python -m으로 실행했을 때 더 이상 ImportError: attempted relative import with no known parent package가 뜨지 않고 moduleA까지 정상 호출되면(exit=0) 해결된 것이다. 이는 위 실제 재현 출력이 그대로 보여준다. 파일이 상대 임포트 파일이 아니라 진입점이라면 python3 main.py로 검증한다.

7. 버전별 차이

같은 재현 세트를 Python 3.10.12와 3.11.15에서 각각 실행해 결과를 비교했다.

검증 항목Python 3.10.12Python 3.11.15
python3 package/subpackage1/moduleX.py (스크립트 직접 실행)ImportError: attempted relative import with no known parent package (exit=1)동일 에러 (exit=1) — stdout/stderr가 3.10과 바이트 단위로 완전히 동일
python3 -m package.subpackage1.moduleX (모듈 실행)성공, __package__ = package.subpackage1 (exit=0)동일 (exit=0)
python -c import 후 __package__package.subpackage1package.subpackage1
__package__ 강제 설정ModuleNotFoundError: No module named 'package'ModuleNotFoundError: No module named 'package'

핵심 결론: 이 에러의 근본 동작과 해결책은 Python 3.10과 3.11에서 동일했다. 스크립트 직접 실행 오류의 stdout/stderr는 두 버전 모두 바이트 단위로 완전히 동일했고, 두 버전 모두 이 relative import ImportError에 캐럿 지시줄(^^^^^^)이 붙지 않았다. 에러 의미와 해결 방법에 차이가 없다.

일반적인 점검 기준: 매우 오래된 Python 2.x 기준 해설 중 일부는 “패키지 디렉터리에 __init__.py가 있어야 상대 임포트가 동작한다"고 설명하지만, 위 구조처럼 __init__.py가 갖춰진 상태에서도 파일을 스크립트로 직접 실행하면 이 에러가 발생한다. 즉 이 문제는 __init__.py 부재가 아니라 실행 방식이 핵심이다.

관찰 신뢰성 참고: 3.11(PEP 657)의 캐럿 지시줄이 이 빌드(3.11.15)에서 정상 동작함을 x = undefined_name 같은 NameError 재현으로 별도 확인했지만(해당 예에서는 ^^^^^^ 표시가 출력됨), relative import ImportError에는 두 버전 모두 캐럿이 붙지 않았다. 상대 임포트 오류는 import 계열 메커니즘에서 발생해 bytecode 명령 위치 정보를 부착하는 캐럿의 대상이 아니기 때문이다.

버전별 주의 요약

  • 상대 임포트가 패키지 안에서만 동작한다는 원칙은 Python 2.5(PEP 328)부터 현재까지 유지된다.
  • python -m 실행 방식은 Python 3.10·3.11 모두 동일하게 __package__를 올바르게 설정한다.
  • 특정 버전에 의존하는 우회 코드 대신, python -m 실행 습관과 진입점 분리 구조가 모든 버전에서 가장 안정적이다.

8. 향후 예방 조치

  • 진입점은 최상위 스크립트 하나로 분리하고, 나머지는 모두 패키지의 모듈로 둔다.
  • 패키지 내부 모듈끼리 참조할 때는 실행 방식을 python -m package.module로 통일한다.
  • 상대 임포트와 절대 임포트를 무분별하게 섞지 말고, 프로젝트 컨벤션을 하나로 정한다.
  • CI에서는 테스트 러너(pytest 등)가 모듈 임포트 기준으로 동작하므로, 로컬에서 개별 파일을 python 파일경로.py로 돌리다가 테스트만 통과하고 실서비스에서 깨지는 경우가 없도록 python -m 실행 습관을 들인다.
  • 운영/CI 재발 방지 체크: (1) 진입점 파일과 패키지 모듈 파일을 구분해서 관리, (2) 실행 스크립트에 항상 -m 사용, (3) 어느 디렉터리에서 실행해도 되도록 상대 경로가 아닌 모듈 기반 실행으로 통일.

DevTrace verdict: 이 문제의 핵심은 코드의 문법이나 파일 경로가 아니라, 상대 임포트는 패키지의 모듈로 로드될 때만 동작하는데 파일을 최상위 스크립트(__main__)로 직접 실행했기 때문이라는 점이다. Python 3.10/3.11 재현 실측 모두에서 python -m package.module로 실행하면 __package__가 올바르게 설정되어 즉시 해결된다. __package__ 강제 설정이나 sys.path 조작은 에러를 바꾸거나 실행 위치에 따라 불안정할 뿐 근본 해결이 아니다.


출처: StackOverflow — How do I resolve the error “ImportError: attempted relative import with no known parent package”?