Python 모듈 객체(Module Object): 모듈도 객체라는 사실 이해하기

Ad Placeholder

앞에서 파이썬의 모듈(Module)에 대해 알아봤습니다.

모듈은 일반적으로 하나의 .py 파일이며, 함수나 클래스, 변수 등을 한곳에 모아 다른 코드에서 재사용할 수 있도록 해줍니다.

예를 들어 다음과 같은 모듈이 있다고 해보겠습니다.

# calculator.py

PI = 3.141592


def add(a, b):
    return a + b

그리고 다른 파일에서 이 모듈을 가져옵니다.

import calculator

print(calculator.PI)
print(calculator.add(10, 20))

여기서 한 가지 궁금한 점이 생길 수 있습니다.

calculator

이것은 단순한 파일 이름일까요?

그렇지 않습니다.

import calculator를 실행하면 파이썬은 모듈 객체(module object)를 만들고, calculator라는 이름이 그 모듈 객체를 가리키게 됩니다.

이번 글에서는 모듈 객체가 무엇인지, 모듈 안의 이름을 어떻게 관리하는지, __dict__, dir(), getattr() 등을 이용하면 모듈을 어떻게 살펴볼 수 있는지 알아보겠습니다.


모듈도 객체다

파이썬에서 중요한 특징 중 하나는 함수, 클래스뿐만 아니라 모듈도 객체로 다뤄진다는 것입니다.

예를 들어 다음 코드를 실행해보겠습니다.

import math

print(type(math))

실행 결과는 다음과 비슷합니다.

<class 'module'>

즉, math는 단순한 파일 이름이 아니라 module 타입의 객체입니다.

다음과 같이 확인할 수도 있습니다.

import math

print(type(math) is type(math))

물론 이 코드는 같은 객체의 타입을 비교하는 예이므로 결과는 True입니다.

더 직접적으로 확인하려면 다음과 같이 할 수 있습니다.

import math
import types

print(isinstance(math, types.ModuleType))

실행 결과:

True

math가 실제로 모듈 객체라는 것을 확인할 수 있습니다.


import를 하면 무슨 일이 일어날까?

다음 코드를 생각해보겠습니다.

import calculator

단순히 calculator.py 파일을 읽는다고 생각하기 쉽지만, 조금 더 정확하게 보면 파이썬은 다음과 같은 과정을 거칩니다.

calculator.py
      ↓
모듈 로드
      ↓
module object 생성
      ↓
모듈 내부 코드 실행
      ↓
calculator라는 이름으로 접근

결과적으로 현재 코드에서는 다음과 같이 모듈 객체를 사용할 수 있게 됩니다.

calculator.add(10, 20)

여기서 calculator는 모듈 객체이고 add는 그 객체 안에 저장된 이름입니다.

즉,

calculator.add

라는 표현은 객체의 속성에 접근하는 일반적인 파이썬 문법과 같은 구조입니다.


모듈의 __dict__

모듈 객체를 이해하는 데 중요한 것이 __dict__입니다.

앞에서 다음과 같은 모듈을 만들었다고 해보겠습니다.

# calculator.py

PI = 3.141592


def add(a, b):
    return a + b

이 모듈을 가져온 뒤 다음 코드를 실행할 수 있습니다.

import calculator

print(calculator.__dict__)

그러면 모듈 내부에서 관리하고 있는 여러 이름을 확인할 수 있습니다.

출력에는 대략 다음과 같은 항목들이 포함됩니다.

{
    '__name__': 'calculator',
    '__file__': '...',
    ...
    'PI': 3.141592,
    'add': <function add at ...>
}

정확한 출력 내용은 파이썬 버전과 실행 환경에 따라 달라질 수 있습니다.

중요한 것은 다음 두 항목입니다.

'PI'
'add'

우리가 calculator.py에 작성한 변수와 함수가 모듈 객체의 이름 공간에 들어가 있는 것입니다.


calculator.add와 calculator.__dict__['add']

여기서 재미있는 점을 확인할 수 있습니다.

다음 두 코드를 비교해보겠습니다.

calculator.add

그리고

calculator.__dict__["add"]

둘은 같은 함수 객체를 가리킵니다.

import calculator

print(calculator.add is calculator.__dict__["add"])

결과:

True

즉,

calculator.add()

와 같은 속성 접근은 모듈 객체 내부의 이름을 찾아 사용하는 것이라고 이해할 수 있습니다.

물론 실제 내부 동작을 단순히 딕셔너리 조회 하나로만 생각하면 구현 세부 사항을 지나치게 단순화하는 것이지만, 모듈의 이름 공간이 __dict__로 관리된다는 개념을 이해하는 데는 좋은 방법입니다.


dir()로 모듈 살펴보기

모듈 안에 어떤 이름들이 있는지 확인하고 싶다면 dir()을 사용할 수 있습니다.

import math

print(dir(math))

그러면 math 모듈이 가지고 있는 다양한 이름을 확인할 수 있습니다.

예를 들어 다음과 같은 이름들이 포함됩니다.

acos
asin
atan
ceil
cos
floor
log
pi
sqrt
...

직접 만든 모듈에서도 사용할 수 있습니다.

import calculator

print(dir(calculator))

그러면 calculator 모듈 안에 정의된 PI, add 등의 이름을 확인할 수 있습니다.


dir()과 __dict__의 차이

둘 다 객체가 가지고 있는 이름을 살펴보는 데 사용할 수 있지만 목적에는 차이가 있습니다.

dir(calculator)

는 객체에서 접근할 수 있는 이름들을 정리해서 보여주는 데 유용합니다.

반면,

calculator.__dict__

는 모듈 객체가 실제로 가지고 있는 이름과 값을 확인하는 데 유용합니다.

예를 들어 다음과 같이 사용할 수 있습니다.

import calculator

print(dir(calculator))

print(calculator.__dict__["PI"])
print(calculator.__dict__["add"])

특히 디버깅이나 파이썬의 객체 구조를 이해할 때 __dict__가 유용합니다.


getattr()로 모듈의 속성 가져오기

파이썬에는 객체의 속성을 동적으로 가져오는 getattr()이라는 함수가 있습니다.

예를 들어 다음과 같이 사용할 수 있습니다.

import calculator

add_function = getattr(calculator, "add")

print(add_function(10, 20))

결과:

30

getattr()의 기본적인 형태는 다음과 같습니다.

getattr(객체, "속성명")

따라서

getattr(calculator, "add")

는 calculator 객체에서 "add"라는 이름의 속성을 가져오는 것입니다.


문자열로 함수 이름을 관리할 수 있다

getattr()이 유용한 이유 중 하나는 속성 이름을 문자열로 전달할 수 있다는 것입니다.

예를 들어 다음과 같이 작성할 수 있습니다.

import calculator

name = "add"

function = getattr(calculator, name)

print(function(10, 20))

여기서 name의 값만 변경하면 다른 함수도 가져올 수 있습니다.

name = "multiply"

function = getattr(calculator, name)

이런 방식은 설정 파일이나 사용자 입력에 따라 실행할 기능을 선택해야 하는 프로그램에서 활용할 수 있습니다.


hasattr()로 속성 존재 여부 확인하기

동적으로 속성에 접근할 때는 해당 이름이 실제로 존재하는지 먼저 확인할 수도 있습니다.

이때 hasattr()을 사용할 수 있습니다.

import calculator

print(hasattr(calculator, "add"))
print(hasattr(calculator, "hello"))

결과:

True
False

hasattr()은 다음과 같은 형태로 사용합니다.

hasattr(객체, "속성명")

예를 들어 다음과 같이 작성할 수도 있습니다.

import calculator

name = "add"

if hasattr(calculator, name):
    function = getattr(calculator, name)
    print(function(10, 20))

이처럼 hasattr()과 getattr()을 함께 사용하면 동적으로 모듈의 기능을 선택할 수 있습니다.


setattr()로 모듈에 이름 추가하기

모듈 객체도 객체이기 때문에 속성을 추가할 수 있습니다.

예를 들어 다음과 같이 작성할 수 있습니다.

import calculator

setattr(calculator, "VERSION", "1.0")

print(calculator.VERSION)

결과:

1.0

setattr()의 형태는 다음과 같습니다.

setattr(객체, "속성명", 값)

위 코드는 결과적으로 calculator 모듈 객체에 VERSION이라는 이름을 추가한 것입니다.

다음과 같은 코드와 비슷한 효과를 얻을 수 있습니다.

calculator.VERSION = "1.0"

다만 setattr()을 사용하면 속성 이름을 문자열로 동적으로 지정할 수 있다는 차이가 있습니다.


모듈에 함수를 추가할 수도 있을까?

가능합니다.

함수를 만든 다음 모듈 객체의 속성으로 추가할 수도 있습니다.

import calculator


def divide(a, b):
    return a / b


calculator.divide = divide

print(calculator.divide(10, 2))

결과:

5.0

이 코드를 보면 모듈 역시 일반적인 객체처럼 속성을 가질 수 있다는 것을 알 수 있습니다.

다만 이런 식으로 실행 중에 모듈의 구조를 변경하는 것은 일반적인 프로그램 코드에서는 자주 사용할 필요가 없습니다.

모듈 파일에 직접 기능을 정의하는 것이 대부분의 경우 더 명확합니다.


모듈의 __name__

모듈 객체에는 여러 특별한 속성이 있습니다.

그중 하나가 __name__입니다.

import calculator

print(calculator.__name__)

결과:

calculator

모듈 자신의 이름을 나타냅니다.

앞에서 살펴본 __name__과 연결해서 생각하면 이해하기 쉽습니다.

모듈 파일에서 직접 실행하면:

print(__name__)

결과는 일반적으로:

__main__

이지만 다른 파일에서 모듈로 가져오면:

import calculator

print(calculator.__name__)

다음과 같이 모듈 이름이 됩니다.

calculator

이것이 바로 다음과 같은 패턴이 사용되는 이유이기도 합니다.

if __name__ == "__main__":
    main()

모듈의 __file__

모듈에는 __file__이라는 속성도 있습니다.

import calculator

print(calculator.__file__)

이 값을 출력하면 해당 모듈 파일의 위치를 확인할 수 있습니다.

예를 들어 환경에 따라 다음과 비슷한 형태로 출력될 수 있습니다.

C:\project\calculator.py

이를 통해 현재 사용하고 있는 모듈이 실제로 어느 위치에서 로드되었는지 확인할 수 있습니다.

특히 이름이 같은 모듈이 여러 곳에 존재하거나 import가 예상대로 되지 않을 때 확인해보면 도움이 됩니다.


모듈은 한 번만 실행될까?

다음과 같은 코드가 있다고 해보겠습니다.

import calculator
import calculator

import를 두 번 작성했다고 해서 일반적으로 모듈 코드가 매번 처음부터 다시 실행되는 것은 아닙니다.

파이썬은 이미 가져온 모듈을 관리하며, 같은 모듈을 다시 import할 때 이미 로드된 모듈을 재사용합니다.

이와 관련된 것이 sys.modules입니다.

import sys

print("math" in sys.modules)

math가 이미 로드되어 있다면 True가 출력됩니다.

직접 만든 모듈도 확인할 수 있습니다.

import calculator
import sys

print("calculator" in sys.modules)

결과:

True

즉, 파이썬은 로드된 모듈들을 sys.modules라는 공간에서 관리합니다.


sys.modules에서 모듈 객체 확인하기

조금 더 직접적으로 확인해보겠습니다.

import calculator
import sys

module = sys.modules["calculator"]

print(module is calculator)

결과:

True

여기서 중요한 것은 sys.modules["calculator"]가 실제 calculator 모듈 객체를 가리키고 있다는 것입니다.

따라서 다음 관계를 생각할 수 있습니다.

calculator
    ↓
module object
    ↑
    │
sys.modules["calculator"]

이 구조를 이해하면 파이썬의 import 시스템을 조금 더 깊게 이해할 수 있습니다.


모듈 객체도 변수에 저장할 수 있다

모듈도 객체이기 때문에 다른 변수에 참조를 저장할 수 있습니다.

import calculator

module = calculator

print(module.add(10, 20))

결과:

30

module이라는 새로운 변수가 같은 모듈 객체를 가리키고 있는 것입니다.

print(module is calculator)

결과:

True

이 역시 파이썬에서 모듈을 객체로 다룬다는 것을 보여주는 예입니다.


모듈 객체를 함수의 인자로 전달하기

모듈은 객체이기 때문에 함수에 전달할 수도 있습니다.

import calculator


def show_module(module):
    print(module.__name__)
    print(module.__file__)


show_module(calculator)

이처럼 모듈 객체를 함수의 인자로 전달할 수 있습니다.

실무에서 이런 방식이 항상 필요한 것은 아니지만, 파이썬이 모듈을 하나의 객체로 취급한다는 개념을 이해하는 데 좋은 예입니다.


types.ModuleType

모듈 객체의 타입은 types.ModuleType으로 확인할 수 있습니다.

import types
import calculator

print(type(calculator))
print(isinstance(calculator, types.ModuleType))

결과:

<class 'module'>
True

types 모듈에는 파이썬에서 사용되는 여러 객체 타입을 표현하는 클래스들이 제공됩니다.

모듈 객체인지 확인해야 하는 상황에서는 types.ModuleType을 사용할 수 있습니다.


모듈 객체를 이해하면 import가 조금 다르게 보인다

처음 파이썬을 배울 때는 다음 코드를 단순히 이렇게 생각하기 쉽습니다.

import calculator

calculator.py 파일을 가져온다.

하지만 지금까지의 내용을 종합하면 조금 더 정확하게 이해할 수 있습니다.

calculator.py
      ↓
모듈을 찾아 로드
      ↓
module object 생성
      ↓
모듈 내부 코드 실행
      ↓
sys.modules에 관리
      ↓
현재 코드에서 calculator라는 이름으로 사용

그리고 우리가 작성하는

calculator.add(10, 20)

은 모듈 객체에 들어 있는 add라는 이름에 접근하는 것입니다.

이 관점으로 보면 다음 코드들도 하나의 흐름으로 연결됩니다.

calculator.add
calculator.__dict__
dir(calculator)
getattr(calculator, "add")
hasattr(calculator, "add")
calculator.__name__
calculator.__file__

모두 모듈 객체를 대상으로 작업하고 있는 코드입니다.


모듈과 일반 객체의 공통점

모듈은 일반적인 클래스의 인스턴스와 완전히 동일한 방식으로 동작하는 것은 아니지만, 객체라는 관점에서는 여러 공통점을 볼 수 있습니다.

예를 들어 객체의 속성에 접근할 때:

object.name

모듈에서도:

module.name

형태로 접근합니다.

속성을 동적으로 가져올 때:

getattr(object, "name")

모듈에도 그대로 사용할 수 있습니다.

getattr(module, "name")

속성을 동적으로 설정할 때도:

setattr(module, "name", value)

와 같이 사용할 수 있습니다.

이런 특징 때문에 파이썬을 이해할 때 “모듈도 하나의 객체다”라는 관점이 꽤 중요합니다.


모듈 객체를 직접 수정하는 것은 조심해야 한다

모듈 객체에 속성을 추가하거나 변경하는 것은 가능하지만, 일반적인 코드에서는 꼭 필요한 경우가 아니라면 과도하게 사용하지 않는 것이 좋습니다.

예를 들어 다음과 같은 코드가 있다고 해보겠습니다.

import calculator

calculator.PI = 100

이후에는 다음과 같은 결과가 나옵니다.

print(calculator.PI)
100

원래 calculator.py에 작성했던 값과 현재 메모리에서 사용되는 값이 달라진 것입니다.

이처럼 모듈 객체를 실행 중에 변경하면 코드의 흐름을 추적하기 어려워질 수 있습니다.

따라서 일반적인 프로그램에서는 모듈 파일에 필요한 변수와 함수를 명확하게 정의하고 사용하는 것이 좋습니다.


정리

이번 글에서는 모듈을 단순히 .py 파일이라고 보는 것에서 한 단계 더 나아가 모듈 객체(Module Object)라는 관점에서 살펴봤습니다.

핵심 내용을 정리하면 다음과 같습니다.

모듈은 객체다

import math

print(type(math))
<class 'module'>

모듈 내부의 이름은 __dict__로 확인할 수 있다

import calculator

print(calculator.__dict__)

dir()로 접근 가능한 이름을 확인할 수 있다

print(dir(calculator))

getattr()으로 이름을 동적으로 가져올 수 있다

function = getattr(calculator, "add")

hasattr()으로 속성의 존재 여부를 확인할 수 있다

hasattr(calculator, "add")

setattr()으로 속성을 추가하거나 변경할 수 있다

setattr(calculator, "VERSION", "1.0")

sys.modules에서 로드된 모듈을 관리한다

import sys

print(sys.modules["calculator"])

결국 다음과 같이 이해하면 좋습니다.

.py 파일
   ↓
모듈
   ↓
module object
   ↓
변수 / 함수 / 클래스 등의 이름을 관리

모듈을 단순히 “코드를 저장하는 파일”이라고만 이해하는 것보다 파이썬에서 하나의 객체로 존재하며 이름 공간을 관리하는 대상이라고 이해하면 import의 동작이나 파이썬의 패키지 구조를 이해할 때도 도움이 됩니다.

이번 모듈 시리즈에서는 모듈의 기본적인 사용법부터 모듈 객체까지 살펴봤습니다.

앞에서 배운 클래스, 함수, 데코레이터와 마찬가지로 모듈 역시 파이썬 프로그램을 구성하는 중요한 단위입니다.

jiji

AI, Python, 생활정보와 정부 정책 정보를 쉽게 설명하는 정보 블로그입니다.

Ad Placeholder
Scroll to Top