Az 2.2 alfejezetben létrehozott scripts/s006_diagnose.py
és a reports/s006 kimenetek már a munkakönyvtárban vannak.
A data/raw/orders_raw.csv a Northstar nyers adata, amely a
generátorral újra előállítható, ezért nem akarjuk a Git-történetbe
zárni. A feladat most az, hogy egy új feldolgozási ellenőrző szkriptet,
a hozzá tartozó titkokat kizáró szabályt és egy továbbfejlesztett
változatot három külön, visszakereshető commitba
(véglegesítésbe) szervezzünk.
Bemenet: a fenti könyvtárállapot és a
data/raw/orders_raw.csv. Megfigyelési egység: egy-egy
fájlváltozat a Git által rögzített pillanatképekben. Cél: a három commit
logikailag különválik, a nyers adat és a .env titokfájl
soha nem kerül a repóba. Előzetes becslés: a .gitignore
után a repó mérete néhány száz KB alatt marad, mert a szándékosan hibás
CSV-t a Git nem tárolja.
A Git három területet különböztet meg: a working
tree (munkakönyvtár) a ténylegesen látható fájlokat, a
staging area vagy index az
előkészített, következő commitba szánt állapotot, a
HEAD pedig az aktuális commit által mutatott
pillanatképet jelenti. A git add a working tree-ből az
indexbe másol, a git commit az indexből új,
megváltoztathatatlan pillanatképet hoz létre. Minden commitnak egyedi
SHA-azonosítója van; a rövid forma például a3f9c21. A
commit üzenet a „miért”-et rögzíti, nemcsak a „mit”-et.
A branch (ág) egy mozgatható névcímke, amely egy
commitra mutat. Egy kísérleti változtatást saját ágon érdemes végezni,
hogy a főág, például main, továbbra is a legutóbbi ismert
jó állapotot mutassa. A git switch -c experiment új ágat
hoz létre; a commitok azon az ágon haladnak előre, a main
pedig nem mozdul.
A diff (különbség) két állapot szövegszintű
összehasonlítása. A git diff a working tree és az index, a
git diff --staged az index és a HEAD, a
git diff HEAD a working tree és a HEAD közötti eltérést
mutatja. A kimenet úgynevezett hunkokba rendeződik. Egy
hunk fejlécében a @@ -a,b +c,d @@ azt jelenti, hogy a régi
fájl a. sorától b sor, az új fájl
c. sorától d sor érintett. A mínuszos sor
törlés, a pluszos sor hozzáadás. Például:
@@ -1,6 +1,7 @@
"""Ellenőrzi az orders_raw.csv szerződését a Northstar adatszerződés szerint."""
from pathlib import Path
import csv
import sys
+from collections import CounterEz azt mutatja, hogy az új változatban a Counter import
bekerült, és a régi 6 sor helyett 7 sor szerepel; tartalmi konfliktus
nincs, a környező sorok kontextusként szolgálnak.
A .gitignore fájl olyan mintákat tartalmaz,
amelyekre illeszkedő, még nem követett fájlokat a Git figyelmen kívül
hagy. A minták például data/raw/ könyvtárat,
.env titokfájlt, __pycache__/ mappát,
*.pyc bájtkódot vagy *.joblib modellfájlt
jelölhetnek. Fontos: a szabály csak a még nem indexelt fájlokra hat; ha
egy fájl korábban már bekerült a commitba, a .gitignore
önmagában nem távolítja el a követésből.
A Jupyter-notebookok .ipynb kiterjesztésű JSON-fájlok,
így a nyers git diff a cellakimenetek és a metaadatok miatt
zajos. Jó gyakorlat a notebookot kimenet nélkül commitolni, például
jupyter nbconvert --to notebook --ClearOutputPreprocessor.enabled=True --inplace analysis/eda.ipynb
paranccsal, vagy a végső logikát külön Python-szkriptbe exportálni. A
nbdime nevű eszköz notebookbarát diffet ad, de nem kötelező
telepíteni; a tanulási főút a notebookból scriptté alakítást részesíti
előnyben.
A nagy binárisok határa: a Git a pillanatképekben a teljes fájltartalmat tárolja, még tömörítve is. Egy 200 MB-os Parquet-fájl minden commitban újabb másolatot jelent; a diff nem értelmes, a repó mérete gyorsan nő. Ezért a nagy nyers adat, a generált feature-tábla, a modellfájl és a notebook checkpoint nem a Git-repó tartalma, hanem külön, verziózott adattároló vagy az újragenerálást leíró kód része.
Az alábbi munkamenet Linux/macOS Bash héjon fut, munkakönyvtára
~/projects/northstar-lab. A
data/raw/orders_raw.csv a 2.2 alatt már létrejött, a
scripts/s006_diagnose.py pedig a repóban lesz. A kimenetek
szemléltetőek, nem mért futtatási jegyzék; a parancsok a leírt
állapotból kiindulva ezt az eredményt adják, de a commit-hash-ek
gépenként eltérhetnek.
Először inicializáljuk a repót és rögzítjük a helyi azonosítót:
git init -b main
git config user.name "N. Diák"
git config user.email "diak@example.com"Létrehozzuk az első ellenőrző szkriptet, amely a Northstar szerződés
szerint a data/raw/orders_raw.csv oszlopait és a hiányzó
distance_km értékeket vizsgálja:
"""Ellenőrzi az orders_raw.csv szerződését a Northstar adatszerződés szerint."""
from pathlib import Path
import csv
import sys
RAW = Path("data/raw/orders_raw.csv")
def main() -> int:
if not RAW.exists():
print(f"missing input: {RAW}", file=sys.stderr)
return 2
with RAW.open(newline="") as f:
reader = csv.DictReader(f)
fieldnames = reader.fieldnames or []
rows = list(reader)
required = {"order_id", "customer_id", "order_ts", "distance_km", "basket_minor"}
missing_cols = required - set(fieldnames)
if missing_cols:
print(f"missing columns: {sorted(missing_cols)}", file=sys.stderr)
return 3
missing_distance = sum(1 for r in rows if r["distance_km"] == "")
print(f"rows={len(rows)} missing_distance_km={missing_distance}")
return 0 if missing_distance == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())Ez az első commit. A szakaszos mentés a git add és a
git commit páros:
mkdir -p scripts
# a fenti tartalmat a scripts/s007_check_orders.py fájlba mentjük
git add scripts/s007_check_orders.py
git commit -m "Add initial orders_raw contract checker
"A második commit a .gitignore szabályt rögzíti. A nyers
adat és a titokfájl kimarad:
cat > .gitignore <<'EOF'
data/raw/
.env
artifacts/
__pycache__/
*.pyc
EOF
git add .gitignore
git commit -m "Ignore raw data and local secrets"A harmadik commit kiterjeszti a szkriptet a duplikált
order_id és a negatív basket_minor
ellenőrzésével. A git diff először a working tree és az
index közti változást mutatja; feltesszük, hogy a módosítás már
megtörtént. A Counter import hunkja a fenti fogalmi
példában látható, a további hunk pedig:
@@ -19,4 +20,8 @@ def main() -> int:
if missing_cols:
print(f"missing columns: {sorted(missing_cols)}", file=sys.stderr)
return 3
missing_distance = sum(1 for r in rows if r["distance_km"] == "")
- print(f"rows={len(rows)} missing_distance_km={missing_distance}")
- return 0 if missing_distance == 0 else 1
+ ids = [r["order_id"] for r in rows]
+ duplicates = {k: v for k, v in Counter(ids).items() if v > 1}
+ negative_basket = sum(1 for r in rows if r["basket_minor"] and int(r["basket_minor"]) < 0)
+ print(f"rows={len(rows)} missing_distance_km={missing_distance} "
+ f"duplicate_order_ids={sum(duplicates.values())} negative_basket_minor={negative_basket}")
+ return 0 if missing_distance == 0 and not duplicates and negative_basket == 0 else 1A szkript tetején a from collections import Counter sor
is megjelenik. A commit:
git add scripts/s007_check_orders.py
git commit -m "Add duplicate and negative basket checks"A végeredmény ellenőrzése:
git log --oneline --reverse
git ls-files data/raw
git check-ignore -v data/raw/orders_raw.csvSzemléltető kimenet:
a3f9c21 Add initial orders_raw contract checker
b7e41d2 Ignore raw data and local secrets
c2d88e5 Add duplicate and negative basket checks
A git ls-files data/raw üres kimenete azt igazolja, hogy
a nyers fájl nincs a commitokban. A git check-ignore -v
például a .gitignore:1:data/raw/ data/raw/orders_raw.csv
sort írja ki, tehát a szabály ténylegesen illeszkedik.
A három commit logikailag elkülönül: az első a minimális ellenőrzést
hozza létre, a második a tárolási határt rögzíti, a harmadik a szerződés
szerinti hibák további osztályait fedi le. A
git log --oneline --reverse három sort ad; a
git ls-files data/raw üres; a
git status --porcelain tiszta. A
git show --stat HEAD a harmadik commitban csak a
scripts/s007_check_orders.py módosítását mutatja, a
.gitignore nem érintett. A
git diff HEAD~1 -- scripts/s007_check_orders.py kizárólag a
harmadik commit hunkjait tartalmazza. A script kimenete elméletileg a
Northstar szerződésből várt hibákat jelzi: rows=2405,
missing_distance_km=2,
negative_basket_minor=1, a duplikátumszám pedig nulla
feletti; a pontos szám a helyi futtatásból derül ki. A
.gitignore miatt sem a
data/raw/orders_raw.csv, sem a .env, sem a
generált artifacts/ tartalom nem kerül a repóba.
Titok és adatreprodukció. A .gitignore csak a még nem
követett fájlok felvételét akadályozza meg; a már commitolt titkot nem
törli a történetből. Ilyen esetben a titkot vissza kell vonni vagy
rotálni, és szükség esetén a történetet külön eljárással tisztítani.
Személyes vagy licencköteles adatot a Gitből való kizárás önmagában nem
tesz jogszerűvé.
A Git kis szöveges forrásfájlok és konfigurációk verziózására
alkalmas; adat- és modellartefaktumoknál külön tartalomazonosító és
hozzáférési szabály szükséges. Gyakorlati kontroll:
git status --short, git ls-files, titokkereső
ellenőrzés és a generált állományok kizárása a commit előtt.
Értelmezés. Adott az alábbi diff a
scripts/s007_check_orders.py egy későbbi változatáról:
@@ -24,6 +24,7 @@ def main() -> int:
negative_basket = sum(1 for r in rows if r["basket_minor"] and int(r["basket_minor"]) < 0)
print(f"rows={len(rows)} missing_distance_km={missing_distance} "
f"duplicate_order_ids={sum(duplicates.values())} negative_basket_minor={negative_basket}")
- return 0 if missing_distance == 0 and not duplicates and negative_basket == 0 else 1
+ checks_ok = missing_distance == 0 and not duplicates and negative_basket == 0
+ return 0 if checks_ok else 1Mit fejez ki ez a változtatás, és milyen commitüzenet illik hozzá? Indokolja, miért nem változtatja meg a függvény külső viselkedését.
Megvalósítás. Készítsen három commitot egy új
scripts/s007_check_orders.py javításról: (a) a hiányzó
oszlopok üzenetét egészítse ki a talált oszlopok listájával; (b) a
.gitignore-t bővítse a *.joblib mintával; (c)
a szkripthez adjon egy --strict kapcsolót, amely a
duplikált sorokat is hibának tekinti. Írja le a teljes Bash-parancssort
a fájlmódosításoktól a commitokig, és adja meg a három
commitüzenetet.
Hibadiagnózis. A git status azt
mutatja, hogy a data/raw/orders_raw.csv a staging area-ban
van, pedig a .gitignore első sora data/raw/.
Mi lehet az ok, és pontosan milyen parancsokkal állítja helyre a
helyzetet? Miért nem elég a .gitignore módosítása?
Transzfer. A projekt átkerül egy Windows gépre,
ahol PowerShell fut. Adja meg a három commit PowerShell-parancsokkal, és
ellenőrizze, hogy a .gitignore mintái változatlanul
működnek-e. Ezután a feltétel megváltozik: a kész modellfájlokat
models/*.joblib néven, a köztes adatokat
data/interim/*.parquet néven generálja a pipeline.
Egészítse ki a .gitignore-t, és egyetlen paranccsal
igazolja, hogy egy ilyen fájl valóban figyelmen kívül maradna.
A diff a hosszú feltételt egy checks_ok nevű,
beszédes változóba emeli ki, majd a return ezt használja. A
külső viselkedés azonos, mert a logikai kifejezés értéke nem változott;
ez refaktorálás, nem funkcionális javítás. Illő commitüzenet:
Refactor check condition into named variable. Az
indoklásban a diff kontextussorai mutatják, hogy sem a kiírt sor, sem a
feltétel tagjai nem változtak.
A Bash-munkamenet vázlata:
# (a) a szkript üzenetének módosítása után
git add scripts/s007_check_orders.py
git commit -m "Report found columns on schema error"
# (b) a .gitignore kiegészítése után
printf '*.joblib\n' >> .gitignore
git add .gitignore
git commit -m "Ignore serialized model files"
# (c) a --strict kapcsoló implementálása után
git add scripts/s007_check_orders.py
git commit -m "Add strict mode treating duplicates as errors"Az ellenőrző invariant: git status --porcelain üres,
git log --oneline --reverse pontosan három új sort ad, és a
git show --stat HEAD csak a szkriptfájlt sorolja fel a
harmadik commitban.
Az ok: a data/raw/orders_raw.csv már a
.gitignore létrehozása előtt bekerült az indexbe; a
.gitignore csak a még nem követett fájlokat szűri. A
helyreállítás két lépésből áll: először a fájlt el kell távolítani az
indexből, de a munkakönyvtárban meg kell hagyni,
git rm -r --cached data/raw paranccsal, majd
git commit -m "Stop tracking raw data" véglegesíti. A
.gitignore módosítása önmagában azért nem elég, mert a már
indexelt fájl továbbra is a commit tartalma maradna. Az ellenőrzés:
git ls-files data/raw üres, a git status pedig
a fájlt már nem mutatja.
PowerShell-munkamenet:
# a szkript létrehozása után
git add scripts/s007_check_orders.py
git commit -m "Add initial orders_raw contract checker"
# a .gitignore létrehozása után
git add .gitignore
git commit -m "Ignore raw data and local secrets"
# a szkript módosítása után
git add scripts/s007_check_orders.py
git commit -m "Add duplicate and negative basket checks"A .gitignore mintája platformfüggetlen: a
data/raw/ és a .env minták Windows alatt is
ugyanúgy illeszkednek, mert a perjel a Git mintanyelvében
könyvtárelválasztóként funkcionál. A kiegészítés:
Add-Content .gitignore "models/*.joblib"
Add-Content .gitignore "data/interim/*.parquet"
git add .gitignore
git commit -m "Ignore generated models and interim data"Az ellenőrzés egyetlen paranccsal:
git check-ignore -v models/example.joblib, amelynek
kimenete megmutatja a .gitignore megfelelő sorát; ha nincs
kimenet, a minta hibás.
Gyakori tévedés, hogy a Git-repót automatikusan nagy adathalmazok és
modellbinárisok tárolójának tekintik. Ellenpélda: egy 200 MB-os
orders.parquet és egy 500 MB-os model.joblib
minden egyes commitban új teljes másolatot igényelne. Három-négy ilyen
commit után a repó több GB-os, a git clone és a push
percnyi, a diff pedig értelmezhetetlen bináris kimenetet ad. A helyes
határ: a repóba a forrás, a kis kontrolladatok és a szerződések
kerülnek; a nagy generált fájlokhoz külön verziózási eszköz való, amit
az Adatverzió és splitmanifest tárgyal. A
visszautalás 1.3-ra: a commit üzenet a tudományos munkanapló része, mert
a döntést és a szándékot rögzíti; a manifest hash-ellenőrzése
determinisztikus és nem tolerál eltérést, míg a Git-commitok hash-ei a
tartalomtól függő, de gépenként is reprodukálható azonosítók. Az 2.2-ra
visszatekintve a szkript a hibákat stderr-re írja, a
return érték a processz exit code-ja, így a későbbi
automatizált futtatás a Git által verziózott szkriptet közvetlenül
használhatja.
Önellenőrzés minden commit előtt: git diff --check
whitespace-hibát keres, git status --porcelain megmutatja a
rendezetlen állapotot. A titkos adatok véletlen bekerülését a
git grep -n "api_key" HEAD vagy hasonló keresés jelzi; ha
titok került a history-ba, a fájl törlése önmagában nem elég, a titkot
érvényteleníteni kell, és a history újraírása külön, kockázatos művelet.
A git check-ignore -v a .gitignore szabályok
tényleges illeszkedését ellenőrzi.
A Git a data science projektben a kód, a konfiguráció és az elemzési
döntések visszakereshető története. A working tree, az index és a HEAD
hármas elkülönítése lehetővé teszi, hogy a git add és a
git commit kis, logikus egységeket rögzítsen; a
git diff és a hunkok a változás pontos tartalmát mutatják.
A .gitignore a nyers adatot, titkokat, cache- és
modellfájlokat zárja ki; a notebookok zajos JSON-diffje helyett kimenet
nélküli commit és scriptexport a praktikus. A nagy binárisok nem a
Git-tárolóba valók. A következő alfejezetben ezt a helyi repót egy másik
géppel kötjük össze, hogy a projekt azonos környezetben folytatható
legyen.