본문 바로가기
Python/디버깅

AttributeError: 'module' object has no attribute

by 붕어사랑 티스토리 2022. 12. 8.
반응형

파이썬은 참으로 쉬운 언어이다. 배우지 않아도 개발을 바로 할 수 있을만큼 쉽고 간단하고 강력하다.

그러나 위 에러를 마주하게 된다면 멘붕이 올 것이다.

 

 

결론부터 말하면 위 에러를 마주했다는 것은 파이썬의 모듈패키지에 대한 이해가 부족한 것이다.

 

그리고 아마 당신은 패키지를 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 이 보이게 된다.

 

 

 

 

 

즉 위 코드 변화는 파이썬의 실행 위치 기준에서 / 모듈의 현재 위치로 기준을 옮긴거라 생각할 수 있다.

 

모듈의 현재 위치 기준으로 변경할 경우 파이썬의 실행 위치와 상관없이 문제 없이 파일 실행이 가능하다!

 

 

반응형

댓글