init.py란?
__init__.py는 Python에서 directory를 package로 다룰 때 사용하는 파일이다.
예를 들어 다음과 같은 구조가 있다고 하자.
my_app/
main.py
users/
__init__.py
models.py
services.py
users directory 안에 __init__.py가 있으면 Python은 users를 package로 인식한다.
그래서 다음처럼 import할 수 있다.
from users.models import User
from users.services import create_user
최근 Python에서는 __init__.py가 없어도 namespace package로 동작할 수 있다.
하지만 일반적인 application code에서는 package 의도를 명확히 하기 위해 __init__.py를 두는 경우가 많다.
module과 package
먼저 용어를 정리하자.
| 용어 | 의미 |
|---|---|
| module | 하나의 .py 파일 |
| package | 여러 module을 담는 directory |
__init__.py | package가 import될 때 실행되는 초기화 파일 |
예를 들어 다음 구조에서 models.py는 module이고, users는 package이다.
users/
__init__.py
models.py
services.py
import 기준으로 보면 다음과 같다.
import users
import users.models
import users를 실행하면 users/__init__.py가 실행된다.
import users.models를 실행하면 package 초기화 후 models.py가 import된다.
빈 init.py
가장 단순한 __init__.py는 빈 파일이다.
users/
__init__.py
models.py
services.py
이 경우 __init__.py는 별도 코드를 실행하지 않고, 단지 이 directory가 package라는 것을 명확히 표시한다.
처음에는 빈 파일로 시작해도 충분하다. package가 커지고 public API를 정리해야 할 때 내용을 추가하면 된다.
package public API 만들기
__init__.py는 package의 public API를 모아두는 용도로 자주 사용된다.
예를 들어 users/models.py가 다음과 같다고 하자.
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
그리고 users/services.py가 다음과 같다고 하자.
from users.models import User
def create_user(id: int, name: str) -> User:
return User(id=id, name=name)
이제 users/__init__.py에서 필요한 이름을 다시 export할 수 있다.
from users.models import User
from users.services import create_user
그러면 사용하는 쪽에서는 더 짧게 import할 수 있다.
from users import User, create_user
이렇게 하면 package 내부 파일 구조가 바뀌어도 외부 import 경로를 안정적으로 유지하기 쉽다.
all
__all__은 from package import *를 했을 때 외부로 노출할 이름을 정한다.
from users.models import User
from users.services import create_user
__all__ = ["User", "create_user"]
이제 다음 import에서 User, create_user만 import 대상이 된다.
from users import *
import *는 일반 application code에서 많이 권장되지는 않는다.
하지만 library를 만들 때 public API를 문서화하는 용도로 __all__을 두면 도움이 된다.
즉 __all__은 다음 의미를 가진다.
이 package에서 외부 사용자가 써도 되는 이름 목록
내부 module 숨기기
package 내부 구현은 _ prefix로 표시할 수 있다.
users/
__init__.py
models.py
services.py
_validators.py
_validators.py는 내부용 module이라는 의미이다.
Python이 강제로 import를 막지는 않지만, 외부에서 직접 사용하지 말라는 관례이다.
# users/_validators.py
def validate_name(name: str) -> None:
if not name:
raise ValueError("name is required")
외부에는 create_user만 노출한다.
# users/__init__.py
from users.models import User
from users.services import create_user
__all__ = ["User", "create_user"]
이렇게 하면 package를 사용하는 쪽에서는 내부 구현보다 public API를 중심으로 사용하게 된다.
import 경로 단순화
__init__.py를 잘 쓰면 import 경로를 단순화할 수 있다.
다음처럼 내부 구조가 깊다고 하자.
my_app/
auth/
__init__.py
domain/
__init__.py
user.py
token.py
use_cases/
__init__.py
login.py
그냥 import하면 경로가 길어진다.
from auth.domain.user import User
from auth.use_cases.login import login
auth/__init__.py에서 필요한 이름을 re-export하면 더 단순하게 만들 수 있다.
from auth.domain.user import User
from auth.use_cases.login import login
__all__ = ["User", "login"]
사용하는 쪽에서는 다음처럼 쓸 수 있다.
from auth import User, login
다만 너무 많은 이름을 최상위 package에 몰아넣으면 오히려 어디에서 온 이름인지 알기 어려워진다. 자주 쓰는 핵심 API만 올리는 것이 좋다.
package 초기화 코드
__init__.py는 package가 import될 때 실행된다.
그래서 초기화 코드를 넣을 수 있다.
print("users package loaded")
하지만 일반적으로 __init__.py에 무거운 실행 코드를 넣는 것은 좋지 않다.
예를 들어 다음 작업은 피하는 것이 좋다.
DB connection 생성
network request 실행
큰 파일 읽기
환경 변수 검증 후 process 종료
오래 걸리는 초기화
이유는 단순하다.
import users만 했는데 예상하지 못한 작업이 실행될 수 있기 때문이다.
좋은 기준은 다음과 같다.
__init__.py에는 import와 가벼운 상수 정의 정도만 둔다.
실제 초기화는 명시적인 함수로 분리한다.
예를 들어 database 초기화는 다음처럼 명시적으로 호출하는 편이 낫다.
def init_database():
...
circular import 주의
__init__.py에서 여러 module을 한꺼번에 import하면 circular import가 생길 수 있다.
예를 들어 다음 구조를 보자.
users/
__init__.py
models.py
services.py
__init__.py에서 User와 create_user를 export한다.
from users.models import User
from users.services import create_user
그런데 services.py에서 다시 package root를 import하면 문제가 생길 수 있다.
# users/services.py
from users import User # circular import 위험
이 경우 내부 module끼리는 직접 module 경로를 import하는 편이 안전하다.
# users/services.py
from users.models import User
기준은 다음과 같다.
package 외부에서는 from users import User
package 내부에서는 from users.models import User
이렇게 나누면 circular import 가능성을 줄일 수 있다.
relative import
package 내부에서는 relative import를 사용할 수도 있다.
# users/services.py
from .models import User
from ._validators import validate_name
.은 현재 package를 의미한다.
..은 상위 package를 의미한다.
from ..database import Session
relative import는 package 내부 구조를 기준으로 import하므로, package 이동 시 수정량이 줄어들 수 있다. 반면 너무 많이 사용하면 import 경로를 읽기 어려워질 수 있다.
작은 package에서는 relative import가 깔끔하다. 큰 application에서는 absolute import를 선호하는 팀도 많다.
version 같은 metadata
library package에서는 __init__.py에 version 같은 metadata를 두기도 한다.
__version__ = "0.1.0"
사용자는 다음처럼 확인할 수 있다.
import my_library
print(my_library.__version__)
다만 package metadata를 pyproject.toml에서 관리하는 경우에는 중복 관리 문제가 생길 수 있다.
작은 library에서는 괜찮지만, 큰 project에서는 version 관리 방식을 하나로 정하는 것이 좋다.
예시 구조
다음은 __init__.py를 사용해 package public API를 정리한 예시이다.
shop/
__init__.py
products.py
orders.py
_validators.py
products.py
from dataclasses import dataclass
@dataclass
class Product:
id: int
name: str
price: int
orders.py
from shop.products import Product
from shop._validators import validate_quantity
def create_order(product: Product, quantity: int) -> dict:
validate_quantity(quantity)
return {
"product_id": product.id,
"quantity": quantity,
"total_price": product.price * quantity,
}
_validators.py
def validate_quantity(quantity: int) -> None:
if quantity <= 0:
raise ValueError("quantity must be positive")
__init__.py
from shop.orders import create_order
from shop.products import Product
__all__ = ["Product", "create_order"]
사용하는 쪽에서는 다음처럼 쓴다.
from shop import Product, create_order
product = Product(id=1, name="Keyboard", price=100)
order = create_order(product, quantity=2)
외부 사용자는 shop.products, shop.orders의 위치를 몰라도 된다.
package root에서 공개한 API만 사용하면 된다.
언제 비워두고 언제 작성할까
__init__.py는 항상 복잡하게 작성할 필요가 없다.
| 상황 | 권장 |
|---|---|
| 단순 package 표시 | 빈 __init__.py |
| public API 정리 | 핵심 class/function re-export |
| library 개발 | __all__, __version__ 고려 |
| 큰 application | import side effect 최소화 |
| circular import가 잦음 | __init__.py import 줄이기 |
처음에는 빈 파일로 시작하고, 외부에서 자주 import하는 이름이 명확해질 때 re-export를 추가하는 방식이 안전하다.
정리
__init__.py는 Python package를 관리하는 중요한 파일이다.
핵심은 다음과 같다.
- directory를 package로 명확히 표시한다.
- package가 import될 때 실행된다.
- public API를 re-export할 수 있다.
__all__로 공개할 이름을 정리할 수 있다.- 내부 module은
_module.py처럼 표시할 수 있다. - 무거운 초기화 코드는 넣지 않는 것이 좋다.
- package 내부에서는 circular import를 조심해야 한다.
간단히 말하면 __init__.py는 package의 입구이다.
외부 사용자가 어떤 이름을 import해야 하는지 정리하고, 내부 파일 구조를 숨기는 역할을 한다.