Uv: 의존성이 있는 스크립트 실행하기
(docs.astral.sh)- uv를 사용하면 Python 스크립트 실행 시 의존성 관리를 자동화함
- 별도의 가상환경 관리 없이 스크립트별로 환경이 자동으로 생성 및 유지됨
- 필요한 패키지는 inline metadata 또는 명령행 옵션 등 다양한 방식으로 선언 가능함
- Python 버전과 패키지 관리도 스크립트 단위로 선언 및 자동 조정 가능함
- Lock 파일과 의존성 버전 제한 옵션 등으로 재현성과 유지관리성을 높임
개요
- uv는 Python 스크립트 실행 시 해당 스크립트가 필요로 하는 패키지 의존성을 자동으로 관리해주는 도구임
- 사용자는 번거로운 가상환경 생성, 패키지 설치를 직접 하지 않아도 됨
- 여러 실행 옵션과 inline metadata 활용법, 다양한 의존성 선언 방식, 각종 제어 기능을 제공함
Python 환경과 uv의 역할
- Python은 각각의 설치마다 고유한 환경을 가짐
- 일반적으로 가상환경을 만들고 관리하는 것이 권장됨
- uv는 가상환경을 자동으로 관리하며, 선언적 방식으로 의존성을 다룸
- 단순한 스크립트는
uv run example.py만으로 즉시 실행 가능함 - 표준 라이브러리 사용 시 추가 설정 없이 작동함
인자 전달 및 입력 방식
- 스크립트에 명령행 인자를 넘길 수 있음
- 표준 입력에서 직접 스크립트 코드를 받아 실행하거나, here-document 기능도 지원함
프로젝트 환경과 --no-project 옵션
- 스크립트가 프로젝트 폴더(예:
pyproject.toml이 있는 곳)에서 실행될 경우, 프로젝트 의존성도 설치됨 - 필요 없다면
--no-project플래그를 스크립트 이름 앞에 넣어 프로젝트 환경을 무시할 수 있음
스크립트 의존성 선언과 관리
- 외부 패키지가 필요한 경우, 명령행 옵션
--with를 이용해 의존성을 지정해서 실행 가능함 - 특정 버전 제약 조건도 지원하며, 여러 의존성도 반복해서 옵션으로 지정 가능함
- 프로젝트 환경에서 추가 의존성을 더할 수 있고, 원하지 않으면
--no-project로 제어 가능함
Inline Script Metadata(PEP 723 방식)
- Python에서는 이제 스크립트 자체에 의존성이나 Python 버전을 선언하는 표준 포맷을 지원함
-
uv init --script로 인라인 메타데이터가 포함된 스크립트를 손쉽게 생성 가능함 -
uv add --script로 스크립트에 필요한 의존성을 TOML 포맷으로 추가 관리할 수 있음 - 인라인 메타데이터가 있으면, 프로젝트의 의존성은 무시되고 오직 스크립트 의존성만 적용됨
Python 버전 선언 및 관리
- 스크립트 내 또는 실행 시 원하는 Python 버전을 지정할 수 있음
- 지정된 버전이 없다면 자동으로 다운로드 및 설정됨
Shebang으로 바로 실행 가능한 스크립트 작성
- shebang(@#!...)을 이용해
uv run --script방식으로 직접 실행 파일 제작 가능함 - 이때도 의존성 선언 및 Python 버전 등도 스크립트 상단에서 가능함
패키지 인덱스 및 인증 지원
-
--index옵션으로 커스텀 패키지 인덱스 사용 가능 - index 정보도 메타데이터에 포함됨
- 인증이 필요한 경우 별도 문서 참고 가능
의존성 고정(Lock)과 재현성 향상
-
uv lock --script로 스크립트 단위의 Lock 파일 작성 및 관리 가능 - 이후 실행/의존성 추가 시 lock 파일 재사용 및 필요시 갱신됨
- 버전 재현성을 위한
exclude-newer(특정 날짜 이후 릴리즈 제외) 옵션 제공 - RFC 3339 타임스탬프로 날짜 지정
Python 버전 유연성
- 각 실행 시 명령행 옵션으로 任의의 Python 버전 사용 지정 가능
- 예시:
uv run --python 3.10 example.py
Windows 지원
-
.pyw확장자를 가진 스크립트는 윈도우에서 pythonw로 실행됨 - GUI 기반의 스크립트도 의존성 함께 실행 가능함
참고 문서
- 더 자세한 명령 사용법은 CLI 참조 문서 및 툴 실행/설치 가이드 참고 가능
결론
- uv는 Python 스크립트의 실행 환경, 의존성, 버전, 패키지 인덱스, 재현성 등을 자동으로 간편하게 관리해 생산성과 신뢰성을 동시에 높여주는 도구임
다른 기능은 제쳐두고, 단순히 속도만으로도 사용 할 이유가 충분합니다.
다시 pip 쓰라고 하면 절대 못할 정도입니다.
conda의 시스템 패키지 관리는 flake.nix로 대체해서 사용하고 있는데, 공동 작업이나 기존 conda+pip로 유지 관리되던 프로젝트 이외에 개인적으론 앞으로 uv+nix를 사용할 것 같습니다.
최근 대부분의 python 실행을 uv로 대체했는데 정말 빠릅니다.
완벽히 호환되지 않는 몇몇 고급 기능이 있긴 하지만 대부분의 경우 거의 똑같이 동작합니다.
Hacker News 의견
-
"스크립트 의존성 선언" 기능이 정말 유용함을 경험함
공식 가이드 문서에서 소개하듯, 다음과 같이 Python 코드 최상단에 주석으로 의존성을 명시할 수 있음# /// script # dependencies = [ # "requests<3", # "rich", # ] # /// import requests, rich # ... script이 파일을 script.py로 저장한 뒤 "uv run script.py"로 실행하면 명시된 의존성이 마법처럼 임시 가상 환경에 설치되어 곧바로 실행할 수 있음
이는 Python의 PEP 723를 구현한 것이며, Claude 4도 이 트릭을 알고 있으므로 “인라인 스크립트 의존성이 포함된 Python 스크립트”를 작성해달라고 프롬프트하면 올바르게 만들어줌
예제로 httpx와 click을 사용해 대용량 파일을 다운로드하고 진행률 바를 보여주는 코드 작성을 요청할 수 있음
Claude 4 이전엔 이런 기능을 위해 맞춤 프로젝트와 별도 안내가 필요했지만, 이제는 그렇지 않음
자세한 사용 사례에서도 참고할 수 있음-
shebang 모드도 정말 유용함을 느낌
아래와 같이 스크립트 첫 줄에 shebang을 추가하면 ./script.sh처럼 실행 가능함#!/usr/bin/env -S uv run --script # /// script # dependencies = [ # "requests<3", # "rich", # ] # /// import requests, rich # ... script -
requirements 파일과 같은 형식이면 좋겠다는 바람이 있음
만약 그렇게 되면 uv가 없는 사용자를 위해 간단한 주석으로 pip로 동일하게 설치할 수 있는 원라이너도 제공할 수 있기 때문임
예시로pip install -r <(head myscript.py)와 같은 접근이 가능할 듯함 -
실제로 PEP723은 요즘 주목받는 uv뿐 아니라 pipx, hatch에서도 지원 중임
그리고 pip-tools 등도 지원 로드맵에 포함되어 있음
(관련 이슈 참고) -
처음 봤을 때 requests 옆에 하트 이모지인 줄 알았던 적이 있음
-
이 방식이 정말 멋지다고 생각함
하지만 언젠가는 매직 주석이 아닌 내장 언어 문법으로 채택되었으면 좋겠음
주석은 약간 지저분해 보임
물론 도구 입장에선 매직 주석이 파싱엔 더 쉽고, Python 코어가 패키징 지식이 많지 않다는 등 구조적 고민이 있다는 것도 알지만, 언젠가는 내장 문법이 생기면 좋겠음
-
-
이러한 방식에 공감함
Python이 requirements.txt 파일이 필수는 아니지만, 관리를 소홀히 하면 기능이 깨지는 불편이 자주 생김이 아쉬움
관련 트윗 참고 -
이 방식에서 겪은 함정을 공유하고 싶음
인터넷이 끊겼을 때 라우터를 재시작하는 스크립트에 활용했는데, 의존성 설치 동작이 인터넷 연결에 의존하므로 네트워크가 안 되면 스크립트 자체가 작동 불가해지는 문제가 있음
미리 발견하고 의존성 사전 설치로 해결했지만, 나처럼 실수하지 말고 실제 airgapped 환경(네트워크가 완전 차단된 환경)에서는 사용하지 않기를 권장
uv 캐시가 있어도 캐시 미스가 날 수 있음-
uv run --offline옵션 사용 시 캐시된 의존성을 활용해서 새 버전 체크 없이 실행할 수 있음
같은 기능이uvx에도 동작함 (uvx --offline ...) -
의존성이나 venv를 사용할 일이 있다면 최소 한 번은 인터넷 연결에서 실행해야 그 후 오프라인에서도 쓸 수 있는 구조인 것으로 이해함
-
-
최근 Python 생태계에서 여러 기능들이 점점 잘 맞물려 동작하고 있다는 느낌을 받음
Marimo와 uv 스크립트 의존성 조합으로, 다른 팀에서 사용하기 좋은 재현 가능한 리포팅/진단 도구를 만들기 시작함 -
uv의 이 기능이 가장 마음에 들어 이 때문에 uv로 갈아타게 됨
여러 git-hooks 스크립트들이 각각 별도의 의존성을 가지는데, 이를 메인 venv에 설치하고 싶지 않았음
#!/usr/bin/env -S uv run --script --python 3.13한 줄만 추가하면 dev들에게는 brew install uv만 안내하면 됐고, venv를 따로 만들 필요 없이 스크립트 안에서 바로 사용할 수 있게 됨-
혹시
-S플래그가 왜 필요한지 아는 사람 있는지 궁금함
내 BSD 환경에서는/usr/bin/env -S uv run --python 3.11 python과/usr/bin/env uv run --python 3.11 python모두 Python shell을 실행해 결과가 똑같다 느꼈음
env 매뉴얼을 봐도 명확히 해석되지 않아 유용한 정보라면 듣고 싶음
(여기서 -S는 인자를 공백으로 나눠주는 역할임) -
UV 덕에 원래는 파이썬 대규모 이관 작업을 golang으로 할 계획이었는데, UV 덕분에 이 이관 범위를 줄일 수 있었음
특히 작은 스크립트 형태의 작업은 더 이상 이동할 필요가 없어짐 -
이 기능은 정말 ‘킬러(feature)’라고 확신함
-
-
의존성 중 Pytorch가 있는 경우엔 이 방식이 조금 제한이 있을 수 있음
Uv가 Pytorch 용 통합 지원을 잘 제공하고 있지만, 스크립트 헤더만으로는 (CPU, CUDA, ROCm 등) 가장 적합한 wheel 인덱스를 명확히 선택하는 방법이 없어 아쉬움 -
uv가 자동으로 만들어주는 venv을 VS Code가 쉽게 인식할 수 있었으면 하는 바람이 있음
지금은 Python extension이 써드파티 import를 모두 빨간 줄로 표시함
임시 해법으로 uv의 Cache 디렉터리에서 수동으로 venv 경로를 찾아 등록하는데, 자주 venv가 재생성되면 또 반복해야 해서 번거로움-
uv python find --script "${filePath}"명령어로 env 경로를 찾을 수 있음
해당 기능을 VS Code에서 자동 감지해 활성화해주는 확장 프로그램을 개발 중임
-
-
UV의 이 기능이 너무 마음에 듦
jupyter notebook도 별도 설치 없이 다음과 같이 원라인으로 실행할 수 있음uv run --with jupyter jupyter notebook모든 것이 임시 가상환경에 설치되고, 이후에는 깨끗이 정리됨
프로젝트 내에서 실행할 경우 해당 프로젝트의 의존성도 자동 인식함-
다만 완전히 ‘깨끗하게’ 정리되는 것은 아니며, uv 캐시 폴더가 계속 커질 수 있음
-
나 역시
uv run --with ipython --with boto3 ipython식으로 자주 활용 중이며, 정말 시간 절약에 큰 도움이 됨
-
-
최근
uv run관련해서 발견한 소소한 이슈가 있음
스크립트를 프로젝트 폴더 밖에서 실행하면, 실제 스크립트 파일 위치가 아닌 현재 작업 디렉터리에서 pyproject.toml을 찾는 동작이 있음
그래서 의존성을 pyproject.toml에 저장한 스크립트는 “uv run path/to/my/script.py” 식으로 외부에서 실행 시 제대로 동작하지 않을 수 있음
이 현상은 항상 인라인 의존성을 쓰거나,--project인자를 쓰면 해결할 수 있지만, script 경로를 두 번 입력해야 해서 불편함
uv 자체는 매우 훌륭하지만, 이 작은 특성은 꽤 불편하게 느껴짐 -
uv 전용 shebang과 인스크립트 의존성 방식을 만족스럽게 사용 중에 있었음
여기에 더해uv lock --script example.py명령어로 스크립트 한 개 전용 lock 파일까지 만들 수 있다는 점이 더욱 인상적임
Python 패키징이 20년 넘게 이어졌는데 이렇게 자연스러운 경험이 이제야 등장한 것에 놀라움- 단일 스크립트용 lock을 생성하는 사용 사례가 궁금함
우리 조직에선 lockfile의 의존성을trivy fs uv.lock등으로 스캔해, 알려진 CVE가 있는 코드 실행을 예방하는 데에도 활용하고 있음
- 단일 스크립트용 lock을 생성하는 사용 사례가 궁금함