Függvényhívás

Mi történik függvényhíváskor?

Emeljük négyzetre egy lista elemeit:

In [1]:
def square(L):
    new_L = []
    for i in L:
        new_L.append(i*i)
    return new_L

numbers = [5, 1, 8]
sq_numbers =  square(numbers)

print(numbers)
print(sq_numbers)
[5, 1, 8]
[25, 1, 64]

Itt egyszerűen az [5, 1, 8] lista elemeit emeljük négyzetre.

Mi történik, ha közvetlenül a paraméterként kapott L lista elemeit emeljük négyzetre?

In [2]:
def square(L):
    for i in range(len(L)):
        L[i] = L[i] * L[i]
    return L

numbers = [5, 1, 8]
sq_numbers =  square(numbers)

print(numbers)
print(sq_numbers)
[25, 1, 64]
[25, 1, 64]

Mitől változott meg így az argumentumként átadott numbers lista is? Nézzünk egy hasonló példát: egyetlen számot emeljünk négyzetre!

In [3]:
def square(n):
    n = n * n
    return n

number = 5
sq_number = square(number)

print(number)
print(sq_number)
5
25

Most miért nem változott meg az argumentumként adott number, mikor a függvényben megváltoztattuk a paraméter értékét?

Érték szerinti paraméterátadás

Pythonban (és sok más programnyelvben) egy függvény paraméterei érték szerint adódnak át.

Ez azt jelenti, hogy egy függvény paraméterei nem egyeznek meg az argumentumként adott változókkal, hanem azok másolatai lesznek. Ezért nem változott meg az előző példában a number változó. Hasonló történik a lebegőpontos számokkal vagy logikai értékekkel is.

A listák esetében is másolat adódik át, de a másolat nem a listáról készül, hanem a lista memóriacíméről.

Összetett adatszerkezetek esetén, mint a lista, tuple, szótár, saját osztályok objektumai,... valójában a változó egy memóriacímet tárol, azt a címet ahol az objektum található a számítógép memóriájában.

1. példa

2. példa

3. példa

Ezért amikor olyan függvényt írunk mely listát kap paraméterként, mindig érdemes előre tisztázni, hogy a függvény megváltoztatja-e a lista elemeit vagy nem.

Listák másolása (kitérő)

Számok (vagy más egyszerű – primitív típusok) másolása úgy történik, ahogy arra számítunk, vagyis az értékük másolódik:

In [4]:
x = 5
y = x
y = 111

print(x, y)
5 111

Ha listát elemenként akarunk másolni, akkor azt nem tehetjük meg egy egyenlőségjellel. Ahhoz, hogy a lista elemei is lemásolódjanak, mást kell csinálnunk.

In [5]:
L1 = [1, 5, 2]
L2 = L1
L2.sort()

print(L1)
print(L2)
[1, 2, 5]
[1, 2, 5]

Ebben a példában L1 és L2 is ugyanarra az objektumra, egy listára mutat: lásd a pythontutorban.

In [6]:
L = [1,2,3]
M = L[:]    #<<<<<<
M[1] = 9
print(L)
print(M)
[1, 2, 3]
[1, 9, 3]

Még bonyolultab a helyzet a tömbbel (listák listájával)!

In [7]:
M1 = [[1, 2], [3, 4]]
M2 = M1[:]
M3 = M1[:]

M2[0] = [55, 22]
M3[0][0] = 555

print(M1)
print(M2)
print(M3)
[[555, 2], [3, 4]]
[[55, 22], [3, 4]]
[[555, 2], [3, 4]]

Mi is történik most? (lásd a pythontutorban)

Látható, a részlistás másolással az külső lista lemásolódott, de a belsők még mindig ugyanazok.

Hogyan másoljunk biztosan akármilyen mélységig listát:

In [8]:
import copy

M1 = [[1, 2], [3, 4]]
M2 = copy.deepcopy(M1)
M3 = copy.deepcopy(M1)

M2[0] = [55, 22]
M3[0][0] = 555

print(M1)
print(M2)
print(M3)
[[1, 2], [3, 4]]
[[55, 22], [3, 4]]
[[555, 2], [3, 4]]

A copy csomag a másolásra hasznos függvényeket tartalmazza, a deepcopy függvény jól lemásol egy listát, akármilyen mély legyen.

Jegyezzük meg, hogy a hivatkozás megváltoztatása nem változtatja meg a hivatkozott objektumot. Törölhetjük-e egy lista elemeit a következő kóddal?

In [9]:
def erase(L):
    L = []
    
L = [1,2,5]
erase(L)
print(L)
[1, 2, 5]
In [10]:
L = [1,2,5]
del L[:]
print(L)
[]

És ne feledkezzünk meg az immutable objektumokról (pl. tuple, sztring), amelyek elemei egyáltalán nem változtathatók.

Extra függvény paraméterek

Opcionális változó

Megtörténhet, hogy egy függvénynek nem mindig akarjuk megadni az egyik paraméterét, mert pl. legtöbbször ugyanazzal az értékkel használnánk. A következő programban diákok Neptun-kódja és vizsgaeredménye alapján egy listába gyűjtjük azok kódját, akik átmentek a vizsgán:

In [11]:
def atmentek(hallgatok, ponthatar):
    atment = []
    for hallgato in hallgatok:
        if hallgatok[hallgato] >= ponthatar:
            atment.append(hallgato)
    return atment

hallgatok = {'RABCA8': 50, 'BATMAN': 23, '123ABC': 67}
print(atmentek(hallgatok, 40))
['RABCA8', '123ABC']

Ekkor csinálhatjuk a következőt:

In [12]:
def atmentek(hallgatok, ponthatar=40):
    atment = []
    for hallgato in hallgatok:
        if hallgatok[hallgato] >= ponthatar:
            atment.append(hallgato)
    return atment

hallgatok = {'RABCA8': 50, 'BATMAN': 23, '123ABC': 67}
print(atmentek(hallgatok))
print(atmentek(hallgatok, 60))
['RABCA8', '123ABC']
['123ABC']

Nem csak egy opcionális paraméter adható meg, de azok csak a nem-opcionálisak után következhetnek:

In [13]:
def atmentek(hallgatok, ponthatar=40, sorban=True):
    atment = []
    for hallgato in hallgatok:
        if hallgatok[hallgato] >= ponthatar:
            atment.append(hallgato)
    if sorban:
        return sorted(atment)
    else:
        return atment

hallgatok = {'RABCA8': 50, 'BATMAN': 23, '123ABC': 67}
print(atmentek(hallgatok, 40))
print(atmentek(hallgatok, 40, False))
['123ABC', 'RABCA8']
['RABCA8', '123ABC']

Mi történik a következő függvényhívásnál?

In [14]:
print(atmentek(hallgatok, False))
['123ABC', 'BATMAN', 'RABCA8']

Az argumentumok sorrendje fontos! Itt a ponthatar=False, sorban=True értékek adódtak át, a ponthatár=False pedig azonos a ponthatár=0 paraméterrel.

Nevesített paraméterek

Lehet nevükkel hivatkozni az opcionális paramétereket! Ekkor sorrendjük nem számít, de azt a nevet kell írni az = bal oldalára, ami a függvény definíciójában szerepel!

In [15]:
print(atmentek(hallgatok, sorban=False))
print(atmentek(hallgatok, sorban=False, ponthatar=40))
['RABCA8', '123ABC']
['RABCA8', '123ABC']
In [16]:
print("A P({x}, {y}) pontban".format(y=3, x=4))
A P(4, 3) pontban

Változó számú argumentum

Írjunk egy olyan függvényt, mely számok szorzatához hozzáad még egy adott konstanst!

In [17]:
def product(c, L):
    szorzat = 1
    for i in L:
        szorzat *= i
    return szorzat + c

print(product(1, [1,2,3]))
7

Mi lenne ha a szorzat tényezői is paraméterek lennének, nem pedig egy lista elemei?

In [18]:
def product2(c=0, x=1, y=1, z=1):
    return x*y*z + c

print(product2())
print(product2(1))
print(product2(1, 1))
print(product2(1, 1, 2))
print(product2(3, 1, 2, 3))
1
2
2
3
9

Ez sajnos nem működik több változóra, csak ha kézzel hozzáfűzünk egy csomó opcionális paramétert.

Ehelyett van a variadikus, vagy váltózó számú paraméterrel rendelkező függvény!

Figyeljük meg, hogy az egyetlen különbség az eredeti product függvényhez képest a *

In [19]:
def product3(c=0, *szamok):
    szorzat = 1
    for i in szamok:
        szorzat *= i
    return szorzat + c

print(product3())
print(product3(5, 1, 2, 3, 4, 5, 6))
print(product3(5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
1
725
3628805

Ekkor a beírt paramétereket egy tuple-ben kapja meg a függvényünk. Ez lehet 0, 1 vagy több elemű is, de iterálható.

In [20]:
def variadic(*x):
    return type(x)

print(variadic(3,2))
<class 'tuple'>

Variadikus függvény meghívása

Mi van ha egy variadikus függvényt akarunk megívni, de az argumenumok már egy listában vannak?

Ekkor ez nem jó:

In [21]:
c = 5
L = [1, 2, 3]
print(product3(c, L))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-ac329e25fb23> in <module>
      1 c = 5
      2 L = [1, 2, 3]
----> 3 print(product3(c, L))

<ipython-input-19-373a7019700d> in product3(c, *szamok)
      3     for i in szamok:
      4         szorzat *= i
----> 5     return szorzat + c
      6 
      7 print(product3())

TypeError: can only concatenate list (not "int") to list
In [22]:
# Jó
print(product3(c, *L))

# ez ekvivalens ezzel de bárhány elemmel működik:
print(product3(c, L[0], L[1], L[2]))
11
11

Tetszőleges számú nevesített argumentumok: **kwargs

Írjunk függvényt, mely kiírja egy tudományos cikk megadott könyvtári adatait:

In [23]:
def article(**journal):
    for i in journal:
        print(i, "=", journal[i], end=", ")
    print()
        
article(author="Endre Szemerédi", title="On Sets of Integers Containing...", year=1975)
author = Endre Szemerédi, title = On Sets of Integers Containing..., year = 1975, 

Itt az argumentumok egy könyvtárba másolódnak, és eszerint kezelhetők.

In [24]:
def article(**journal):
    print(type(journal))
    
article(author="Euler")
<class 'dict'>

Láthatóság

scope

Hívhatunk azonos névvel kölünböző változókat, de a programnak tudnia kell, mikor melyiket kell használnia, honnan melyik látható!

In [25]:
def fuggveny(L):
    # ez egy másik i
    for i in L:
        if i != 0:
            return True
    return False

i = [0, 1, -1]
print(fuggveny(i))
True
In [26]:
def valami(L):
    i = 10
    for i in L:
        i = 5*i
    return i

print(valami([1, 2, 3]))
15
In [27]:
def valami2(L):
    # lokális i
    i = 0
    for j in L:
        i = i+j
    return i

# globális i
i = 10
print(valami2([1, 2, 3]))
print(i)
6
10

Ha egy változót egy függvény belsejében hozunk létre (neve lokális változó), akkor ott az fog látszódni, akkor is, ha volt olyan nevű változónk máshol. Ez a változó megszűnik létezni, amikor a függvény befejezi futását (a return után) és nem változtatja az esetlegesen meglévő ugyanolyan nevű másutt használt változónkat.

Sőt a függvény többszöri meghívásával újra létrejön, nem jegyzi meg értékét a függvényhívások között.

Ha egy változót nem függvényben hozunk létre, akkor az minden más helyen látszódni fog (neve globális változó). A függvények belsejében is, hacsak nem hozunk létre ott lokális változót azonos névvel.

In [28]:
def f(x):
    # az i itt globális
    print(i)
    
i = 10
f(None)
10
In [29]:
def f(x):
    i = 0 # az i itt lokális
    print(i)
    
i = 10
f(None)
0

Elágazásnál (if) figyeljünk, hogy a változónak ugyan nem mindig adunk értéket ha olyan ágban szerepel, de ettől még ott látható, így a másutt definiált azonos nevű változó itt nem fog látszani:

In [30]:
def f(x):
    if x:
        i = 0
    return i
    
i = 10
print(f(True))
print(f(False))
0
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-30-54d7274d9724> in <module>
      6 i = 10
      7 print(f(True))
----> 8 print(f(False))

<ipython-input-30-54d7274d9724> in f(x)
      2     if x:
      3         i = 0
----> 4     return i
      5 
      6 i = 10

UnboundLocalError: local variable 'i' referenced before assignment

Függvény referencia

Ha már ismerjük a referencia fogalmát, akkor egyszerűen adódik, hogy a függvények is csak referenciák, így használhatók akár függvény argumentumként is. Emlékezzünk előbb vissza a buborék rendezés algoritmusra:

In [31]:
def rendez(lista):
    rendezve = lista[:]
    for i in range(len(lista) - 1):
        for j in range(len(lista) - i - 1):
            if rendezve[j] > rendezve[j + 1]:
                rendezve[j],rendezve[j+1] = rendezve[j+1],rendezve[j]
    return rendezve

L = [1, 8, 5, 2, 9, 3, 6]

print(rendez(L))
[1, 2, 3, 5, 6, 8, 9]

Módosítsuk ezt úgy, hogy bármilyen rendezésre működjön! Írjunk először rendező függvényeket:

In [32]:
def novekvo(a, b):
    if a < b:
        return False
    else:
        return True
    
def csokkeno(a, b):
    if a > b:
        return False
    else:
        return True

És adjunk a függvénynek egy alapértelmezett rendezést!

In [33]:
def rendez(lista, hasonlit=novekvo):
    rendezve = lista[:]
    for i in range(len(lista) - 1):
        for j in range(len(lista) - i - 1):
            if hasonlit(rendezve[j], rendezve[j + 1]):
                rendezve[j],rendezve[j+1] = rendezve[j+1],rendezve[j]
    return rendezve
In [34]:
L = [1, 8, 5, 2, 9, 3, 6]

print(rendez(L))
print(rendez(L, csokkeno))
[1, 2, 3, 5, 6, 8, 9]
[9, 8, 6, 5, 3, 2, 1]
In [35]:
type(novekvo)
Out[35]:
function
In [ ]: