Search
🌍

파이썬이 __init__.py 없이 패키지를 구분해내는 방법, sys.path 와 두 가지 종류의 패키지

🚀 prev note
♻️ prev note
♻️ next note
💡 아이디어
16 more properties
10년 전 답변을 참고(참고1) 해도 그렇고, 불과 지금 @11/13/2021 로부터 몇 년 전까지만 하더라도(참고2) python 이 내부적으로 '패키지' 를 구분해내는 방법의 핵심에는 __init__.py 파일이 있었다. 하지만 python 3.3 이후 python 패키지의 __init__.py 은 이제 필수요소에서 빠져 버렸다(참고2). 하지만 python 3.3 이후에도 __init__.py 파일은 많이 사용되고 있다(참고3). 필요 없다는데 왜 사용될까? 그냥 관례적으로 사용되고 있는 것일까?
이 파일을 설명하는 많은 사람들이 ‘하위 호환성’ 을 위해 이 파일을 만들어야 한다고 설명하지만, 이는 반쪽짜리 정답이다. 이를 올바르게 이해하기 위해서는 패키지의 두 가지 종류에 대해서 이해해야 한다. __init__.py 가 빠진 패키지를 namespace package 라고 하고, __init__.py 가 포함된 패키지를 regular package 라고 구분해 부른다(참고5). 대부분의 일반적인 상황에서 개발자들은 namespace package 가 아닌 regular package 를 만들 가능성이 훨씬 높기 때문에, __init__.py 를 꼭 넣어야 한다(참고11).
대부분의 개발자가 선택하게 될 regular package 에 대해서 먼저 이해해 보자. 만약 내가 어떤 디렉터리를 만들고, 그 안에 해당 패키지 관련된 서브패키지와 모듈을 모두 들어 있는 패키지를 만들고자 한다면, __init__.py 를 포함하여 regular package 를 만들어야 한다(참고12) (당연히 requirements.txt 에 명시되어 있는 패키지나 python 기본 패키지같은 것들은 디렉터리 내에 들어 있어야 하는 패키지에서 제외한다).
Python 3.3 이후에 __init__.py 라는 제약조건이 사라지져도 패키지를 만들 수 있게 되었다고 했다. 달라진 점이라면 조금 더 독특하고 유연한 형태의 패키지 구조를 만들 수 있게 되었다는 것이다. 이 부분을 이해하려면 regular package 와 namespace package 의 차이를 이해해야 한다. namespace package 는 무엇이 독특하고 무엇이 유연하다는 말일까? 이건 실제로 python 을 사용하는 많은 개발자들이 헷갈려 하는 내용인 듯하다 (참고7:당시 분위기도 그러하였음을 알 수 있는 인용). python 을 상당히 사랑하는 나에게는 너무 답답한 요소였기 때문에 이참에 정리한다.
Python 3.3 이후에 __init__.py 파일 없이 어떻게 파이썬이 패키지의 시작과 끝을 알아낼 수 있는지를 먼저 알아보자(참고4). 적절한 import 를 위해 package 를 구분해 내려거든 root package 도 python 이 스스로 찾을 줄 알아야 한다(참고6). python 인터프리터에게 __init__.py 가 존재하든 존재하지 않든 패키지가 시작되었다는 것을 알려주어야 할 것 아닌가! Python 3.3 버전 이후부터는, sys.path 에 등록되어 있는 path 와 일치하는 이름을 가진 모든 경로들은 패키지로 여겨진다. 그리고, 그 경로 속의 모든 내용물은 해당 패키지의 서브패키지/모듈 로 여겨진다. 이해를 돕기 위해 아래와 같은 상황을 생각해 보자.
예제를 보면, python 2.7 에서는 project/ 작업 디렉터리에서 example 패키지를 찾으라고 했음에도 못 찾고 있다
위 예제는 "-c" 플래그를 이용해 터미널에서 파이썬 스크립트를 인라인으로 실행시킨다. sys.path 에는 실행하는 python 스크립트가 저장된 절대경로가 자동으로 추가된다. python3.3 이상에서는 스크립트가 아무 문제 없이 잘 동작한다는 사실을 보여준다(참고10). 여기까지만 알아도, 파이썬이 패키지의 시작점을 어떻게 찾아내는지는 알 수 있었다. 그냥 sys.path 에 잡히면 패키지로 여겨지는 것이다. 이것이 __init__.py 가 존재하지 않아도 패키지를 찾아내는 비결이다.
위 예제를 보고 오해하기 쉬운 부분이 있다. sys.path 에는 실행하는 python 스크립트가 저장된 절대경로가 추가되지, python 스크립트를 실행하는 디렉토리의 절대경로가 추가되지는 않는다(참고9). Python3.3 실행에서 이 import 가 동작하는 것은, -c 플래그가 작업 디렉터리를 sys.path 에 넣어 주기 때문이다(참고10).
아래와 같은 디렉터리 구조가 있다고 생각해 보자. 두 개의 디렉터리 구조가 나타나 있는데, 그 루트가 다르다. 한편 google/cloud/storage 는 서로 다른 루트를 가진 두 패키지에서 공통되는 부분이다.
google_pubsub/ <- Package 1 google/ <- Namespace package (there is no __init__.py) cloud/ <- Namespace package (there is no __init__.py) pubsub/ <- Regular package (with __init__.py) __init__.py <- Required to make the package a regular package foo.py google_storage/ <- Package 2 google/ <- Namespace package (there is no __init__.py) cloud/ <- Namespace package (there is no __init__.py) storage/ <- Regular package (with __init__.py) __init__.py <- Required to make the package a regular package bar.py
Python
복사
자, 다음과 같은 상황들을 상상해 보라.
1.
bar.py 에서 from google.cloud import pubsub 을 하고 싶다
2.
foo.py 에서 from google.cloud import storage 을 하고 싶다
3.
어떤 임의의 파이썬 파일에서 pubsub.py 이나 storage.py 를 사용하고 싶다.
이런 상황에는 찾고자 하는 파일의 상위 디렉터리들인 google, cloud 디렉터리 내에는 __init__.py 가 포함되어있지 않도록 해야 한다. 이런 패키지 구성을 namespace package 라고 한다.
앞서 from google.cloud import 예제를 정확히 이해한 사람은, namespace package 가 regular package 의 상위 호환 정도이구나... 라고 이해할 수도 있다. 하지만 여기서 들 수 있는 두 번째 의문은 왜 맨 마지막에만 __init__.py 가 들어 있는지에 대한 내용이다.
많은 개발자들이 진짜 모르고 있는 부분이기도 하다. __init__.py 가 비록 비어 있는 파일일지라도 __init__.py 를 발견했을 때와 그냥 패키지를 찾아냈을 때의 행동이 다르다.
파이썬 인터프리터가 sys.path 에 존재하는 모든 것들이 패키지일 것이라고 염두해 두고, 어떤 경로 /A/B 에 딱 찾아들어갔을 때 __init__.py 가 존재하는 순간, 이제 어떤 경로 /A/B 에는 B 라는 '고정된' 패키지가 존재한다고 여기고 그 안에 들어있는 모듈들만 가지고 'single directory package' 를 따로 만들어버린다. 이제 from B import sth 라는 구문을 입력했을 때 다른 경로를 찾을 필요가 없고, 방금 찾은 sys.path 로부터 만든 'single directory package' 에서만 찾으면 되는 것이다 (참고13).
요약
python 3.3 이전
그냥 쓰지 말 것
python 3.3 이상인 경우
__init__.py 가 있는 경우
시작 : sys.path
끝 : __init__.py
__init__.py 가 없는 경우
시작 : sys.path
끝 : sys.path
드디어 모든 경우에 python 이 패키지의 끝을 찾아내는 방법을 알았다. 이제 글 상단에서 언급한 “namespace package 를 만들 것이 아니라면, 탐색 스코프를 내가 만들고 있는 패키지 내부로 고정시켜 주는 __init__.py 를 사용하라” 는 조언이 왜 나온 것인지 이해할 수 있다.
참고
3.