재밌고 유용한 파이썬 프로그래밍! 파이썬으로 뭔가 의미있는 걸 만들고 나면, 이걸 잘 쓸 수 있도록 포장하고 배포하는 일 역시 중요하다. 실행에 필요한 라이브러리들이 잘 포함되도록 하고, 유닉스 명령행 앱 스타일로 다양한 옵션을 처리할 수 있도록 만들면 그럴 듯하게 써먹을 수 있다. 이를 위해선 파이썬 패키지 관리 pip, pipx 등이 필요하고, venv, pyenv 등 가상환경, argparse와 같은 명령행 파싱 모듈이 떠오른다. 하지만 막상 해보려 하면 생각치 못한 많은 이유로 쉽지 않다.
특히 파이썬을 macOS에서 쓰려면, 시스템에 설치된 것을 써야 할지, homebrew로 설치된 것을 쓸지, pyenv를 쓸지에 따라 여러 실행버전들을 혼동하게 되고, 거기에 패키지 관리 pip, pip3, conda 등이 겹치면서 지옥이 펼쳐지게 된다.
나는 homebrew 설치버전을 시스템 수준에서 사용하고 개별 프로젝트는 pyenv, virtualenv로 관리했으나, 최근엔 homebrew 설치버전에 pip로 써드파티 라이브러리를 설치하는 것을 추천하지 않으면서 pipx를 쓰라고 나오는데, 이것은 또 내가 맘에 들지 않더라. 어쨌건, 파이썬으로 뭔가 필요한 걸 만들면, venv로 매번 가상환경 만들고 써 오던 중 uv 라는 재밌는 도구를 발견했다. 마침, Python Weekly 메일리스트에서 Using uv to build and install Python CLI apps 글을 보고, 이 타이밍에 유용한 듯 하여 소개해본다.
uv
uv는 Rust로 만들어진 파이썬 패키지/프로젝트 관리 도구이다. 고속 의존성 해석 엔진을 기반으로, 가상환경 생성, 의존성 관리, 파이썬 버전 관리, 패키징, 포매터 기능을 간편하게 제공한다. Rust 기반 Resolver는 의존성 설치 속도가 pip나 poetry 대비 10~100배 빠르며, "uv add" 등 단순화된 CLI 명령어로 대부분의 작업을 수행한다. ".venv" 폴더나 파이썬 버전 설정 ".python-version"을 자동으로 생성하고 관리한다.
macOS 환경에서 다음 명령 한방에 설치한다.
brew install uv
이후, 다음과 같은 명령으로 가상환경을 만든다.
uv init --app --package myproj
상세한 사용 방법은 곧 알아보자.
click
명령행 UI를 만드는 파이썬 모듈로 argparse가 있다. 다양한 옵션 데이터형을 정한다던가, 서브커맨드를 만든다던가, 도움말을 자동으로 출력한다던가 등 유용한 기능들이 많다. 무엇보다, 파이썬 표준라이브러리여서 별도로 설치할 필요가 없다. 하지만, 문법이 다소 복잡하고 옵션이 많을수록 가독성이 낮은 코드가 만들어진다. click은 파이썬 데코레이터를 써서 좀 더 직관적, 간결한 코드를 만들게 한다.
import click
@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo(f"Hello {name}!")
if __name__ == '__main__':
hello()
위의 예시코드를 보면 코드만으로도 이해가 쉽다. 함수 hello에 데코레이터를 씌우는 것으로 명령행 프로그램이 만들어진다.
$ python hello.py --count=3
Your name: John
Hello John!
Hello John!
Hello John!
도움말도 자동으로 만들어진다.
$ python hello.py --help
Usage: hello.py [OPTIONS]
Simple program that greets NAME for a total of COUNT times.
Options:
--count INTEGER Number of greetings.
--name TEXT The person to greet.
--help Show this message and exit.
하지만, click은 써드파티 라이브러리이므로 별도로 설치해야 한다. click으로 만든 명령행 앱을 uv로 패키지 관리하여 사용하는 방법을 알아보자.
wc 같은 파일 크기 출력 프로그램
wc는 유닉스 기본 프로그램으로 파일에 대한 줄(line) 단어(word), 문자(char), 바이트수(byte)를 알려준다. 이와 유사한 mywc를 파이썬으로 만들어보자.
from pathlib import Path
def wc(filepath: Path, show_bytes: bool, show_words: bool, show_lines: bool) -> None:
content = filepath.read_bytes()
byte_count = len(content)
word_count = len(content.split())
line_count = len(content.splitlines())
output = (
(f"{line_count:8}" if show_lines else "")
+ (f"{word_count:8}" if show_words else "")
+ (f"{byte_count:8}" if show_bytes else "")
+ f" {filepath}"
)
print(output)
if __name__ == "__main__":
wc(Path(__file__), True, True, True)
위 파이썬 코드는 wc 처럼 특정 파일에 대해 바이트수, 단어수, 라인수를 출력한다. 이 프로그램에 click 명령행 UI 를 붙이고, uv로 빌드해보자.
먼저 다음 명령으로 uv 프로젝트를 만든다.
uv init --app --package mywc
위 명령은 mywc 라는 폴더를 만들고 초기화한다. "--app" 옵션은 이 프로젝트가 바로 실행할 수 있는 어플리케이션이라는 의미이다. 만일 라이브러리를 만들고 싶으면 "--lib" 옵션을 사용한다. "--package" 옵션은 프로젝트를 패키지 형태로 만든다. 이렇게 하면, 설치 가능한 형태로 만들어서 내 시스템에 설치하거나 배포할 수 있도록 한다. 이를 원하지 않으면 "--no-package" 옵션을 사용한다.
mywc 폴더에 들어가면 자동으로 같은 이름의 가상환경에 진입한다.
$ cd mywc
$ tree -a
.
├── .git
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── src
└── mywc
└── __init__.py
12 directories, 23 files
".python-version" 에는 사용하는 파이썬 버전이 표시되며, "pyproject.toml" 에는 프로젝트 기본 설정 내용이 표시된다.
$ cat .python-version
3.13
$ cat pyproject.toml
[project]
name = "mywc"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "Hyungyong Kim", email = "yong27@gmail.com" }
]
requires-python = ">=3.13"
dependencies = []
[project.scripts]
mywc = "mywc:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
다른 파이썬 버전을 쓰고 싶을 때는 uv init 할 때 "--python 3.14" 와 같은 옵션을 추가하면 된다. 패키지 옵션을 켰기 때문에 mywc 가 파이썬 패키지인 폴더이름 mywc 와 그 안에 __init__.py 파일이 자동으로 생성된다.
$ cat src/mywc/__init__.py
def main() -> None:
print("Hello from mywc!")
main 함수가 자동으로 생성되어 있고, 인사를 출력하게 되어 있다. 이를 바로 실행해볼 수 있다.
$ uv run mywc
Using CPython 3.13.2 interpreter at: /opt/homebrew/opt/python@3.13/bin/python3.13
Creating virtual environment at: .venv
Built mywc @ file:///Users/hygkim/study/mywc
Installed 1 package in 1ms
Hello from mywc!
중간에 출력되는 내용을 보면, 가상환경이 만들어지는 시점은 처음 실행할 때이다. .venv 를 생성하고, 패키지를 설치하고, 실행한다.
uv로 만들어진 파일을 어떻게 실행하는지 알게 되었다. 여기 __init.py 파일에 좀전에 준비했던 wc 함수를 붙히고, click 명령행 데코레이터를 붙힌다.
from pathlib import Path
import click
def wc(filepath: Path, show_bytes: bool, show_words: bool, show_lines: bool) -> None:
content = filepath.read_bytes()
byte_count = len(content)
word_count = len(content.split())
line_count = len(content.splitlines())
output = (
(f"{line_count:8}" if show_lines else "")
+ (f"{word_count:8}" if show_words else "")
+ (f"{byte_count:8}" if show_bytes else "")
+ f" {filepath}"
)
print(output)
@click.command()
@click.argument("filepath", type=Path)
@click.option("-c", is_flag=True, help="Show byte count.")
@click.option("-w", is_flag=True, help="Show word count.")
@click.option("-l", is_flag=True, help="Show line count.")
def main(filepath: Path, c: bool | None, w: bool | None, l: bool | None) -> None:
# If none of the options was explicitly set, they're all `True`.
if {c, w, l} == {False}:
c, w, l = True, True, True
wc(filepath, c, w, l)
위 코드가 동작하려면 써드파티 라이브러리인 click이 설치되어야 한다. 다음 명령으로 간단하게 추가한다.
$ uv add click
Resolved 3 packages in 76ms
Built mywc @ file:///Users/hygkim/study/mywc
Prepared 1 package in 253ms
Uninstalled 1 package in 1ms
Installed 2 packages in 3ms
+ click==8.1.8
~ mywc==0.1.0 (from file:///Users/hygkim/study/mywc)
잘 실행되는지 확인해본다.
$ uv run mywc src/mywc/__init__.py
27 118 966 src/mywc/__init__.py
위 명령은 특정 파일 "src/mywc/__init__.py" 에 대한 mywc 결과를 표시한다. 아주 잘 동작한다.
동작을 잘 살펴보면, "uv run" 명령으로 폴더이름 mywc 가상환경을 띄우고, 그 안에서 mywc 파이썬 패키지를 실행함을 알 수 있다. 여기 디렉토리에서야 이렇게 하면 되는데, 이 프로그램을 어느 경로에서나 실행하고 싶다면?
mywc를 시스템 수준으로 설치하기
다음 명령은 mywc 패키지를 시스템 수준으로 쓸 수 있도록 설치한다.
$ uv tool install . -e
"-e" 옵션은 editable 의미로, mywc 패키지를 수정하더라도 바로 반영될 수 있도록 한다. 이렇게 하고 나면, 어느 경로에서나 실행할 수 있다.
$ cd ~
$ mywc .zshrc
160 731 5607 .zshrc
mywc 실행파일이 어디있나 살펴보면,
$ which mywc
/Users/hygkim/.local/bin/mywc
$ cat ~/.local/bin/mywc
#!/Users/hygkim/.local/share/uv/tools/mywc/bin/python
# -*- coding: utf-8 -*-
import sys
from mywc import main
if __name__ == "__main__":
if sys.argv[0].endswith("-script.pyw"):
sys.argv[0] = sys.argv[0][:-11]
elif sys.argv[0].endswith(".exe"):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())
홈디렉토리의 .local 에 실행파일을 자동으로 만들고 mywc 패키지의 main 함수를 수입하여 실행함을 알 수 있다. ".exe" 확장자가 보이는 걸로 봐서, 윈도우즈에서도 잘 써먹을 수 있을 것 같다.
mywc 배포하기
이제 이 프로그램을 다른 사람이 쓸 수 있도록 배포해보자.
$ uv pip install build
Resolved 3 packages in 102ms
Prepared 2 packages in 12ms
Installed 3 packages in 3ms
+ build==1.2.2.post1
+ packaging==24.2
+ pyproject-hooks==1.2.0
$ uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist/mywc-0.1.0.tar.gz
Successfully built dist/mywc-0.1.0-py3-none-any.whl
위 두 명령으로 dist 폴더에 배포 가능한 파일이 생성되었다. 위 파일을 받은 다른 사람은 다음과 같은 방법들을 써서 mywc 를 설치할 수 있다.
$ uv pip install mywc-0.1.0-py3-none-any.whl
만일 uv를 안쓰는 분이라면, 그냥 pip로 설치해도 된다.
여기까지 새로운 파이썬 패키지 관리자 uv에 대해 알아보았다. 이전의 pip 지옥에서 벗어나서, 편리하게 파이썬 프로그래밍하고, 잘 포장하고, 잘 배포하자!
'프로그래밍' 카테고리의 다른 글
파이썬 발생자(generator)와 코루틴(coroutine) (3) | 2025.02.19 |
---|---|
macOS 터미널 녹화하기 - asciinema 와 agg 활용 (5) | 2025.02.12 |
파이썬 반복자(iterator)와 발생자(generator) (3) | 2025.01.30 |
아이폰 단축어로 그날의 모든것 자동으로 일기쓰기 (1) | 2025.01.25 |
X API v2로 오늘의 내 트윗들을 가져오는 아이폰 단축어 (6) | 2025.01.17 |