1. fejezet
Ismétlés: hogyan kapnak értéket a paraméterek?
A 7. modulban a paraméterek egyszerű, pozicionális formáját használtuk: a hívás argumentumai sorrendben kerülnek a paraméterekbe – az első az elsőbe, a második a másodikba –, és pontosan annyi argumentum kell, ahány paraméter van. Ebben a modulban ezt rugalmasabbá tesszük:
alapértelmezett érték
A paraméter elhagyható a hívásnál.
név szerinti argumentum
Az argumentum a paraméter nevével párosul, nem a helyével.
tetszőleges számú érték
A fölös pozicionális argumentumok egy tuple-be gyűlnek.
tetszőleges név szerinti érték
A fölös név szerinti argumentumok egy szótárba gyűlnek.
Ezeket valójában már használtad: a print("a", "b", sep=", ") tetszőleges számú értéket fogad (*args), a sep pedig egy alapértelmezett értékkel rendelkező, név szerint megadott paraméter. A sorted(lista, reverse=True) ugyanígy.
2. fejezet
Alapértelmezett érték
Ha a paraméter neve után = jellel egy értéket írunk, az lesz az alapértéke: ha a hívás nem ad neki argumentumot, ezt kapja. Így a ritkán változó beállításokat nem kell minden hívásnál megadni.
def koszont(nev, udvozles="Szia"):
return f"{udvozles}, {nev}!"
print(koszont("Anna"))
print(koszont("Béla", "Jó reggelt"))
def brutto(netto, afa=0.27):
return round(netto * (1 + afa))
print(brutto(1000), brutto(1000, 0.05))
Az alapértékes paraméterek a sor végére kerülnek. A def f(a=1, b): szintaktikai hiba: ha a hívás egyetlen argumentumot adna, nem lehetne eldönteni, melyik paraméteré. Előbb a kötelező paraméterek, utána az elhagyhatók.
def koszont(udvozles="Szia", nev):
return f"{udvozles}, {nev}!"
A módosítható alapérték csapdája
Az alapértéket a Python egyszer, a def sor végrehajtásakor hozza létre, és utána minden hívás ugyanazt az objektumot kapja meg. Számnál vagy szövegnél ez nem okoz gondot, egy listánál viszont igen: a hívások közösen használják és módosítják. Kövesd lépésenként, és figyeld a nyilakat.
def hozzaad(elem, lista=[]):
lista.append(elem)
return lista
print(hozzaad("alma"))
print(hozzaad("körte"))
A második hívás egy új, üres listát várna, de az előző hívás „almája” is benne van. A szokásos megoldás: az alapérték None, és a függvény maga hoz létre új listát.
def hozzaad(elem, lista=None):
if lista is None:
lista = []
lista.append(elem)
return lista
print(hozzaad("alma"))
print(hozzaad("körte"))
Az is azt vizsgálja, hogy a két oldal ugyanaz az objektum-e (nem csak egyenlő-e). A None egyetlen példányban létezik, ezért rá mindig is None-nal kérdezünk.
3. fejezet
Név szerinti argumentum
Híváskor az argumentum elé írhatjuk a paraméter nevét: f(nev="Anna"). Ilyenkor a párosítás a név alapján történik, a sorrend nem számít. Két előnye van: a hívás olvashatóbb (szamla(1000, afa=0.05) elárulja, mi a 0.05), és a középső alapértékes paramétereket át lehet ugrani.
def beir(tanulo, targy, jegy=5, felev=False):
tipus = "félévi" if felev else "évközi"
return f"{tanulo} – {targy}: {jegy} ({tipus})"
print(beir("Anna", "matek"))
print(beir("Anna", "matek", 4))
print(beir("Bence", "fizika", felev=True))
print(beir(targy="kémia", tanulo="Csilla", jegy=3))
A hívásban a pozicionális argumentumok állnak elöl, utánuk a név szerintiek. A beir(tanulo="Anna", "matek") szintaktikai hiba. Egy paraméter pedig csak egyszer kaphat értéket: a beir("Anna", "matek", tanulo="Bence") TypeError.
Mit ír ki?
def f(a, b=2, c=3):
return a * 100 + b * 10 + c
print(f(1, c=9))
199193129
Az a pozíció szerint 1-et kap, a c név szerint 9-et, a b pedig – mivel nem kapott argumentumot – az alapértékét, a 2-t. 1 · 100 + 2 · 10 + 9 = 129.
4. fejezet
Tetszőleges számú argumentum: *args
Ha egy paraméter neve elé csillagot írunk, az összegyűjti az összes „fölös” pozicionális argumentumot egy tuple-be. Így a függvény akárhány értéket fogadhat – akár nullát is. A név bármi lehet (*szamok, *nevek); az args csak megszokott elnevezés.
def atlag(*szamok):
print("A szamok tuple:", szamok)
if len(szamok) == 0:
return None
return sum(szamok) / len(szamok)
print(atlag(4, 5, 3))
print(atlag(2, 4))
print(atlag())
A lépésenkénti nézetben a szamok paraméter egy tuple-re mutat, benne a hívás összes argumentumával. Előtte állhatnak „rendes” paraméterek is: def kiir(cim, *sorok): – az első argumentum a cim-be, a többi a sorok-ba kerül.
def kiir(cim, *sorok):
print(cim.upper())
for i, sor in enumerate(sorok, start=1):
print(f" {i}. {sor}")
kiir("Bevásárlás", "kenyér", "tej", "alma")
kiir("Üres lista")
Az enumerate(sorok, start=1) az elemek mellé sorszámot is ad: (1, "kenyér"), (2, "tej"), … – a ciklus fejében rögtön kicsomagoljuk.
5. fejezet
Tetszőleges név szerinti argumentum: **kwargs
A két csillagos paraméter a „fölös” név szerinti argumentumokat gyűjti össze egy szótárba: a kulcs a megadott név (szövegként), az érték az argumentum. Akkor hasznos, ha a függvény előre nem ismert beállításokat vagy adatmezőket fogad.
def adatlap(nev, **adatok):
print(f"--- {nev} ---")
for kulcs, ertek in adatok.items():
print(f"{kulcs}: {ertek}")
adatlap("Anna", kor=17, varos="Pécs")
adatlap("Bence", szak="informatika", evfolyam=2, kollegista=True)
Példa: beállítások alapértékekkel
Gyakori minta, hogy a függvény egy alapbeállítás-szótárat a kapott **kwargs-szal frissít – így csak az eltérő beállításokat kell megadni:
def polo(**valasztas):
beallitas = {"szin": "fehér", "meret": "M", "anyag": "pamut"}
beallitas.update(valasztas)
return beallitas
print(polo())
print(polo(szin="kék", meret="L"))
print(polo(anyag="len", zseb=True))
6. fejezet
Kicsomagolás a hívásnál: *lista, **szotar
A csillag a hívás oldalán is használható, fordított értelemben: f(*lista) a lista elemeit külön argumentumokként adja át, f(**szotar) pedig a szótár párjait név szerinti argumentumokként.
def terfogat(a, b, c):
return a * b * c
meretek = [2, 3, 4]
print(terfogat(*meretek))
print(*meretek)
print(*meretek, sep=" × ")
def koszont(nev, udvozles="Szia"):
return f"{udvozles}, {nev}!"
adatok = {"nev": "Dóra", "udvozles": "Helló"}
print(koszont(**adatok))
Mit ír ki?
def db(*elemek):
return len(elemek)
lista = [1, 2, 3]
print(db(), db(lista), db(*lista))
0 3 30 1 31 1 3
Argumentum nélkül az elemek üres tuple (0). A db(lista) egyetlen argumentumot ad át – magát a listát –, így a tuple egyelemű: ([1, 2, 3],). A db(*lista) viszont kicsomagolja a listát, három külön argumentum lesz belőle.
7. fejezet
Minden együtt – és egy tanulságos hiba
A paraméterfajták kötött sorrendben állhatnak a fejlécben:
def f(kotelezo, alapertekes=1, *args, csak_nev_szerint=2, **kwargs):
...
A *args után álló paraméterek csak név szerint kaphatnak értéket – hiszen minden további pozicionális argumentumot az *args „elnyel”. Ha erre nem figyelünk, nehezen észrevehető hibát kapunk:
def rendezes(sorrend="növekvő", *szamok, **beallitasok):
szamlista = list(szamok)
if beallitasok.get("abszolut", False):
szamlista.sort(key=abs)
else:
szamlista.sort()
if sorrend == "csökkenő":
szamlista.reverse()
return szamlista
print(rendezes(5, 1, -3, 2))
print(rendezes("csökkenő", 5, 1, -3, 2))
Az első hívástól a [-3, 1, 2, 5] listát várnánk, a valódi eredmény viszont [-3, 1, 2]: az első argumentum (az 5) pozíció szerint a sorrend paraméterbe került, és kimaradt a rendezésből. Kövesd lépésenként, és nézd meg a hívás keretében a paraméterek értékét. A javítás: a sorrend kerüljön a *szamok mögé, így csak név szerint lehet megadni.
def rendezes(*szamok, sorrend="növekvő", **beallitasok):
szamlista = list(szamok)
if beallitasok.get("abszolut", False):
szamlista.sort(key=abs)
else:
szamlista.sort()
if sorrend == "csökkenő":
szamlista.reverse()
return szamlista
print(rendezes(5, 1, -3, 2))
print(rendezes(5, 1, -3, 2, sorrend="csökkenő"))
print(rendezes(5, 1, -3, 2, abszolut=True))
A sort(key=abs) az elemeket az abszolút értékük szerint rendezi: a key egy függvény, amelyet a rendezés minden elemre meghív, és az eredmények alapján sorol.
8. fejezet
Gyakorló feladatok
A gép itt is a függvényeidet hívja meg többféleképpen – pozíció és név szerint is. A függvénynevek és a paraméternevek egyezzenek a feladatban megadottakkal.
Ár formázása
Írj ar_szoveg(ar, penznem="Ft", tizedes=0) függvényt, amely az árat a megadott számú tizedesjegyre kerekítve, szóközzel és a pénznemmel adja vissza szövegként.
ar_szoveg(1234.4) → '1234 Ft' ar_szoveg(19.99, "€", 2) → '19.99 €' ar_szoveg(5, tizedes=1) → '5.0 Ft'
def ar_szoveg(ar, penznem="Ft", tizedes=0):
pass
print(ar_szoveg(1234.4))
def ar_szoveg(ar, penznem="Ft", tizedes=0):
return f"{ar:.{tizedes}f} {penznem}"
print(ar_szoveg(1234.4))
Tipp
Az f-string formátumában is lehet változó: f"{ar:.{tizedes}f}". Vagy: round(ar, tizedes), de figyelj a 5.0 és a 1234 alakra.
Téglalap vagy négyzet
Írj terulet(a, b=None) függvényt: ha a b-t megadják, a téglalap területét adja vissza, ha nem, akkor az a oldalú négyzetét.
def terulet(a, b=None):
if b is None:
return a * a
return a * b
print(terulet(3), terulet(3, 5))
Tipp
Az if not b: a 0-t is „hiányzónak” venné. A pontos vizsgálat: if b is None:.
A legnagyobb bármennyi közül
Írj legnagyobb(*szamok) függvényt, amely tetszőleges számú argumentum közül a legnagyobbat adja vissza, a max() használata nélkül. Argumentum nélküli hívásra None-t adjon.
def legnagyobb(*szamok):
if len(szamok) == 0:
return None
eddigi = szamok[0]
for x in szamok:
if x > eddigi:
eddigi = x
return eddigi
print(legnagyobb(3, 9, 4), legnagyobb())
Tipp
A szamok egy tuple – pont úgy járhatod be, mint egy listát. Az üres tuple hossza 0.
Adatlap szövegként
Írj adatlap(nev, **adatok) függvényt, amely egy többsoros szöveget ad vissza: az első sor a név, utána minden adat egy-egy sorban kulcs: érték alakban, a megadás sorrendjében. A sorokat "\n" válassza el (a végén ne legyen sortörés).
print(adatlap("Anna", kor=17, varos="Pécs"))
Anna
kor: 17
varos: Pécs
def adatlap(nev, **adatok):
sorok = [nev]
for kulcs, ertek in adatok.items():
sorok.append(f"{kulcs}: {ertek}")
return "\n".join(sorok)
print(adatlap("Anna", kor=17, varos="Pécs"))
Tipp
Gyűjtsd a sorokat egy listába (az első a név), és a végén "\n".join(sorok).
Javítsd a paramétersorrendet
Az alábbi osszeg(kezdo=0, *szamok) függvény látszólag jól működik: osszeg(1, 2, 3) eredménye 6. Kövesd lépésenként, és nézd meg a hívás keretét: az első szám valójában a kezdo paraméterbe kerül, nem a szamok-ba – az összeg csak véletlenül jó. Ha pedig kezdőértéket is megadnánk (osszeg(1, 2, 3, kezdo=10)), TypeError lesz a vége. Írd át úgy, hogy a kezdo csak név szerint legyen megadható, és alapból 0 legyen:
osszeg(1, 2, 3) → 6 osszeg(1, 2, 3, kezdo=10) → 16 osszeg() → 0
def osszeg(kezdo=0, *szamok):
eredmeny = kezdo
for x in szamok:
eredmeny += x
return eredmeny
print(osszeg(1, 2, 3))
def osszeg(*szamok, kezdo=0):
eredmeny = kezdo
for x in szamok:
eredmeny += x
return eredmeny
print(osszeg(1, 2, 3))
Tipp
A hibás változatban az első szám mindig a kezdo-be kerül (ezért „véletlenül” jó az összeg). A kezdőértékes hívás viszont TypeError-t ad. Tedd a kezdo-t a *szamok mögé.
9. fejezet
Összefoglalás
| Eszköz | Lényeg |
|---|---|
def f(a, b=2): | alapérték; a végén álljon; módosítható alapérték helyett None |
f(b=5, a=1) | név szerinti argumentum; a pozicionálisak állnak elöl |
def f(*args): | a fölös pozicionális argumentumok tuple-ben |
def f(**kwargs): | a fölös név szerinti argumentumok szótárban |
def f(*args, x=1): | a * utáni paraméter csak név szerint adható meg |
f(*lista), f(**szotar) | kicsomagolás híváskor |
Gyakorold tovább: írj egy naplo(*uzenetek, szint="INFO", elvalaszto=" | ") függvényt, amely az üzeneteket egy sorba fűzve, a szinttel kezdve adja vissza (pl. INFO | indul | kész). Próbáld ki pozicionális, név szerinti és kicsomagolt (*lista) hívással is.