[Python] 모듈 상대경로(ImportError: attempted relative import with no known parent package)
파이썬에서 모듈을 만들어서 import 하다보면
ImportError: attempted relative import with no known parent package
위 에러를 많이 만나게 된다.
패키지 안에서 스크립트 파일(실행 파일)을 실행 시키려고 했을 때 발생하는 에러이다.
일단 이유는 파이썬의 인터프리터가 __main__ 의 위치를 알지 못하기 때문에 위 같은 에러가 발생하는 것이다.
파이썬의 인터프리터는 relative import의 모듈 위치를 정할 때(기준이 되는 위치) __name__ 속성에 의해 결정되고
터미널에서 파이썬을 직접 실행시키면 __name__ == '__main__'이 되기 때문이다.
이 부분이 이해가 잘 안된다면,
먼저 모듈과 패키지의 차이에 대해서 이해해야 한다.
모듈과 패키지는 둘다 import를 불러와 쓰는데, 패키지는 모듈을 포함시켜놓은 것이다.
모듈(Module) : 각종 변수, 함수, 클래스를 담고 있는 파일로 특정 기능을 .py 파일 단위로 작성한 것
패키지(Package) : 여러 모듈을 묶은 것이며 패키지는 모듈에 네임스페이스(namespace)를 제공함
파이썬 3.3 이하에서는 폴더가 패키지임을 명시하기 위해 __init__.py 파일을 폴더 안에 생성해야 했다.
3.3이상에서는 __init__.py 가 없어도 패키지임을 파이썬이 인식하지만 되도록 작성해주는 것이 좋다.
패키지안의 모듈을 불러오는 방법은
import pacakge.module
import package.module1, package.module2
package.module.변수
package.module.함수()
pacakge.module.클래스()
가 있다.
다음과 같은 구조가 있다고 할 때 main.py에서 utils 패키지의 utils 모듈(utils.py)를 가져와서 사용한다고 해보자.
__init__.py에 아무것도 쓰지 않았을 때 utils 모듈을 가져오려면 from utils import utils 처럼 패키지와 모듈을 모두 명시해주어야 한다.
# utils/__init__.py
from . import utils
__init__.py에 위와 같이 명시해주면 import utils만 불러와주고 utils.py안에 hello()라는 함수가 있다고 할 때 해당 함수를 바로 사용할 수 있게 된다. 패키지를 불러올 떄 (import utils) __init__.py 파일이 자동으로 실행되므로 from . 을 통해 현재 패키지에서 모듈을 가져오라고 인식할 수 있게 되는 것이다.
import utils
print(utils.utils.hello()) # utils 패키지의 utils 모듈(utils.py) 안의 hello() 함수 사용
이때 utils.py의 __name__ 변수에는 'utils'가 들어가 있고 utils 모듈을 불러오는 main.py의 __name__에는 '__main__'이 들어가 있게 된다. 파이썬의 경우 import 할 때 import 하는 모듈을 모두 실행하기 때문에 원하지 않는 코드가 실행될 수 있는 경우가 있다.
따라서 시작점(entry point)가 어떤 파일인지 명확히하기 위해 if __name__ == '__main__'을 통해 스크립트 파일을 실행시키는 것이다. 즉 if __name__ == '__main__' 는 현재 스크립트 파일이 프로그램의 시작점이 맞는지 판단하는 역할을 한다.
해결 방안
에러가 발생했을 것 같은 상황은 패키지 안에서 스크립트 파일(실행파일)을 실행했을 때이다.
예를 들어 pacakge/calculation.py에서 package/package2/add.py안의 add 함수를 불러왔을 때 entry point를 찾지 못한다.
# package/calcuation.py
from .package2.add import my_add
result = my_add(1,2)
print(result) # ImportError
1. python의 -m 옵션 이용
-m 옵션은 모듈을 스크립트로 수행할 때 쓰는 옵션인데 이를 통해 패키지 구조를 알려주면 파이썬이 상대경로를 찾을 수 있게 된다.
~/test/ > python3 -m package.calcuation
2. python의 절대경로 이용
# package/calcuation.py
if __name__ == '__main__':
if __package__ is None:
import sys
from os import path
print(path.dirname( path.dirname( path.abspath(__file__) ) ))
sys.path.append(path.dirname( path.dirname( path.abspath(__file__) ) ))
from package.package2 import add
else:
from .packge2 import add
result = my_add(1,2)
print(result)
코드를 위와 같이 작성하고 pacage/caculation.py 파일을 실행하면 된다.
References
https://myjorney.tistory.com/52
https://dojang.io/mod/page/view.php?id=2448