Magyarázat az osztály- és példányváltozók közötti különbségtételre.
A változók lényegében olyan szimbólumok, amelyek egy általunk használt értéket helyettesítenek a programban. Az objektumorientált programozás lehetővé teszi, hogy a változókat osztály- vagy példányszinten használjuk. A cikk célja, hogy egyértelmű különbséget tegyünk a Python objektummodellje által kínált változók típusai között, és a továbbiakban egy olyan sajátos viselkedést tárgyalunk, amelynek elhanyagolása meglepő eredményekhez vezethet. Tehát ássunk bele!
A Python objektummodellje szerint kétféle adatattribútum létezik a Python-objektumokon: az osztályváltozók és a példányváltozók.
Osztályváltozók – Az osztály definícióján belül (de a példánymódszereken kívül) deklaráltak. Nem kötődnek az osztály egyetlen konkrét objektumához sem, ezért az osztály összes objektuma számára közösek. Az osztályváltozó módosítása egyszerre érinti az összes objektum példányát.
Instance Variable – Az osztály konstruktor metódusán (a __init__
metóduson) belül deklarált. Az osztály adott objektumpéldányához kötődnek, ezért egy példányváltozó tartalma teljesen független az egyik objektumpéldánytól a másikig.
Kezdjük egy rövid és könnyen emészthető példával:
class Car:
wheels = 4 # <- Class variable def __init__(self, name):
self.name = name # <- Instance variable
Fentebb az alapvető, sallangmentes Car osztály van definiálva. Minden példányának a példányváltozó neve mellett lesz osztályváltozója a kerekek. Pusztítsuk el az osztályt, hogy hozzáférjünk a változókhoz.
>>> jag = Car('jaguar')
>>> fer = Car('ferrari')>>> jag.name, fer.name
('jaguar', 'ferrari')>>> jag.wheels, fer.wheels
(4, 4)>>> Car.wheels
4
A példányváltozó nevének elérése elég egyszerű. Az osztályváltozó elérésekor azonban egy kicsit rugalmasabbak vagyunk. A fentiekhez hasonlóan elérhetjük a kerekeket az objektum példányán vagy magán az osztályon keresztül.
Az osztályon keresztül történő névelérés megkísérlése szintén AttributeError-t eredményez, mivel a példányváltozók objektum specifikusak és a __init__
konstruktor meghívásakor jönnek létre. Ez a központi különbségtétel az osztály és a példányváltozók között.
>>> Car.name
AttributeError: type object 'Car' has no attribute 'name'
Most tegyük fel egyelőre, hogy a Jaguár autónknak 3 kereke van. (igen, ez hülyeség, de viseljük el!!! 😅). Ahhoz, hogy ezt megjelenítsük a kódunkban, módosíthatjuk a wheels változót.
>>> Car.wheels = 3
Amiatt, amit fentebb tettünk, minden autónak 3 kereke lesz, mivel egy osztályváltozót módosítottunk, ami a Car osztály minden példányára vonatkozik.
>>> jag.wheels, fer.wheels
(3, 3)
Ezért egy osztályváltozó módosítása az osztály névterében az osztály minden példányára hatással van. Vegyük vissza a módosítást, és módosítsuk a kerekek változót a jag objektummal.
>>> Car.wheels = 4
>>> jag.wheels = 3
Ez a kívánt eredményt fogja adni.
>>> jag.wheels, fer.wheels, Car.wheels
(3, 4, 4)
Mégis megkaptuk a kívánt eredményt, de a színfalak mögött az történt, hogy a jag objektumhoz egy új kerekek változó került hozzá, és ez az új változó árnyékolja az azonos nevű osztályváltozót, felülírja és elrejti azt. Mindkét wheels változóhoz az alábbiak szerint férhetünk hozzá:
>>> jag.wheels, jag.__class__.wheels
(3, 4)
Ez alapján megállapíthatjuk, hogy a jag.wheels = 3
létrehozott egy új példányváltozót, amelynek neve megegyezik az osztályváltozóéval (wheels). Ez egy fontos fogalom, amivel tisztában kell lennünk.
Az osztály- és példányspecifikus változók használatával biztosíthatjuk, hogy a kódunk betartsa a DRY elvét, hogy csökkentsük a kódon belüli ismétlődéseket.
Ezek ismerete úgy érzem, igazán hasznos lehet, és sok órányi hibakeresést takaríthat meg egy nap. 😇
A Python különböző típusú metódusairól (instance, class & static) bővebben ebben a bejegyzésemben olvashat.
Következtetések
- Az osztályváltozókat minden objektum megosztja, míg a instance változók az egyes példányok egyedi adataihoz tartoznak.
- A instance változó felülírja az azonos nevű osztályváltozókat, ami véletlenül hibákat vagy meglepő viselkedést hozhat a kódunkban.
.