[에러 해결] Python NameError: 모듈 임포트 시 함수를 찾을 수 없을 때 원인과 해결 방법 (feat. __init__.py)

파이썬 개발 과정에서 코드를 체계적으로 관리하기 위해 모듈이나 패키지로 분리하는 것은 필수적인 단계입니다. 하지만 이 과정에서 종종 “함수를 찾을 수 없다”는 NameError와 같은 문제를 마주치곤 합니다. 특히 __init__.py 파일을 활용하여 패키지 구조를 만들 때, 예상치 못한 임포트 오류로 당황할 수 있습니다.

이 문서는 스택오버플로우에서 공유된 실제 사례를 바탕으로, 파이썬 패키지 구조에서 NameError: name 'function_name' is not defined 에러가 발생하는 근본적인 원인을 분석하고, 개발자가 쉽게 이해하고 적용할 수 있는 해결책을 제시합니다. 구글 검색 상위 노출에 최적화된 명확하고 실용적인 정보를 통해 여러분의 개발 생산성을 높이는 데 기여하고자 합니다.

1. 에러 발생 상황

프로젝트를 모듈화하면서 개발자들이 흔히 겪는 NameError 중 하나입니다. 주로 __init__.py 파일을 처음 사용하거나, 패키지 내 모듈의 함수를 외부에서 직접 호출하려고 할 때 발생합니다. 스택오버플로우 사례에서 문제가 된 프로젝트 구조는 다음과 같았습니다.

# 프로젝트 구조
.
├── main.py
└── core/
    ├── __init__.py
    └── fileManagement.py

core/fileManagement.py 파일 안에는 importInputs()라는 함수가 정의되어 있었습니다. main.py에서는 core 패키지에서 모든 것을 임포트하기 위해 from core import *를 사용한 후, 곧바로 df=importInputs()를 호출했습니다. 이때 core/__init__.py 파일에는 __all__ = ["fileManagement"]가 포함되어 있었습니다.

모든 코드가 하나의 파일에 있을 때는 정상적으로 작동했지만, 위와 같이 모듈화를 진행하자 다음과 같은 에러가 발생했습니다.

NameError: name 'importInputs' is not defined

특이한 점은 fileManagement.py 파일을 main.py와 같은 디렉토리로 옮기면 문제가 해결되었다는 것입니다. 이 현상은 파이썬의 모듈 임포트 메커니즘, 특히 패키지 내부 모듈 처리 방식에 대한 이해가 필요함을 명확히 보여줍니다.

2. 명확한 발생 원인

이 에러의 핵심 원인은 파이썬의 from package import * 문과 __init__.py 파일 내의 __all__ 변수가 상호작용하는 방식에 있습니다.

2.1. from package import *의 동작 방식

from core import * 문은 core 패키지의 __init__.py 파일을 실행합니다. 이때 __init__.py 파일 내부에 __all__ 변수가 정의되어 있으면, import *는 __all__ 리스트에 명시된 이름들만 현재 네임스페이스로 가져옵니다.

스택오버플로우 사례에서 core/__init__.py 파일은 다음과 같았습니다.

# core/__init__.py
__all__ = ["fileManagement"]

이 설정으로 인해 main.py에서 from core import *를 실행하면, main.py의 네임스페이스에는 fileManagement라는 모듈 이름만 추가됩니다. 즉, core 패키지 안에 있는 fileManagement.py 모듈 자체는 임포트되지만, 그 안에 정의된 importInputs() 함수는 직접적으로 main.py의 네임스페이스로 들어오지 않습니다. 따라서 importInputs()를 직접 호출하면 “정의되지 않은 이름”이라며 NameError가 발생하는 것입니다.

만약 from core import *를 사용한 후 fileManagement.importInputs()와 같이 호출했다면 문제가 발생하지 않았을 것입니다. 하지만 사용자는 importInputs()를 직접 호출하기를 원했습니다.

2.2. 파일 위치 변경 시 해결되는 이유

fileManagement.py를 main.py와 같은 디렉토리로 옮기면 문제가 해결되는 이유는 다음과 같습니다.

# 변경된 프로젝트 구조
.
├── main.py
└── fileManagement.py # main.py와 같은 레벨로 이동
└── core/             # 빈 디렉토리 또는 다른 파일만 남음

이 경우, main.py에서는 더 이상 from core import *를 사용할 수 없거나, 사용하더라도 fileManagement 모듈을 찾을 수 없게 됩니다. 만약 main.py에서 import fileManagement와 같이 직접 임포트했다면, 파이썬은 현재 디렉토리에서 fileManagement.py를 찾아 로드하고 그 안의 함수를 직접 fileManagement.importInputs()와 같이 사용할 수 있게 됩니다. 이는 패키지 구조가 아닌 단순 모듈 임포트 상황이 되기 때문입니다.

3. 해결 방법 및 코드 예시

이 문제를 해결하는 방법은 크게 두 가지로 나눌 수 있습니다. 가장 권장되는 방법은 명시적인 임포트 방식을 사용하는 것입니다.

3.1. [권장] main.py의 임포트 문 수정

가장 명확하고 파이썬 커뮤니티에서 권장하는 방법은 main.py에서 필요한 함수나 모듈을 직접 명시하여 임포트하는 것입니다. from package import * 문은 네임스페이스를 오염시키고 코드 가독성을 해칠 수 있어 일반적으로 피하는 것이 좋습니다.

main.py 파일을 다음과 같이 수정합니다:

# main.py
# core 패키지 내 fileManagement 모듈에서 importInputs 함수를 직접 임포트
from core.fileManagement import importInputs

# 이제 importInputs() 함수를 직접 호출할 수 있습니다.
df = importInputs()
print(f"임포트된 데이터: {df}") # 예시 출력

이 방법은 core 패키지 내의 fileManagement 모듈에서 importInputs 함수만 정확하게 가져오므로, NameError를 방지하고 코드의 의도를 명확하게 합니다.

3.2. __init__.py 파일 수정 (신중하게 사용)

만약 from core import * 문을 반드시 유지하고 싶다면, core/__init__.py 파일에서 importInputs 함수를 core 패키지 네임스페이스로 직접 가져와야 합니다. 그리고 __all__ 변수가 사용되고 있다면, 임포트하려는 함수 이름을 __all__ 리스트에 추가해야 합니다.

core/__init__.py 파일을 다음과 같이 수정합니다:

# core/__init__.py
# 상대 경로 임포트를 사용하여 fileManagement 모듈의 importInputs 함수를 현재(core) 패키지 네임스페이스로 가져옵니다.
from .fileManagement import importInputs

# __all__ 변수가 정의되어 있다면, import * 시 노출할 이름을 명시해야 합니다.
# 이제 importInputs 함수가 'core' 패키지의 공개 인터페이스가 됩니다.
__all__ = ["importInputs"]

이렇게 하면 main.py에서 from core import *를 실행할 때, __init__.py를 통해 importInputs 함수가 직접 네임스페이스로 가져와지므로 오류 없이 호출할 수 있습니다.

# main.py
# 이제 core 패키지의 공개 인터페이스에 importInputs 함수가 포함됩니다.
from core import *

df = importInputs()
print(f"임포트된 데이터: {df}") # 예시 출력

하지만 이 방법은 __init__.py가 점점 길어지고 복잡해질 수 있으며, 어떤 함수가 어디서 왔는지 추적하기 어려워질 수 있으므로, 꼭 필요한 경우가 아니라면 섹션 3.1의 명시적인 임포트 방식을 사용하는 것이 좋습니다.

4. 향후 예방을 위한 팁

  • 명시적인 임포트 선호: from package.module import function_name 또는 import package.module 과 같이 필요한 모듈이나 함수를 정확히 명시하여 임포트하는 습관을 들이세요. 이는 코드의 가독성을 높이고, 이름 충돌(Name Collision) 가능성을 줄이며, 디버깅을 용이하게 합니다.
  • __init__.py의 역할 이해: __init__.py 파일은 파이썬에게 해당 디렉토리가 패키지임을 알리는 역할을 합니다. 또한, 패키지 초기화 로직을 수행하거나, from package import * 시 노출될 모듈/이름들을 __all__ 변수를 통해 정의하는 데 사용됩니다. __all__은 import *의 동작을 명시적으로 제어할 때 유용합니다.
  • 패키지 구조의 중요성: 프로젝트 초기부터 논리적인 패키지 및 모듈 구조를 설계하는 것이 중요합니다. 이는 나중에 발생할 수 있는 임포트 관련 문제를 예방하고 코드 유지보수를 용이하게 합니다.
  • PYTHONPATH는 최후의 수단: 스택오버플로우 질문에서 언급된 PYTHONPATH 설정은 일반적으로 개발자가 직접 건드릴 필요가 없는 환경 변수입니다. 대부분의 임포트 문제는 상대 경로 임포트나 올바른 패키지 구조를 통해 해결 가능합니다. PYTHONPATH를 잘못 설정하면 의도치 않은 문제가 발생할 수 있으며, 이는 특정 환경에만 의존하는 코드를 만들 위험이 있습니다. 가능한 한 표준적인 패키지 관리 방법을 따르세요.

파이썬의 모듈 및 패키지 시스템은 프로젝트의 규모가 커질수록 그 중요성이 더욱 부각됩니다. 이 가이드가 여러분의 NameError 해결에 도움이 되었기를 바라며, 더욱 견고하고 유지보수하기 쉬운 파이썬 프로젝트를 만드는 데 기여하기를 바랍니다.

댓글 남기기