Skip to content

Python Project 구조화 1

당황하지 말자. 우리 모두 한 번쯤은 경험해 보았을 것이다. 솔직히 말해서 새 프로젝트를 시작할 때마다 코드 편집기를 보면서 프로젝트를 어떻게 구성할지, 폴더와 파일 이름을 어떻게 지을지, 폴더와 파일을 언제 어떻게 만들지 고민한다.


개요

이 포스팅에서는 여러분의 프로젝트를 구조화하는 방법을 더 잘 이해하고 다른 사람의 프로젝트를 이해하고 "읽을" 수 있도록 Python 프로젝트가 어떻게 구조화되는지 설명할 것이다.

이 포스팅을 모든 프로젝트에 적용할 수 있는 것은 아니다. 더 나은 접근 방식이 있다면 공유해 주세요 🤓.

읽은 다음 Python 프로젝트에서 파일(모듈)의 의미, 모듈과 스크립트의 차이점, 폴더(패키지)의 의미에 대해 이해할 수 있을 것이다.

바로 내용으로 들어가자.

모듈(Modules)

물론 가장 먼저 해야 할 일은 모든 프로젝트에서 코드를 작성할 파일을 만드는 것이다. 모든 것이 작게 시작된다. 그런데 갑자기 파일이 커져서 이제는 유지 관리하기가 쉽지 않게 된다.

그래서 코드를 세분화하기 위해 새 파일을 만드는 것이 좋겠다고 결정하며 같은 일이 계속 반복된다.

하지만 프로젝트에서 생성하는 모든 파일을 무엇이라고 부를까? 단순히 "파일"이라고 부르고 그에 맞는 이름을 붙여야 할까?

Python에서 프로젝트의 모든 파일(.py)은 모듈이다. 따라서 새 파일을 만들 때마다 엄밀히 말하면 프로젝트에 새 모듈을 만드는 것dl다.

Python의 공식 문서에 따른 모듈의 보다 기술적인 정의는 다음과 같다.

an object that serves as an organizational unit of Python code.

따라서 모든 코드는 하나 이상의 모듈 내에 작성된다고 해도 무방하다.

# database.py
#
# Creates a connection to a database and returns a session
from sqlalchemy.orm.session import Session
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine


def connector(db_url: str) -> Session:
    return sessionmaker(bind=create_engine(db_url))

더 진행하기 전에 모듈의 이름은 파일의 이름이다. 따라서 파일 이름이 database.py이므로 모듈database이다.

모듈 임포팅(Module Importing)

멋진 database 모듈에서 몇 가지를 임포트(import)한 것을 보았나요?

여기서 무슨 일이 일어나고 있는지 자세히 살펴보겠다.

  • 첫 번째 import는 매우 간단하다. session이라는 이름의 모듈이 있고, 이 모듈에서 SQLAlchemy 라이브러리에 속하는 객체(클래스)인 Session을 임포트하고 있다.
  • 두 번째와 세 번째 import는 첫 번째와는 다르다. 이 경우에는 __init__.py라는 파일에서 무언가(sesionmakercreate_engine)를 임포트하고 있다. __init__.py는 특수 Python 파일이다. 패키지를 초기화하는 데 이 파일을 사용한다(감히 말하건대, initinitialize의 약자이다 😎)(다음에 살펴보겠다). Python 3.3 이전에는 이 파일이 모든 Python 패키지 안에 있어야 하는 필수 파일이었다.
# __init__.py - for sqlalchemy
#
# this file imports create_engine from another module within sqlalchemy, engine
from .engine import create_engine as create_engine
# __init__.py - for sqlalchemy.orm
#
# this file imports sessionmaker from another module within sqlalchemy.orm, session
from .session import sessionmaker as sessionmaker

요약하면, sqlachemy에는 각각 ormsqlachemy __init__에서 임포트된 sessionengine 모듈이 있다.

모듈 vs. 스크립트

이 내용을 살펴보기 전에 특수 변수인 __name__에 대해 간략히 살펴보자. 코드의 각 모듈에는 모듈의 이름을 저장하는 전역 변수가 있다.

# let's import our module database
import database

print(database.__name__)

출력 결과는 'database'가 될 것이다. 이것이 실제로 모듈의 이름이기 때문이다.

그러나..., 다음과 같이 코드를 실행하기로 결정하면 어떻게 될까? 모듈의 이름이 변경될까?

$ python database.py

그렇다. 코드를 스크립트로 실행하기 때문에 Python은 모듈의 이름을 -__main__으로 변경한다.

Python 스크립트는 실행할 수 있는 파일이며, 실행하려면 다음을 포함하여야 한다.

if __name__ == "__main__":
    print("Do something")

if 조건을 사용하면 파일을 모듈로 실행하거나 코드 내 다른 곳에서 임포트한 라이브러리로 더 쉽게 실행할 수 있다.

패키지

코드를 정리하는 가장 좋은 방법 중 하나는 모듈패키지로 계층적으로 구성하는 것이다.

코드 베이스에 새 디렉터리를 만들고 __init__.py를 추가하여 패키지로 만든다.

프로젝트인 python_project에서 볼 수 있듯이 모든 코드를 배치할 소스(src) 디렉터리가 있다. 빈 __init__.py 파일이 있는 패키지(database)와 데이터베이스 어딘가에 연결하기 위한 모듈(connector.py)이 있다.

Utils 패키지 생성

물론 필요에 따라 여러 패키지를 추가하여 코드를 더 체계적으로 정리하고 유지 관리하기 쉽게 만들 수 있다. 코드의 여러 위치에서 사용되는 모듈이 있다고 가정해 보자. 이러한 모듈은 프로젝트에 대한 일종의 종속성이다.

이러한 모듈을 모두 포함하는 패키지를 만들어 필요한 곳에 가져오면 프로젝트에서 코드가 중복되는 것을 방지할 수 있다.

일반적으로 이 패키지의 이름은 utils이지만, 반드시 이렇게 명명해야 한다는 규정이 있는 것은 아니다. 단지 모범 사례일 뿐이며 이를 따를지 여부는 여러분이 결정할 수 있다. 그러나 모범 사례인 만큼 따라야 할 수도 있다.

예를 들어 connector.py에서 do_magical_stuff.py 모듈을 가져올 수 있다.

# database/connector.py
#
# importing the module do_magical_stuff from utils/

from utils import do_magical_stuff

from sqlalchemy.orm.session import Session
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine


def connect(bd_url: str) -> Session:
    # call our module to handle some magical stuff before connecting
    do_magical_stuff()
    return sessionmaker(bind=create_engine(db_url))

Models 패키지 생성

이 예에서는 데이터베이스를 사용하고 있으므로 테이블(모델)을 표현하고 싶다고 가정하면, 유지 관리가 쉽고 가독성을 높이기 위해 단일 패키지로 개별적으로 구성하는 것이 좋은 접근 방식이다.

Route 폴더 생성

마지막 사용 사례이다. API를 구축하는 경우 엔드포인트가 많이 있을 것이다. 엔드포인트는 그 기능에 따라 그룹화할 수 있다.

시스템에서 제품을 관리하고자 한다고 가정해 보자. 무언가를 관리한다는 것은 일반적으로 다음과 같은 시나리오를 의미한다.

  • 제품 생성
  • 제품 업데이트
  • 제품 읽기
  • 제품 삭제

제품과 관련된 모든 엔드포인트를 하나의 모듈(product_route.py)에 그룹화하면 특히 프로젝트가 커지는 경우 모든 엔드포인트를 하나의 파일에 넣는 대신 훨씬 더 쉽게 관리할 수 있다.

마치며

여기까지. 도움이 되기를 바랍니다.

1: 이 페이지는 Structuring Python Project을 편역한 것임.