Hivatkozások, megváltoztathatóság, láthatóság


In [1]:
%pylab inline
Populating the interactive namespace from numpy and matplotlib

Hivatkozások

Változók viselkedése értékadásnál

Hogyan gondoljunk arra, amikor leütjük a következő kifejezést?

a = 1

  • a : név
  • az a tartalma: hivatkozás (referencia) egy objektumra
  • 1: maga az objektum, amire hivatkozunk
In [2]:
a = 1

Az id() függvénnyel lekérdezhetjük az objektum egyedi azonosítóját.

In [3]:
id(a)
Out[3]:
139776461195808
In [4]:
id(1)
Out[4]:
139776461195808

Ha több összetett típusú változót külön hozunk létre, akkor azok mindig különböző objektumokra mutatnak.

In [5]:
a=[0,1]
b=[0,1]
id(a),id(b)
Out[5]:
(139774089045384, 139774089045320)

Így is.

In [6]:
a,b=[0,1],[0,1]
id(a),id(b)
Out[6]:
(139774089247688, 139774094310728)

Leggyakoribb használata az azonosítóknak, hogy az is függvénnyel leelenőrizhetjük, hogy két objektum id értéke megegyezik-e. Két objektum azonos-e.

In [7]:
a is a
Out[7]:
True
In [8]:
a is b
Out[8]:
False
In [9]:
a is [0,1]
Out[9]:
False

Trükkös dolgok: Ha nem mentjük el a objektumot, amit éppen most hoztuk létre, a Python újrahasznosítja.

In [10]:
id([0,1]),id([0,1])
Out[10]:
(139774089219912, 139774089219912)

Számok, kis karakterláncok esetén nem feltétlenül kapunk különböző objektumot, ha értéket adunk egy változónak.

In [11]:
a=1
b=1
id(a),id(b)
Out[11]:
(139776461195808, 139776461195808)
In [12]:
a='a'
b='a'
id(a),id(b)
Out[12]:
(139776439460504, 139776439460504)

Kellően nagy számra/karakterláncra már különbözőt kapunk.

In [13]:
a=1000
b=1000
id(a),id(b)
Out[13]:
(139774107020592, 139774107020528)
In [14]:
a='gfuybgnwfewfcewmgcyuwgrc'
b='gfuybgnwfewfcewmgcyuwgrc'
id(a),id(b)
Out[14]:
(139774090552144, 139774090552144)

( De például, ha egy sorban csináljuk, akkor már ugyanazt az objektumot kapjuk)

In [15]:
a,b=1000,1000
id(a),id(b)
Out[15]:
(139774107020432, 139774107020432)
In [16]:
a,b='gfuybgnwfewfcewmgcyuwgrc','gfuybgnwfewfcewmgcyuwgrc'
id(a),id(b)
Out[16]:
(139774090551344, 139774090551344)

Összeségében: Az is parancsot összetett típusokra használjuk csak (lista,tuple,array,...) . Ne használuk az is parancsot, vagy az id-t számokkal, ugyanis nem triviális, hogy mi történik!


Új referenciák létrejötte

Mi történik, ha egy új változónak értékadáskor egy előző változót adtok meg értékül?

A sys.getrefcount() nevű függvény megmondja, hogy egy objektumra hány hivatkozás létezik. (Vigyázzunk, mindig 1-gyel többet mond, mert a meghívása során létrejön még egy új hivatkozás az objektumra!)

A következő példában egy tuple-t találunk, mert kis int számokra túl sok minden hivatkozik, ezért nem lenne szemléletes velük a példa.

In [17]:
import sys

a=(1,)   # létrehozzuk az új (1) objektumot
        # 'a'-ba beleírjuk az (1)-re mutató referenciát
print("Erre az objektumra mutat 'a': ", id(a))
print(sys.getrefcount(a))  # 2 referenciát számolunk, mert a függvény megghívásakor is létrejön egy

b=a # 'b'-be belekerül egy új referencia, ami ugyanarra az objektumra mutat, az (1)-re
print("Erre az objektumra mutat 'b': ", id(b))
print(sys.getrefcount(a))  # 3 referenciát számolunk, mert már 'b' is az (1)-re mutat

b=(1,) # létrehozunk egy új (1) objektumot
      # 'b'-be beleírjuk az új (1)-re mutató referenciát
print("Most már másik objektumra mutat 'b'! ", id(b))
print(sys.getrefcount(a))  # 2 referenciát számolunk, mert 'b' már egy másik (1)-es objektumra mutat
Erre az objektumra mutat 'a':  139774090209488
2
Erre az objektumra mutat 'b':  139774090209488
3
Most már másik objektumra mutat 'b'!  139774131769016
2

Megváltoztathatóság

Megváltoztathatatlan objektumok: int,float,string,tuple.

Amikor a megváltoztathatatlan objektumra mutató változónak új értéket adunk (pl. az integert megnöveljük eggyel), akkor nem az objektumot változtatjuk meg, hanem az eredeti névhez egy új hivatkozást rendelünk, ami most már egy másik objektumra mutat.

Az id() függvény megadja az objektumok egyedi azonosítóját. A következő példában figyeljük meg, hogy más értéket ad vissza a változtatás után, hiszen az int típusú objektum megváltoztathatatlan.

In [18]:
a=1 # 'a'-ba beleírjuk az 1-re mutató referenciát
print(id(a)) 
a+=1 # 'a'-ba beírjuk a 2-re mutató referenciát
print(id(a)) # most másik objektumra mutat az 'a'
print(a)
139776461195808
139776461195840
2

Megváltoztatható objektumok: lista, array stb.

Amikor a megváltoztatható objektumra mutató változónak adunk új értéket, vagy módosítjuk, akkor magát az objektumot módosítjuk, a hivatkozás érintetlen marad.

Figyeljük meg, hogy a következő példában a változó módosítása után is ugyanazt az azonosítót adja vissza az id() függvény.

In [19]:
a=[1] # a ba beleirjuk az [1] re mutato referenciat
print(id(a)) 
a+=[1]
print(id(a)) #ugyanarra az objektumra mutat
print(a) # de az objektum megvaltozott
140451873502152
140451873502152
[1, 1]

Következmények

Megváltoztathatatlan objektumok esetén, ha két változó ugyanarra az objektumra mutat, akkor hiába módosítjuk az egyik változót, a másik változatlan marad.

Ez természetesen az elvárt viselkedés. Két különböző mennyiség teljesen nyugodtan felveheti ugyanazt az értéket egymástól függetlenül, ennek a későbbi viselkedésüket nem kell befolyásolnia.

Azt, hogy két változó ugyanarra az objektumra mutat-e, az is művelet segítségével ellenőrizhetjük.

In [19]:
#integerrel
a=1 # 'a' az 1-re mutat
b=a # 'b' is az 1-re mutat, ez egy új referencia
print(a is b) # a két változó ugyanarra az objektumra mutat

b+=2 # 'b' most már a 3-ra mutat
print(a is b) # a két változó már nem ugyanarra az objektumra mutat
print(a,b) # 'a' nem változik (megváltoztathatatlan volt!)
True
False
1 3
In [20]:
#stringgel
a='a'
b=a
print(a is b)

b+='b'
print(a is b)
print(a,b)
True
False
a ab
In [21]:
#tuple-lel
a=(1,2)
b=a
print(a is b)

b+=(2,3)
print(a is b)
print(a,b)
True
False
(1, 2) (1, 2, 2, 3)

Megváltoztatható objektumok esetén ha két változó ugyanarra az objektumra mutat, akkor az egyik változót módosítva maga az objektum változik meg, tehát a másik változó is meg fog változni!

Ezért általában nem szerencsés, ha egy megváltoztatható objektumra több névvel is hivatkozunk. De látni fogjuk, hogy for ciklusban és függvényekben előfordul ez az eset, ezért tudni kell mi történik pontosan.

In [22]:
#listával
a=[1,2] # 'a' az [1,2]-re mutat
b=a # 'b' is az [1,2]-re mutat már
print(a is b) # a két változó ugyanarra az objektumra mutat

b+=[2,3] # megváltoztatom az objektumot
print(a is b) # a két változó még mindig ugyanarra az objektumra mutat
print(a,b) # mivel az objektum változott meg, de a hivatkozások nem, ezért 'a' is megváltozott!
True
True
[1, 2, 2, 3] [1, 2, 2, 3]
In [23]:
#array-jel
a=array([1,2])
b=a
print(a is b)

b+=array([2,3])
print(a is b)
print(a,b)
True
True
[3 5] [3 5]

No de mi történik, ha ezután b-nek új értéket adok, nem csak módosítom?

In [24]:
a=[1,2]
b=a
print (a is b)

b=[2,3]
print (a is b)
print(a,b)
True
False
[1, 2] [2, 3]

Vegyük észre, hogy most viszont 'b' már új objektumra hivatkozik, így 'a' értéke nem változott.

For ciklus

A végigszaladás közben egy-egy új hivatkozást kapunk az objektumokra!

In [26]:
a=[1,2]
b=[1,2]
print(sys.getrefcount(a),end=', ') # 2, ahogy eddig is láttuk
print(sys.getrefcount(b)) # 2, ahogy eddig is láttuk

c=[a,a,b,b]
print(sys.getrefcount(a),end=', ')# 4, mert c elemei is 'a'-ra és 'b'-re mutatnak
print(sys.getrefcount(b)) # 4, mert c elemei is 'a'-ra és 'b'-re mutatnak

for elem in c:
    print(sys.getrefcount(a),end=', ') # aktuálisra 4+1, másikra továbbra is 4
    print(sys.getrefcount(b)) # # aktuálisra 4+1, másikra továbbra is 4
2, 2
4, 4
5, 4
5, 4
4, 5
4, 5

Következmény megváltoztathatatlan objektumok esetén

Ha van egy megváltoztathatatlan objektumokból (pl. int, float) álló listánk (bármi, ami iterálható), akkor a for ciklusban a ciklusváltozóként használt elemmel bármit csinálunk, az eredeti lista nem fog megváltozni.

A ciklusváltozó ugyanis egy új változó, ami egy új referenciát tartalmaz ugyanarra az objektumra, mint az eredeti változó. Megváltoztathatatlan obektumok esetén a ciklusváltozót nyugodt szívvel módosíthatjuk, mert ekkor egy másik objektumra való hivatkozást fog tartalmazni. (Ugyanaz történik a ciklusváltozóval, mintha a megváltoztathatatlan változók esetére visszalapozunk.)

In [27]:
x=[1,2,3,4]
for xi in x:
    print(id(xi),end=", ")
    xi+=5
    print(id(xi))
    # xi egy uj referenciat tartalmaz ugyanarra az objektumra
    # mint x adott eleme
    # ha xi-t megvaltoztatom akkor nem a szamot valtoztatom meg
    # hanem xi-t lecserelem egy masik szamra mutato referenciara
    # x elemei meg mindig ugyanarra a szamra mutatnak 
x
140452173875744, 140452173875904
140452173875776, 140452173875936
140452173875808, 140452173875968
140452173875840, 140452173876000
Out[27]:
[1, 2, 3, 4]

Itt szerintem félrevezető volt az eredeti szöveg. Megtehetem azt is, hogy a ciklusváltozó segítségével módosítom az eredeti listám elemeit. Mivel az csak megváltoztathatatlan objektumokat tartalmazott, ezért a módosítás után a listában új objektumok lesznek. Ez ismét a már látott jelenség.

In [28]:
x=[1,2,3,4]
for i in range(len(x)):
    print(id(x[i]),end=", ")
    x[i]+=5
    print(id(x[i]))
x
140452173875744, 140452173875904
140452173875776, 140452173875936
140452173875808, 140452173875968
140452173875840, 140452173876000
Out[28]:
[6, 7, 8, 9]

Következmény megváltoztatható objektumok esetén

Megváltoztatható objektumok esetén más a helyzet. Az új változót módosítva magát az objektumot változtatjuk meg, tehát az eredeti változó is a megváltoztatott objektumra mutat már. (Előző eset.)

In [29]:
x=[[1],[1]]
for i in x:
    print(id(i),end=", ")
    i+=[2]
    print(id(i))
x
140451873520072, 140451873520072
140451873520136, 140451873520136
Out[29]:
[[1, 2], [1, 2]]

Viszont ha a ciklusváltozónak új értéket adunk, akkor az eredeti lista nem változik, mert a ciklusváltozó az újonnan létrehozott objektumra fog hivatkozni, de a régi listában változatlanok maradtak a hivatkozások. (Előző eset.)

In [30]:
x=[[1],[1]]
for i in x:
    print(id(i),end=", ")
    i=[2]
    print(id(i))
x
140451456813704, 140451458900040
140451456793608, 140451458900040
Out[30]:
[[1], [1]]

Függvények

A függvényekben is új hivatkozast kapunk az argumentumokra.

In [31]:
a=[1]
print(sys.getrefcount(a)) # 2, ahogy eddig is lattuk

def f(x): # itt az egyik
    print(sys.getrefcount(x)) # 2-vel tobb

f(a) #itt a masik
2
4

Következménye mgeváltoztathatatlan objektumok esetén

Ha az argumentum megváltoztathatatlan objektum, akkor a függvényben az argumentummal bármit csinálunk, az eredeti változó nem fog megváltozni!

A függvényen belüli változó egy új változó, ami egy új referenciát tartalmaz ugyanarra az objektumra, mint az eredeti változó. Bármit is csinálunk ezzel a referenciával, az eredeti változóval ez semmit sem csinál.

In [32]:
#int
def f(valami):
    valami+=1
    return

x=6
f(x)
x
Out[32]:
6
In [33]:
#string
def f(valami):
    valami+='fajta'
    return

x='majom'
f(x)
x
Out[33]:
'majom'
In [34]:
#tuple
def f(valami):
    valami+=(1,2,3)
    return

x=(0,1,2)
f(x)
x
Out[34]:
(0, 1, 2)

Következménye mgeváltoztatható objektumok esetén

Megváltoztatható objektumok esetén más a helyzet. Az új változót megváltoztatva magát az objektumot változtatjuk meg, és nem csak a referenciát írjuk át, tehát az eredeti referencia is már a megváltoztatott objektumra mutat.

In [35]:
def f(valami):
    valami+=[4,5,6]
    return

x=[0,1,2]
print(id(x))
f(x)
print(id(x))
x
140451873401608
140451873401608
Out[35]:
[0, 1, 2, 4, 5, 6]
In [36]:
def f(valami):
    valami+=5
    return

x=array([0,1,2])
print(id(x))
f(x)
print(id(x))
x
140451873487808
140451873487808
Out[36]:
array([5, 6, 7])

Ha nem módosítás, hanem értékadás történik (mint korábban), akkor ezmostmi másik objektumra fog hivatkozni.

In [37]:
def g(ezmostmi):
    ezmostmi=[3,4,5]
    print(ezmostmi, id(ezmostmi))

x=[0,1,2]
print(id(x))
g(x)
print(id(x))
x
140451456790600
[3, 4, 5] 140451456813448
140451456790600
Out[37]:
[0, 1, 2]

Láthatóság

Hol tudjuk használni a létrehozott változókat?


Amit egy függvényen belül hozunk létre (és nem adjuk vissza), az egyszerűen eltűnik.

In [38]:
def g():
    volt_nincs='Itt meg megvolt'
    print(volt_nincs)
    
g()
volt_nincs
Itt meg megvolt
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-38-18cdffe77a7a> in <module>()
      4 
      5 g()
----> 6 volt_nincs

NameError: name 'volt_nincs' is not defined

Ez a függvényekre is vonatkozik!

In [39]:
def g():
    def volt_nincs():
        print('Itt meg megvan')
    volt_nincs()
    
g()
volt_nincs()
Itt meg megvan
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-39-dd1d1ad67643> in <module>()
      5 
      6 g()
----> 7 volt_nincs()

NameError: name 'volt_nincs' is not defined

A fejlebb (balrább) definiált változók lejjebb (jobbrább) látszanak!

In [40]:
latom='latom'
def g():
    print(latom)
g()
latom
In [41]:
def f():
    eztislatom='nana'
    def g():
        print(eztislatom)
    g()
f()
nana

De ha ugyanazt a nevet felhasználjuk argumentumnévként, akkor azzal elfedjük a külső változót.

In [42]:
latom='latom'
def g(latom):
    print(latom)
g('nem is')
nem is

Ha új referenciát rendelünk bármilyen módon a névhez, azzal elfedjük a globális változót. Így nem írhatjuk át a név tartalmát, azaz nem cserélhetjük le a referenciát.

In [43]:
latom='latom'
def g():
    latom='nem is'
    print(latom)
g()
latom
nem is
Out[43]:
'latom'

Az objektumot, amire mutat, természetesen módosíthatjuk, ha megváltoztatható.

In [44]:
latom=[1,2,3]
def g():
    latom.append(4)
    print(latom)
g()
latom
[1, 2, 3, 4]
Out[44]:
[1, 2, 3, 4]

Ha külön kérjük, a referenciát is átírhatjuk.

In [45]:
latom='latom'
def g():
    global latom
    latom='igy igen'
    print(latom)
g()
latom
igy igen
Out[45]:
'igy igen'

A függvény definíciója után létrehozott globális nevek is látszanak a függvényben.

In [46]:
def f():
    print(hatdeezmegnemisvolthogyhogyhasznalhatom)
    
hatdeezmegnemisvolthogyhogyhasznalhatom='nem ugy van az'
f()
nem ugy van az

A for cikluson belül létrehozott változók azon kívül is látszanak.

In [47]:
for i in range(3):
    eztmindekilatja='jah'
eztmindekilatja
Out[47]:
'jah'

Tehát for ciklus változója még a for után is látszik!

In [48]:
for i in range(3):
    eztmindekilatja='jah'
i
Out[48]:
2