09. MODUL · PYTHON

Függvényparaméterek.

Alapérték, név szerinti átadás, *args, **kwargs

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érték

alapértelmezett érték

A paraméter elhagyható a hívásnál.

nev=…

név szerinti argumentum

Az argumentum a paraméter nevével párosul, nem a helyével.

*args

tetszőleges számú érték

A fölös pozicionális argumentumok egy tuple-be gyűlnek.

**kwargs

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))
  1. 199
  2. 193
  3. 129

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))
  1. 0 3 3
  2. 0 1 3
  3. 1 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özLé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.