🔥 uv 완전 가이드 (1): 설치부터 스크립트 실행까지

#python#uv#astral#package-manager#pep-723
1104자
12분

uv Python 패키지 매니저

pip install을 치고 나서 터미널을 멍하니 바라본 적이 있다. 패키지 몇 개 설치하는 데 왜 이렇게 오래 걸리는 걸까. 의존성 해석이 빙글빙글 돌고, 가상환경 만들겠다고 python -m venv를 치면 또 몇 초를 기다려야 한다. 그러다 uv를 처음 써본 날, 진심으로 놀랐다. 같은 작업이 체감상 눈 깜짝할 사이에 끝났다.

이 글은 uv를 처음 접하는 사람을 위한 시작 가이드다. 설치부터 Python 버전 관리, 그리고 의존성을 스크립트 안에 직접 선언하는 방법까지 다룬다.

uv가 뭔가

uvAstral이 만든 Python 패키지 및 프로젝트 매니저다. Rust로 작성되었고, pip 대비 10~100배 빠르다. 같은 팀이 만든 Python 린터 Ruff를 써본 적 있다면, 그 속도감을 떠올리면 된다.

단순히 빠르기만 한 게 아니다. uv 하나로 이런 것들을 전부 처리할 수 있다:

  • Python 버전 설치 및 관리 (pyenv 대체)
  • 패키지 설치 (pip 대체)
  • 프로젝트 의존성 관리 (poetry, pdm 대체)
  • CLI 도구 실행 (pipx 대체)
  • 스크립트 실행 및 인라인 의존성
  • 패키지 빌드와 배포

2026년 4월 현재 최신 버전은 0.11.7이고, GitHub 스타는 83,000개를 넘겼다. 이미 프로덕션에서 널리 쓰이고 있다.

벤치마크

Astral의 공식 벤치마크에 따르면, 캐시가 없는 상태에서 pip보다 8~10배, 웜 캐시 상태에서는 80~115배 빠르다. 가상환경 생성은 python -m venv 대비 최대 80배, virtualenv 대비 7배 빠르다.

숫자를 외울 필요는 없다. 직접 써보면 체감된다. pip으로 수십 초 걸리던 설치가 1~2초 만에 끝나는 경험을 하면 돌아가기 어렵다.

설치

설치는 한 줄이다.

macOS / Linux:

bash
curl -LsSf https://astral.sh/uv/install.sh | sh
bash
curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell):

powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Homebrew를 쓴다면:

bash
brew install uv
bash
brew install uv

설치 후 버전을 확인해 보자:

bash
uv --version
# uv 0.11.7
bash
uv --version
# uv 0.11.7

pip로도 설치할 수 있지만, 독립 설치를 권장한다. uv 자체가 Python에 의존하지 않는 단일 바이너리라서, 시스템 Python이 꼬여도 uv는 멀쩡하게 동작한다.

Python 버전 관리

uv의 가장 편리한 기능 중 하나가 Python 버전 관리다. pyenv를 따로 설치할 필요가 없다.

최신 버전 설치

bash
uv python install
bash
uv python install

이 한 줄이면 최신 안정 버전의 Python이 설치된다. uv는 Astral의 python-build-standalone 프로젝트에서 배포판을 가져온다. 공식 Python에서는 바이너리를 직접 배포하지 않기 때문이다.

설치 후 버전 지정 실행 파일이 PATH에 추가된다:

bash
python3.13  # 이렇게 바로 실행 가능
bash
python3.13  # 이렇게 바로 실행 가능

pythonpython3 명령도 쓰고 싶다면 --default 옵션을 붙인다:

bash
uv python install --default
bash
uv python install --default

특정 버전 설치

bash
# 특정 마이너 버전
uv python install 3.12
 
# 여러 버전 한 번에
uv python install 3.11 3.12
 
# PyPy도 된다
uv python install pypy@3.10
bash
# 특정 마이너 버전
uv python install 3.12
 
# 여러 버전 한 번에
uv python install 3.11 3.12
 
# PyPy도 된다
uv python install pypy@3.10

설치된 버전 확인

bash
uv python list
bash
uv python list

사용 가능한 버전과 이미 설치된 버전을 한눈에 볼 수 있다.

버전 업그레이드

패치 버전 업그레이드도 지원한다 (현재 프리뷰 기능):

bash
# 특정 버전 업그레이드
uv python upgrade 3.12
 
# 전체 업그레이드
uv python upgrade
bash
# 특정 버전 업그레이드
uv python upgrade 3.12
 
# 전체 업그레이드
uv python upgrade

자동 다운로드

사실 Python을 미리 설치하지 않아도 된다. uv는 필요할 때 알아서 Python을 다운로드한다.

bash
# Python 3.12가 없어도 바로 실행된다
uvx python@3.12 -c "print('hello world')"
 
# 가상환경 생성 시에도 자동 다운로드
uv venv
bash
# Python 3.12가 없어도 바로 실행된다
uvx python@3.12 -c "print('hello world')"
 
# 가상환경 생성 시에도 자동 다운로드
uv venv

시스템에 이미 Python이 깔려 있다면 uv가 자동으로 감지해서 사용한다. 별도 설정은 필요 없다. 만약 시스템 Python만 쓰고 싶으면 --no-managed-python 플래그를 넘기면 된다.

스크립트 실행

여기서부터가 진짜 재미있는 부분이다.

기본 실행

간단한 스크립트를 하나 만들어 보자:

python
# hello.py
print("Hello from uv!")
python
# hello.py
print("Hello from uv!")
bash
uv run hello.py
# Hello from uv!
bash
uv run hello.py
# Hello from uv!

표준 라이브러리만 쓰는 스크립트라면 이게 전부다. 인자도 그대로 넘길 수 있다:

bash
uv run hello.py arg1 arg2
bash
uv run hello.py arg1 arg2

stdin에서 직접 읽는 것도 가능하다:

bash
echo 'print("hello")' | uv run -
bash
echo 'print("hello")' | uv run -

외부 패키지가 필요할 때

스크립트에서 외부 패키지를 쓴다면 --with 옵션으로 지정한다:

python
# progress.py
import time
from rich.progress import track
 
for i in track(range(20), description="작업 중:"):
    time.sleep(0.05)
python
# progress.py
import time
from rich.progress import track
 
for i in track(range(20), description="작업 중:"):
    time.sleep(0.05)
bash
uv run --with rich progress.py
bash
uv run --with rich progress.py

uv가 임시 환경을 만들고, rich를 설치하고, 스크립트를 실행한 뒤, 환경을 정리한다. 전부 순식간에 일어난다.

버전 제약도 걸 수 있다:

bash
uv run --with 'rich>12,<13' progress.py
bash
uv run --with 'rich>12,<13' progress.py

PEP 723: 인라인 스크립트 메타데이터

여기가 핵심이다. PEP 723은 Python 스크립트 파일 안에 의존성을 직접 선언하는 표준이다. 매번 --with를 붙이는 대신, 스크립트 자체에 어떤 패키지가 필요한지 적어둘 수 있다.

먼저 스크립트를 초기화한다:

bash
uv init --script example.py --python 3.12
bash
uv init --script example.py --python 3.12

의존성을 추가한다:

bash
uv add --script example.py 'requests<3' 'rich'
bash
uv add --script example.py 'requests<3' 'rich'

그러면 스크립트 상단에 이런 메타데이터 블록이 생긴다:

python
# /// script
# dependencies = [
#   "requests<3",
#   "rich",
# ]
# ///
 
import requests
from rich.pretty import pprint
 
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])
python
# /// script
# dependencies = [
#   "requests<3",
#   "rich",
# ]
# ///
 
import requests
from rich.pretty import pprint
 
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])

이제 uv run example.py만 치면 된다. uv가 메타데이터를 읽고, 의존성을 설치하고, 실행까지 알아서 한다. --with를 매번 붙일 필요가 없다.

Python 버전 요구사항도 지정할 수 있다:

python
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///
 
type Point = tuple[float, float]
print(Point)
python
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///
 
type Point = tuple[float, float]
print(Point)

uv에서는 dependencies 필드가 비어 있더라도 반드시 포함되어야 한다.

의존성 잠금

스크립트 의존성도 잠글 수 있다:

bash
uv lock --script example.py
bash
uv lock --script example.py

example.py.lock 파일이 생기고, 이후 실행할 때 잠긴 버전을 사용한다. 재현성이 중요한 스크립트라면 꼭 쓰자.

더 엄격하게 재현성을 보장하고 싶다면 exclude-newer 설정을 쓸 수 있다:

python
# /// script
# dependencies = [
#   "requests",
# ]
# [tool.uv]
# exclude-newer = "2023-10-16T00:00:00Z"
# ///
 
import requests
print(requests.__version__)
python
# /// script
# dependencies = [
#   "requests",
# ]
# [tool.uv]
# exclude-newer = "2023-10-16T00:00:00Z"
# ///
 
import requests
print(requests.__version__)

이렇게 하면 uv가 2023년 10월 16일 이전에 릴리스된 패키지만 고려한다. 시간이 지나도 같은 결과를 보장할 수 있다.

실행 가능한 스크립트 만들기

shebang을 추가하면 uv run 없이도 바로 실행할 수 있다:

python
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = ["httpx"]
# ///
 
import httpx
print(httpx.get("https://example.com"))
python
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = ["httpx"]
# ///
 
import httpx
print(httpx.get("https://example.com"))
bash
chmod +x myscript
./myscript
bash
chmod +x myscript
./myscript

스크립트를 다른 사람에게 전달할 때, "이 파일 하나만 실행하면 돼"라고 말할 수 있다는 뜻이다. uv가 설치되어 있기만 하면 Python 버전이든 패키지든 알아서 처리된다.

Python 버전 바꿔가며 실행

같은 스크립트를 다른 Python 버전으로 실행하는 것도 간단하다:

bash
uv run example.py          # 기본 버전
uv run --python 3.10 example.py  # 3.10으로 실행
uv run --python 3.12 example.py  # 3.12로 실행
bash
uv run example.py          # 기본 버전
uv run --python 3.10 example.py  # 3.10으로 실행
uv run --python 3.12 example.py  # 3.12로 실행

필요한 버전이 없으면 uv가 알아서 다운로드한다.

다음 글에서는

여기까지가 uv의 기본기다. Python 설치, 버전 관리, 스크립트 실행까지 — 이전에 pyenv + pip + venv로 따로따로 하던 일을 uv 하나로 끝낼 수 있다.

다음 글에서는 uv로 본격적인 프로젝트를 관리하는 방법을 다룬다. uv init으로 프로젝트를 만들고, 의존성을 관리하고, uvx로 CLI 도구를 실행하는 것까지. 프로젝트 단위로 uv를 쓰면 생산성이 확 달라진다.


시리즈: uv 완전 가이드

참고 자료

YouTube 영상

채널 보기
마지막편, 트라이 노드를 50% 이상 줄이는 방법? 압축 트라이 성능 분석 | Trie 자료구조 이야기
투영과 예측, 그리고 선형 결합 | 선형대수학
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
Trie 자료구조 파이썬 구현: Search와 Starts With 연산 | Trie 자료구조 이야기
내적의 기하학적 의미와 코사인 유사도 원리 | 선형대수학
AI를 위한 선형대수학 - 소개 | 선형대수학
AI는 왜 수백 차원의 벡터를 사용할까? 고차원 공간과 행렬 | 선형대수학
직교성과 벡터 투영 | 선형대수학