홈으로 돌아가기

Python의 venv 아키텍처: pyvenv.cfg가 sys.path를 어떻게 관리하는가

이 글은 Python의 가상 환경이 pyvenv.cfg 파일과 sys.path 재계산을 통해 CPython 수준에서 종속성 격리를 제공하는 방법을 설명합니다. 저수준 아키텍처, 최소 환경 요구사항, 진단 방법을 다룹니다.

CPython이 pyvenv.cfg를 읽고 종속성을 격리하는 방법
Advertisement 728x90

Python 가상 환경의 격리 구현: 아키텍처, pyvenv.cfg, 그리고 저수준 sys.path 로직

Python의 가상 환경은 추상적인 개념이 아니라, 파일 시스템, 설정 파일, 그리고 인터프리터의 C 코드 간 상호작용을 조율하는 명확히 정의된 시스템입니다. 이들의 격리는 컨테이너화나 import 가로채기로 이루어지는 것이 아니라, 초기화 과정에서 모듈 로더의 동작을 결정적으로 변경하는 방식으로 달성됩니다. 이는 재현 가능한 빌드, CI/CD 파이프라인, 그리고 다중 프로젝트 개발 환경에 매우 중요합니다.

왜 virtualenv는 구식이고 venv는 pip 없이도 작동할까?

Python 3.3 이전에는 환경 격리가 외부 도구를 통해 처리되었습니다: virtualenv는 인터프리터 바이너리를 복사하고, 표준 라이브러리를 복제한 뒤, 래퍼를 통해 sys.path를 패치했습니다. 이는 환경 생성 속도를 느리게 하고, 디스크 공간을 더 많이 차지하며, 디버깅을 어렵게 만들었습니다. PEP 405이 도입되면서 이러한 메커니즘은 CPython의 핵심에 통합되었습니다. 주요 차이점은 바이너리 복사를 포기하고 대신 심볼릭 링크(리눅스/맥OS) 또는 런처(윈도우)를 사용함으로써 venv가 즉시 생성되고 설치된 Python 버전에만 의존하게 되었다는 점입니다.

주요 기술적 변화는 로직을 외부 도구에서 인터프리터 자체로 옮긴 것입니다. 환경 변수나 셸 래퍼를 통해 Python을 ‘속이는’ 방식 대신, venv는 내부 pyvenv.cfg 검색 알고리즘에 의존합니다. 이 단일 파일이 CPython으로 하여금 sys.path를 재구성하고 글로벌 site-packages를 비활성화하도록 만드는 트리거입니다.

Google AdInline article slot

CPython이 pyvenv.cfg를 읽고 sys.path를 재계산하는 방법

초기화는 C 수준 로더(Py_Initialize)에서 이루어집니다. 실행 파일이 실행되면 인터프리터는 다음과 같은 단계를 따릅니다:

  • 실행 파일의 위치를 확인합니다(예: /project/.venv/bin/python).
  • 부모 디렉토리로 올라가며 pyvenv.cfg 파일을 찾습니다.
  • 해당 파일을 INI 형식의 텍스트 파일로 파싱합니다—외부 의존 없이 CPython의 표준 파서만 사용합니다.
  • include-system-site-packages = false라면, 사용자 코드가 로드되기 전에 글로벌 경로를 sys.path에서 제외합니다.
  • 새로운 sys.path를 구성합니다: 먼저 현재 스크립트 경로, 그 다음 lib/pythonX.Y, 그리고 로컬 site-packages를 추가하고, 오직 명시적인 허가가 있을 때만 글로벌 패키지를 포함합니다.

즉, 환경을 활성화하지 않고 .venv/bin/python -c "import sys; print(sys.path)"를 직접 실행해도 격리가 보장됩니다. 환경 활성화(source .venv/bin/activate)는 $PATHVIRTUAL_ENV에만 영향을 미칠 뿐, 인터프리터의 동작에는 영향을 주지 않습니다.

최소한의 venv 구현: 정말 필요한 것들

작동하는 가상 환경을 만들기 위해서는 단 세 가지 요소만 있으면 됩니다—그 이상은 필요 없습니다. 기본적인 격리에는 pip, setuptools, 혹은 셸도 전혀 관여하지 않습니다.

Google AdInline article slot
  • 디렉토리 구조: bin/, lib/pythonX.Y/site-packages/
  • pyvenv.cfg 파일: home, include-system-site-packages, version, executable 정보를 포함
  • bin/에 있는 실행 파일: 유닉스에서는 심볼릭 링크, 윈도우에서는 런처로, 시스템 Python을 가리킵니다

나머지는 모두 편의 사항입니다: pip, activate, pyproject.toml. 리눅스에서 작동하는 최소한의 예제는 다음과 같습니다:

#!/bin/bash
ENV_DIR="my_venv"
mkdir -p "$ENV_DIR/bin" "$ENV_DIR/lib/python$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')/site-packages"

cat > "$ENV_DIR/pyvenv.cfg" <<EOF
home = $(dirname $(which python))
include-system-site-packages = false
version = $(python -c 'import sys; print(".".join(map(str, sys.version_info[:3])))')
executable = $(which python)
EOF

ln -sf "$(which python)" "$ENV_DIR/bin/python"

./my_venv/bin/python -c "import sys; print([p for p in sys.path if 'site-packages' in p])"를 실행하면 로컬 경로만 표시될 뿐, 타사 도구는 전혀 관여하지 않습니다.

핵심 요약

  • 가상 환경의 격리는 프로세스나 셸이 아니라, CPython의 상태입니다. 환경이 활성화될 때가 아니라, 인터프리터가 시작될 때 활성화됩니다.
  • pyvenv.cfg 파일은 유일하게 필수적인 아티팩트입니다. 이를 삭제하면 환경은 심볼릭 링크가 있는 일반 디렉토리로 변합니다.
  • sys.path파이썬 코드가 실행되기 전에 재구성됩니다—site.py까지 포함해서요. 글로벌 패키지는 물리적으로 접근할 수 없습니다.
  • Poetry, Pipenv 등 기타 의존성 관리자는 venv 위에 구축된 것입니다. 이들은 격리 로직을 변경하지 않으며, 단지 pyvenv.cfg 생성과 site-packages 관리를 자동화할 뿐입니다.
  • 심볼릭 링크는 안전합니다: CPython은 링크를 올바르게 해석하고, 링크의 경로가 아닌 pyvenv.cfg에 명시된 실제 home 경로를 사용합니다.

격리 문제 디버깅 및 진단

환경이 예상치 못한 동작을 보일 때는 pip list가 아니라, 인터프리터의 상태를 직접 확인해야 합니다:

Google AdInline article slot
  • pyvenv.cfg가 존재하고 include-system-site-packages = false로 설정되어 있는지 확인하세요.
  • .venv/bin/python -c "import sys; print(sys.prefix, sys.base_prefix, sys.path)"를 실행하여 prefixbase_prefix를 비교하세요.
  • sys.path에 로컬 site-packages 항목 하나만 들어있는지 확인하세요.
  • .venv/bin/python -c "import site; print(site.getsitepackages())"를 실행하여 결과가 단 하나의 항목으로 구성되어 있는지 확인하세요.

글로벌 패키지가 여전히 보인다면, pyvenv.cfg가 없거나 손상되었거나 include-system-site-packagestrue로 설정되어 있다는 뜻입니다. 어떤 환경 변수(PYTHONPATH, PYTHONHOME)도 이 동작을 무효화할 수 없습니다—이는 CPython에 하드코딩되어 있습니다.

— Editorial Team

Advertisement 728x90

다음 읽기