[Python] 모듈시스템과 계층구조

프로젝트 내에서 모듈, 소스코드를 관리하는 방법을 정리해본다.

파이썬의 모듈시스템은 다른 언어 대비해서 좀 지저분한 지점이 존재한다.
언어 자체가 그렇긴 하지만.



import 구문

python에서 다른 소스파일을 가져와서 쓸 필요가 있다면, 기본적으로 import 구문을 통해서 가져온다.

만약 이렇게 같은 경로 내에 파일들이 있다면


파일명을 기준으로 import를 해서 바로 가져와서 사용할 수 있다.
이렇게 모듈을 통째로 import해올 때는, 그 모듈 객체를 기준으로 내부의 함수를 전부 접근해서 사용할 수 있다.

특정 모듈에서 특정 함수만 가져와서 쓰고 싶다면, from 구문을 사용할 수 있다.

이러면 bar 모듈에서 bar 함수를 가져온다는 의미가 된다.




모듈의 디렉터리 수준 분리

디렉터리를 기반으로 소스코드를 분리하고 연결하다보면 좀 복잡해지기 시작한다.
utils라는 패키지 디렉터리를 만들고, 그걸 엔트리포인트인 main.py에서 쓰도록 구성한다고 가정해보자.

일단, foo에서 bar를 import하던 부분부터 바뀌어야 한다.

이게 폴더 안에 들어간 시점에서는, 직접적인 엔트리포인트 스크립트가 아니기 때문에 "패키지"로서 취급이 된다.
이 경우에는 절대경로로 import할 수 없고 상대경로(.)를 기준으로 동일 디렉터리의 파일을 import해야 한다.

그리고 이 폴더 자체를 main.py에서 모듈로서 사용하게 하기 위해서는, init.py라는 껍데기용 파일을 만들어야 한다.

내용은 필요하지 않다.
이게 있어야 파이썬 모듈시스템은 이 디렉터리가 모듈로서 사용될 수 있다고 판단한다.

그럼 main.py에서는

적당히 이렇게 가져다가 쓸 수 있다.




import의 상대경로와 절대경로

import 경로를 지정하는 것도 2가지 방식이 있다.

이렇게 프로젝트 구조가 잡혀있다고 가정해보겠다.



상대경로

이렇게 .를 사용하는 방식은 상대경로 기반의 접근 방식이라고 할 수 있다.

.은 단순히 현재 경로의 파일을 찾는다는 것이고, .calculdate는 현재 경로 기준으로 calculate라는 모듈을 찾아서 또 그 안에서 add라는 모듈을 가져오는 것이다.

그리고 위에서는 현재경로 기준으로 스타트를 잡고 들어갔지만, ".."를 사용하면 상위 경로로 거슬러올라갈 수도 있다. 만약 calculate 모듈에서 상위 폴더인 utils의 bar.py을 가져다 쓰고 싶다면, 이렇게 할 수 있는 것이다.

근데 여기엔 제한사항이 조금 있다. 스크립트가 시작된 root 디렉터리를 거쳐갈 수는 없다.
그 root 바로 아래 레벨의 패키지까지만 거슬러올라갈 수 있는 것이다.

그래서 만약 저기서 한 단계 더 올라가려고 시도하면

오류가 발생한다.



절대경로

혹은, 절대경로 기반으로 import 경로를 지정할 수도 있다.
엔트리포인트가 있는 위치(main.py) 디렉터리 기준으로 모듈을 찾아서 들어가는 것이다.

그냥 이런 식으로 치면 루트 디렉터리 기준으로 utils 모듈을 찾아서 그 안의 bar 모듈을 꺼내고, utils 모듈 안의 calculate 모듈을 찾아서 그 안의 add 모듈을 꺼내는 것이 된다.

PEP 8 컨벤션에서는 패키지끼리 연결하거나 패키지를 가져다 쓸 경우엔 절대경로를 쓰되, 패키지 단위 내에서는 상대경로를 쓰는 것을 권장한다.




모듈과 캐시

python은 스크립트 언어라서 함수 바깥 영역에도 실행 코드를 마음대로 집어넣을 수 있다.

그래서 단순 라이브러리성 모듈에도 실행 코드를 넣어서 실행시킬 수 있는데, 이게 만약 여러번 import되면 어떻게 될까? import될때마다 저 print가 실행될까?

결론부터 말하자면, 한번만 실행된다.
python 모듈시스템은 이 소스파일들에 대해서 모듈 캐시를 관리하고, 한번 로드한 모듈 소스는 다시 실행하지 않고 적당히 재사용하게 하기 때문이다.

그래서 import cycle을 넣어서 import가 여러번 발생하게 하더라도

print는 한번씩만 실행될 것이다.