3.1 Interpreter és virtuális környezet

3.1.1 Cél és konkrét helyzet

Egy elemzés akkor reprodukálható, ha nemcsak a kódot, hanem a kódot futtató környezetet is ismerjük. A gyakori hibajelenség: a notebookban lefutó script egy másik gépen nem találja a NumPy-t, vagy éppen más verziót tölt be, mint amit a szerző látott. A diagnosztika első lépése nem a csomagok újratelepítése, hanem a futó folyamat önazonosítása.

A konkrét feladat bemenete: terminálparancsok egy meghatározott munkakönyvtárban. A megfigyelési egység: egy elindított Python-folyamat, pontosabban annak interpreter- és modulkeresési állapota. A kimenet: környezetjelentés, amely tartalmazza a sys.executable, sys.prefix, sys.path, Python-verzió, illetve a NumPy verzióját és betöltési fájlját. Az ellenőrzési feltétel: ha a kódot a .venv-a/bin/python indította, akkor a jelentésben a sys.prefix a .venv-a mappára mutat, a numpy.__file__ pedig ugyanezen prefix alatti site-packages-ben kezdődik. Előzetes becslés: a .venv-a és .venv-b két jelentése eltérő NumPy-verziót ír ki, miközben a Python-verzió azonos marad. A futtatott példák ebben a dokumentumban nem ténylegesen mért kimenetek; ahol fájl- vagy verzióadat szerepel, ott „szemléltető” jelölést használunk.

3.1.2 Fogalmi magyarázat: interpreter, venv, sys.path és PATH

Az interpreter (értelmező) az a program, amely a Python-forrást gépi végrehajtásra alakítja. A tanulási alap itt a CPython 3.12, tehát a hivatalos C-ben írt interpreter. A python3.12 parancs nem magát a nyelvet jelöli, hanem egy konkrét futtatható fájlt. A folyamaton belül a sys.executable mondja meg, hogy az adott futó Python melyik fájlból indult:

import sys
print(sys.executable)

Szemléltető kimenet: /home/user/ds-work/s009/.venv-a/bin/python. A sys.version ezzel szemben a futtató interpreter verzióját adja; a kettő együtt használható gépnaplóba, mert egyazon python név mögött is több verzió állhat a PATH sorrendjétől függően.

A virtuális környezet (virtual environment, venv) nem a Python másolata, hanem egy mappa, amely saját interpreter-hivatkozást és saját csomagtelepítési területet tart fenn. A python -m venv .venv-a parancs létrehozza például a következő szerkezetet Linuxon:

A pyvenv.cfg egyik kulcsa a include-system-site-packages = false. Ez az alapértelmezés biztosítja, hogy a rendszer Python csomagjai nem látszanak a venv-ből, tehát a NumPy telepítése a venv-be ténylegesen izolált marad. A venv tehát a Python saját környezetizolációs eszköze; a conda/mamba alternatíva ezen túl natív binárisokat és akár más Python-verziót is telepíthet, ezért a választást a későbbi s011 alfejezet tárgyalja.

A sys.path a modulkeresés útvonalainak listája. Amikor a kód import numpy-t ír, a Python e listán sorban keresi a modult. Script futtatásakor az első elem a script könyvtára, -c kapcsolóval vagy interaktív módban pedig az aktuális munkakönyvtárat jelölő üres string. A venv site-packages könyvtára a lista végén szerepel, de a konkrét betöltést nem a lista puszta léte, hanem a modul __file__ attribútuma bizonyítja:

import numpy as np
print(np.__file__)

Szemléltető kimenet: /home/user/ds-work/s009/.venv-a/lib/python3.12/site-packages/numpy/__init__.py. Ha ez az útvonal nem a sys.prefix alatt kezdődik, akkor a NumPy máshonnan jött, például a PYTHONPATH környezeti változóból vagy a rendszer Python telepítéséből.

A PATH ezzel szemben operációsrendszer-szintű keresőlista: amikor a terminálban a python vagy a pip parancsot begépeljük, az operációs rendszer e lista elemeit járja végig. Linuxon a which python csak az első találatot mutatja, a type -a python viszont az összes jelöltet; Windowson a Get-Command python -All a PowerShell-megfelelő. A source .venv-a/bin/activate parancs épp ezt a listát írja át az adott terminálablakban: a venv bin könyvtára kerül előre. A direkt interpreter-hívás, például .venv-a/bin/python script.py, megkerüli ezt a PATH-függést, mert a teljes útvonalat adjuk meg.

A pip a Python csomagtelepítője. A pip parancs valójában egy Python-szkript, amelynek saját interpreter-kötése van; a python -m pip viszont az adott python-hoz tartozó pip modult futtatja. Ez a különbség akkor válik fontossá, ha a PATH-on a pip más Pythonhoz tartozik, mint a python. A pip --version kimenete elárulja a hozzá tartozó Pythont:

Szemléltető kimenet: pip 24.2 from /home/user/ds-work/s009/.venv-a/lib/python3.12/site-packages/pip (python 3.12). A python -m pip show numpy parancs Location mezője pedig a telepített NumPy mappáját adja; ennek egyeznie kell a futó kód numpy.__file__ útvonalának elejével.

3.1.3 Teljes minimális munkapélda: két környezet, egy jelentés

A következő munkamenet Linuxon vagy macOS-en bash-terminálban fut, CPython 3.12 és PyPI-hálózat elérhetősége mellett. A munkakönyvtár legyen ~/ds-work/s009. A példa a venv/pip alternatív utat mutatja; a könyv fő útja az uv, amelyet a következő, s010 alfejezet vezet be.

mkdir -p ~/ds-work/s009
cd ~/ds-work/s009
python3.12 -m venv .venv-a
python3.12 -m venv .venv-b

.venv-a/bin/python -m pip install numpy==1.26.4
.venv-b/bin/python -m pip install numpy==2.0.2

A verziószámok itt példák; a ténylegesen elérhető verziókat a PyPI aktuális listája adja meg, ezért a helyi futtatáskor a telepítési log rögzítendő. Ezután hozzunk létre egyetlen env_report.py fájlt:

import json
import sys

def main():
    try:
        import numpy as np
    except ImportError:
        np = None

    report = {
        "executable": sys.executable,
        "prefix": sys.prefix,
        "python_version": (
            f"{sys.version_info.major}."
            f"{sys.version_info.minor}."
            f"{sys.version_info.micro}"
        ),
        "sys_path": list(sys.path),
        "numpy_version": None if np is None else np.__version__,
        "numpy_file": None if np is None else str(np.__file__),
    }
    print(json.dumps(report, indent=2))

if __name__ == "__main__":
    main()

A scriptet mindkét környezet interpreterével futtatjuk, aktiválás nélkül:

.venv-a/bin/python env_report.py > report-a.json
.venv-b/bin/python env_report.py > report-b.json

A report-a.json szemléltető, elméletileg várt tartalma:

{
  "executable": "/home/user/ds-work/s009/.venv-a/bin/python",
  "prefix": "/home/user/ds-work/s009/.venv-a",
  "python_version": "3.12.7",
  "sys_path": [
    "/home/user/ds-work/s009",
    "/home/user/ds-work/s009/.venv-a/lib/python312.zip",
    "/home/user/ds-work/s009/.venv-a/lib/python3.12",
    "/home/user/ds-work/s009/.venv-a/lib/python3.12/lib-dynload",
    "/home/user/ds-work/s009/.venv-a/lib/python3.12/site-packages"
  ],
  "numpy_version": "1.26.4",
  "numpy_file": "/home/user/ds-work/s009/.venv-a/lib/python3.12/site-packages/numpy/__init__.py"
}

A report-b.json ugyanezen a gépen csak a .venv-b útvonalakban és a NumPy-verzióban tér el, 2.0.2 értékkel. Windowson a munkamenet szerkezete azonos, de a binárisok a Scripts mappában vannak, és a py launchert használjuk:

mkdir $HOME\ds-work\3.1
cd $HOME\ds-work\3.1
py -3.12 -m venv .venv-a
py -3.12 -m venv .venv-b
.venv-a\Scripts\python.exe -m pip install numpy==1.26.4
.venv-b\Scripts\python.exe -m pip install numpy==2.0.2
.venv-a\Scripts\python.exe env_report.py | Out-File -Encoding utf8 report-a.json

A sys.executable itt például C:\Users\user\ds-work\3.1\.venv-a\Scripts\python.exe alakú.

3.1.4 Eredmény értelmezése

A két jelentés együtt bizonyítja az izolációt: azonos env_report.py kód, azonos Python-verzió, de eltérő prefix, eltérő site-packages és eltérő NumPy-verzió. A kézi kontroll invariáns: a numpy_file stringnek a prefix stringgel kell kezdődnie. Ha ez nem teljesül, a telepítés nem a várt környezetbe került, vagy egy PYTHONPATH elem elé vág. A sys_path első eleme itt /home/user/ds-work/s009, tehát a script könyvtára; ez nem a NumPy forrása, ezért nem szabad a modulkeresés bizonyítékaként a lista puszta jelenlétére hagyatkozni.

Az aktiválás elhagyása szándékos. A .venv-a/bin/python teljes útvonalas hívás akkor is ugyanoda mutat, ha a terminál PATH-ja közben megváltozott. A source .venv-a/bin/activate csak kényelmi réteg: rövid parancsneveket ad, de nem változtatja meg a már futó folyamatok környezetét. A környezetjelentés tehát akkor értékes, ha a tényleges futtató interpreterrel készül, nem a terminál aktuális PATH-állapotából következtetünk.

Interpreterazonosság. A python, python3 és py parancs ugyanazon gépen sem feltétlenül ugyanazt az interpretert választja. A telepítési és ellenőrzési példákban a kiválasztott interpretert a sys.executable értékével ellenőrizzük, és a csomagkezelést ehhez kötjük: python -m pip .... A virtuális környezet izolációja elsősorban a Python-csomagokra vonatkozik, nem teljes operációsrendszer-szintű konténerizáció.

Ellenpróba: a környezet aktiválása után futtasd a python -c 'import sys; print(sys.executable)' ellenőrzést, majd a notebook kernelében is nézd meg ugyanazt az értéket. Ha különböznek, a notebook nem bizonyítottan a kívánt környezetből fut.

3.1.5 Feladatok

  1. Értelmezés. Egy terminálban a which python az /usr/bin/python útvonalat írja ki, de a python -c "import sys; print(sys.executable)" parancs a /home/user/ds-work/s009/.venv-a/bin/python értéket adja. Nevezzen meg legalább két lehetséges magyarázatot, és adja meg azt a parancsot, amellyel az összes python jelöltet kilistázná.

  2. Implementálás. Egészítse ki az env_report.py scriptet egy --check kapcsolóval. A kapcsoló jelenléte esetén a script ne csak kiírja a jelentést, hanem ellenőrizze is, hogy a betöltött NumPy __file__ útvonala a sys.prefix alatt kezdődik, és a futtató Python legalább 3.12-es. Ha bármelyik feltétel sérül, 1 kilépési kóddal térjen vissza; sikeres ellenőrzésnél 0-val. Ügyeljen az útvonal-normalizálásra Windows és Linux esetén.

  3. Hibadiagnózis. Egy felhasználó a rendszerterminálban kiadta a pip install numpy==2.0.2 parancsot, de a már megnyitott Jupyter notebookban az import numpy as np; np.__version__ továbbra is 1.26.4. Magyarázza el a két legvalószínűbb okot, és írja le azokat a parancsokat, amelyeket a notebook cellájában, illetve a terminálban futtatva eldönthető, melyik környezetbe települt a 2.0.2-es NumPy.

  4. Megváltozott feltétel. Egy következő gépen nincs python3.12 nevű parancs: a python3 a CPython 3.12-t, a python viszont csak 3.9-et indít. Írjon egy ellenőrző parancsot a venv létrehozása előtt, amely megtagadja a futást, ha a kiválasztott interpreter 3.12-nél régebbi, majd a python3 használatával hozza létre a .venv-a környezetet. Választhat bash vagy PowerShell megoldást.

3.1.6 Megoldások és indoklás

1. megoldás. Az egyik magyarázat, hogy a source .venv-a/bin/activate után a shell PATH-ja tartalmazza a .venv-a/bin mappát, de a which egy másik terminálablakból futott. A másik, hogy a shell a korábbi python útvonalat gyorsítótárban tartja (hash), és az aktiválás után hash -r nélkül a régi találatot adja vissza. A teljes lista Linuxon: type -a python; Windowson PowerShellben: Get-Command python -All. Az ellenőrzés lényege: a which egyetlen találat, a type -a az összes PATH-jelölt.

2. megoldás. A kiegészített script kulcsrésze:

import argparse
from pathlib import Path

def check(report):
    prefix = Path(sys.prefix).resolve()
    ok = True
    if report["numpy_file"] is not None:
        np_file = Path(report["numpy_file"]).resolve()
        try:
            np_file.relative_to(prefix)
        except ValueError:
            ok = False
    if sys.version_info < (3, 12):
        ok = False
    return ok

A Path.resolve() szimbolikus hivatkozásokat és relatív elemeket is abszolút útvonallá alakít, így a relative_to nem szembesül a Windows C:\ és Linux / alakok keveredésével. A --check argumentumot az argparse elemzi; hiba esetén sys.exit(1), siker esetén sys.exit(0). Az invariant tehát nem string-összehasonlítás, hanem „a NumPy fájl a prefix mappa alatt van”.

3. megoldás. A legvalószínűbb ok, hogy a terminál pip parancsa nem a notebook kerneléhez tartozó Pythonhoz kötődik, hanem egy másik környezethez. A notebook kernel ugyanis már futó folyamat; a terminál telepítése nem módosítja annak sys.path állapotát. A diagnosztika a notebook cellájában:

import sys
print(sys.executable)
print(sys.prefix)
import numpy as np
print(np.__version__)
print(np.__file__)

A terminálban ugyanezt a környezetet a python -m pip show numpy Location mezőjével vetjük össze. Ha a notebook sys.executable értéke .venv-b/bin/python, a telepítés pedig a rendszer Pythonba ment, a kettő természetesen eltér. A javítás nem a PATH átírása, hanem a notebook kernelének újraindítása a megfelelő interpreterrel, vagy a telepítés a kernel interpreterével: import sys; !{sys.executable} -m pip install numpy==2.0.2.

4. megoldás. Bash változat:

python3 -c "import sys; sys.exit(0 if sys.version_info >= (3, 12) else 1)" \
  && python3 -m venv .venv-a

PowerShell változat, ha a py launcher elérhető:

py -3.12 -c "import sys; assert sys.version_info >= (3, 12), sys.version"
py -3.12 -m venv .venv-a

A feltétel célja, hogy a 3.9-es python soha ne kerüljön a létrehozó parancsba. A python3 név itt verzióellenőrzés után kap szerepet; ha a gépen a python3 is 3.11 lenne, a parancs a venv létrehozása előtt leáll, ami a kívánt viselkedés.

3.1.7 Gyakori tévedés ellenpéldával

A leggyakoribb tévedés: a terminálban aktivált környezet automatikusan átváltja a már futó notebook kernelét. Tegyük fel, hogy a notebook a .venv-b kernellel fut; a felhasználó ezután egy másik terminálban kiadja a source .venv-a/bin/activate parancsot, majd a notebook cellájába írja: import numpy as np; print(np.__version__). A kimenet továbbra is a .venv-b NumPy verzióját adja, mert a kernel folyamata már elindult, és az aktiválás csak az adott terminál jövőbeli gyermekfolyamatainak PATH-ját módosította. A hibás következtetés így hangzik: „most már jó, hiszen a terminálban aktiválva van a környezet”. A helyes ellenőrzés a notebookban a sys.executable kiíratása; ha az még .venv-b/bin/python, akkor a környezet nem váltott. Ez a probléma rokona annak, amit az s005 alfejezet tárgyalt: a notebook kernel CWD-je sem a notebook mappája automatikusan, ezért minden futó folyamatot a saját állapotából kell bizonyítani, nem a terminál látszólagos állapotából.

3.1.8 Önellenőrzés

3.1.9 Rövid összefoglaló

A futó környezet kiléte nem hitkérdés: a sys.executable, a sys.prefix, a sys.path és a modul __file__ mezője együtt megmutatja, melyik interpreter és melyik site-packages szolgálja ki a kódot. A venv akkor izolál, ha a megfelelő interpretert indítjuk; a PATH és a csupasz pip parancs eltérése gyakori forrása a csendes keveredésnek. Két környezetre lefuttatott környezetjelentés a verziók és útvonalak összevetésével bizonyítja a szétválasztást. A következő alfejezet ezt a szerződést szigorítja: az uv zárolt függőségfeloldást készít, és a --locked ellenőrzést teszi a reprodukálhatóság kapujává.

3.1.10 Kiadás előtti szakmai ellenőrzés

Platformellenőrzés. Windows PowerShellben aktiválás nélkül is ellenőrizhető a környezet saját interpretere: ./.venv/Scripts/python.exe --version. Unix-szerű rendszeren a megfelelő útvonal ./.venv/bin/python --version. A rendszer python parancsának elérhetősége önmagában nem bizonyítja, hogy az izolált környezet aktív.