파이썬은 참으로 쉬운 언어이다. 배우지 않아도 개발을 바로 할 수 있을만큼 쉽고 간단하고 강력하다.
그러나 위 에러를 마주하게 된다면 멘붕이 올 것이다.
결론부터 말하면 위 에러를 마주했다는 것은 파이썬의 모듈과 패키지에 대한 이해가 부족한 것이다.
그리고 아마 당신은 패키지를 import했는데 그걸 모듈처럼 사용해서 문제가 생겼을 것 이다.
1. 모듈이란?
파이썬에서 모듈은 .py로 끝나는 파일들 그 자체를 얘기한다.
2. 패키지란?
패키지란 아래처럼 __init__.py 파일을 가지는 폴더 구조를 얘기한다
root/
Package/
__init__.py
module1.py
module2.py
위 폴더에서는 root 폴더 밑에 Package라는 패키지가 있고, 그 패키지는 module1.py를 가지고 있다.
그리고 각각 아래와 같은 함수를 가진다고 하자
module1.py
def function():
print("모듈1")
module2.py
def function():
print("모듈2")
아래와 같이 모듈을 임포트하여 사용하면 에러가 나지 않는다
>>> import Package.module1
>>> module1.function()
모듈1
>>> import module2
>>> module2.function()
모듈2
그러나 아래와 같이 패키지 폴더를 사용하면 에러가 난다
>>> import Package
>>> Package.module1.func()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'Package' has no attribute 'module1'
자세히 보면 위 케이스에서 우리는 import를 Package 즉 디렉토리를 임포트 하였다
그러나 에러로그를 보면 Package 모듈 이 moudle1 이라는 attribute가 없다고 말하고 있다
모듈은 .py로 끝나는 파일이다. 디렉토리가 아니다. 즉 파이썬은 자기가 Package.py 를 임포트했다고 말하는 것 이다.
이게 무슨 소리일까? 정답은 __init__.py에서 찾을 수 있다
3. __init__.py에 대한 이해
정답부터 말하면, 파이썬에서 디렉토리를 임포트 했을 경우, 디렉토리 안에 있는 __init__.py를 참고한다.
즉 위에서 import Package는 사실 import Package.__init__ 이런식으로 이해 해야 된다는 것 이다.
그리고 위 예시에서는 __init__.py 안에 module1 이라는 attribute가 없으므로 에러가 나는 것 이다.
실제로 아래 처럼 테스트를 해보면 __init__.py를 참고하는 것을 알 수 있다.
>>> import Package
>>> Package
<module 'Package' from '/root/Package/__init__.py'>
아래처럼 __init__.py에 함수를 정의한뒤 사용하면 다음과 같이 나타난다.
Package/__init__.py
def function():
print("나는 폴더에요")
>>> import Package
>>> Package.function()
나는 폴더에요
4. __init__.py와 __all__
아래의 코드는 정상 동작한다
>>> from Package.module1 import *
>>> function()
모듈1
허나 아래의 코드는 정상동작 하지 않는다.
>>> from Package import *
>>> module1.function()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'module1' is not defined
우리는 위 이유에 대해 이제 얘기할 수 있다. Package는 디렉토리이고 이때 __init__.py를 참고하기 때문. 그리고 __init__.py에는 module1 이라는게 없으므로 에러가 난다!
허나 만약 디렉토리 안에 있는 모든 모듈들을 임포트 하고 싶다면?
정답은 패키지의 경우, import * 에 사용될 모듈들을 __init__.py에 __all__ 로 정의를 해 주면 임포트가 가능하다!
__init__.py
__all__ = ['module1']
그럼 아래처럼 module1.py가 임포트되어 사용 가능하다
>>> from Package import *
>>> module1.function()
모듈1
5. 심화 문제 그리고 Relative Path
위 내용만 이해해도 충분하나, 좀 더 어려운 문제를 내보도록 하겠다.
아래와 같은 디렉토리 구조가 있다고 하자
root/
Package1/
__init__.py
module1.py
Package2/
__init__.py
module2.py
그리고 module1.py는 다음과 같이 정의되어 있다.
module1.py
def mod1():
print("나는 모듈1의 함수입니다")
여기서 modue2.py가 위 함수를 사용하고 싶다고 하자. 그럼 아래와 같이 module2를 작성해도 될까?
module2.py
from Package1 import module1
def mod2():
print("나는 모듈2의 함수입니다. 모듈1의 함수를 사용하겠습니다")
module1.mod1()
정답은 때에 따라 다르다 이다
만약 파이썬을 root 폴더에서 실행하였을 경우, root폴더에서는 Package1 이 보이기에 위와 같이 작성이 가능하다.
허나 파이썬을 Package2에서 실행하였을 경우, Package2에서는 Package1이 보이지 않기 때문에 에러가 난다.
이를 해결하기 위해서는 Relative Path를 사용해야 된다!
relative path는 다음 두가지가 존재한다
.. : 바로 위의 폴더
. : 현재 폴더
module2.py
from ..Package1 import module1
def mod2():
print("나는 모듈2의 함수입니다. 모듈1의 함수를 사용하겠습니다")
module1.mod1()
여기서 ..은 현재 파일 위치 기준 바로 위의 디렉토를 의미한다. module2의 바로 위 폴더는 root이고 root에서는 Package1 이 보이게 된다.
즉 위 코드 변화는 파이썬의 실행 위치 기준에서 / 모듈의 현재 위치로 기준을 옮긴거라 생각할 수 있다.
모듈의 현재 위치 기준으로 변경할 경우 파이썬의 실행 위치와 상관없이 문제 없이 파일 실행이 가능하다!
댓글