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)
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?
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)
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!
def square(n):
n = n * n
return n
number = 5
sq_number = square(number)
print(number)
print(sq_number)
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?
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.
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.
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:
x = 5
y = x
y = 111
print(x, y)
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.
L1 = [1, 5, 2]
L2 = L1
L2.sort()
print(L1)
print(L2)
Ebben a példában L1
és L2
is ugyanarra az objektumra, egy listára mutat: lásd a
pythontutorban.
L = [1, 2, 3]
M = L[:] # shallow copy = sekély másolat
M[1] = 9
print(L)
print(M)
Még bonyolultab a helyzet a tömbbel (listák listájával)!
M1 = [[1, 2], [3, 4]]
M2 = M1[:]
M3 = M1[:]
M2[0] = [55, 22]
M3[0][0] = 555
print(M1)
print(M2)
print(M3)
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:
import copy
M1 = [[1, 2], [3, 4]]
M2 = copy.deepcopy(M1) # deep copy = mély másolat
M3 = copy.deepcopy(M1)
M2[0] = [55, 22]
M3[0][0] = 555
print(M1)
print(M2)
print(M3)
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?
def erase(L):
L = []
L = [1, 2, 5]
erase(L)
print(L)
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.
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:
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))
Ekkor csinálhatjuk a következőt:
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))
Nem csak egy opcionális paraméter adható meg, de azok csak a nem-opcionálisak után következhetnek:
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))
Mi történik a következő függvényhívásnál?
print(atmentek(hallgatok, False))
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.
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!
print(atmentek(hallgatok, sorban=False))
print(atmentek(hallgatok, sorban=False, ponthatar=40))
print("A P({x}, {y}) pontban".format(y=3, x=4))
Írjunk egy olyan függvényt, mely számok szorzatához hozzáad még egy adott konstanst!
def product(c, L):
szorzat = 1
for i in L:
szorzat *= i
return szorzat + c
print(product(1, [1, 2, 3]))
Mi lenne ha a szorzat tényezői is paraméterek lennének, nem pedig egy lista elemei?
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))
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 *
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))
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ó.
def variadic(*x):
return type(x)
print(variadic(3, 2))
Mi van ha egy variadikus függvényt akarunk megívni, de az argumenumok már egy listában vannak?
Ekkor ez nem jó:
c = 5
L = [1, 2, 3]
print(product3(c, L))
# 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]))
Írjunk függvényt, mely kiírja egy tudományos cikk megadott könyvtári adatait:
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)
Itt az argumentumok egy szótárba másolódnak, és eszerint kezelhetők.
def article(**journal):
print(type(journal))
article(author="Euler")
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))
def valami(L):
i = 10
for i in L:
i = 5*i
return i
print(valami([1, 2, 3]))
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)
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.
def f(x):
# az i itt globális
print(i)
i = 10
f(None)
def f(x):
i = 0 # az i itt lokális
print(i)
i = 10
f(None)
i
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:
def f(x):
if x:
i = 0
return i
i = 10
print(f(True))
print(f(False))
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:
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))
Módosítsuk ezt úgy, hogy bármilyen rendezésre működjön! Írjunk először rendező függvényeket:
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!
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
L = [1, 8, 5, 2, 9, 3, 6]
print(rendez(L))
print(rendez(L, csokkeno))
type(novekvo)