Tegyük fel, hogy egy nagyvállalat piaca fokozatosan nő, és ezért több részlegében egy adott időn keresztül évente egyenletesen növelik a létszámot, vagyis minden évben azonos fővel növekszik a dolgozói állomány. A részlegekben mind az induló létszám, mind pedig az éves növekedés eltérő. A kérdés, hogy a növekedési időszak alatt a részlegben együttesen évente hogyan alakul a létszám? Erre kellene egy programmal választ adni.
A helyzetet matematikailag egyszerű modellezni, mert az egyes részlegek állománynövekedése egy adott kezdőértékkel és növekménnyel rendelkező számtani sorozat. A feladat megoldását azon számtani sorozat előállítása jelenti, amely az egyes részsorozatok elemenkénti összegzéséből adódik.
A megoldást eredményező programot több módon is meg lehet alkotni. Pl. listákkal, listaépítő kifejezéssel. Mivel azonban a sorozatok elemei, azaz a létszámok egész számok, ezért szóba jöhet a listánál memóriatakarékosabb range objektum, amely bár konténerobjektumnak számít, de nem tárolja az általa képviselt egész számokat.
Milyen egyszerű lenne a megoldás, ha a range elemenkénti összeadásra lenne képes, mondjuk a + operátorral. De ez nem értelmezett rá. Nem baj, akkor készítünk magunknak egy olyan specializált Range osztályt, amelynek példányai már lehetnek operandusai a + operátornak. De ha már készítünk egy ilyen osztályt, akkor tegyük képessé a kivonás műveletére is. Ekkor egy olyan új Range objektum jön létre, amelynek elemei a kivonás műveletében szereplő Range példányok elemeinek különbsége. Az sem lenne haszontalan, ha egész számmal való szorzást is lehetne végezni, aminek eredménye egy olyan új, Range típusú számtani sorozat lenne, amelynek elemei az eredeti sorozat elemeinek és az egész számnak a szorzata. Az egész számmal való osztás ebben az esetben nem valósítható meg, mert az osztás eredménye általában nem egész szám, így az eredménysorozat már nem lehetne Range típusú.
Az egyéni Range típus megvalósítására egy kézenfekvő megoldás, ha a range beépített típusból mint alaposztályból örököl, és a kívánt új metódusokat kell már csak implementálni. A gond az, hogy ez a módszer nem fog menni, mert a range azon osztályok közé tartozik, amelyekből nem lehet örökölni. A Pythonban van még néhány ilyen osztály, de szerencsére nem sok. Mit lehet akkor tenni?
Ha nem lehet örökölni, akkor megalkotjuk a Range osztályt kompozícióval. Vagyis a példányosításnál a start, stop és step argumentumok alapján előállítunk egy belső használatú range objektumot, és ezt fogjuk használni mindenütt, ahol lehet.
A kompozícióval való osztálydefiniálás egy kicsit több munkával jár, mint ha örökléssel valósítanánk meg, mert a range össze releváns metódusát implementálni kell, még akkor is, ha az csupán egy, a belső range objektumra történő delegáló metódushívást jelenti.
Valósítsuk meg tehát az egyéni Range osztályt. Ennek definícióját, valamint a tesztsorokat és eredményeket láthatjuk alább. A kódok egyszerűek és kommentek nélkül is érthetők, így nem kell magyarázatot fűzni hozzá.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# Python 3.10+ szükséges. class Range: def __init__(self, start: int, stop: int, step:int=1): self._range = range(start, stop, step) @property def start(self): return self._range.start @property def stop(self): return self._range.stop @property def step(self): return self._range.step def __repr__(self): return repr(self._range).capitalize() def __len__(self): return len(self._range) def __contains__(self, integer: int): return integer in self._range def __getitem__(self, index: int | slice): val = self._range[index] if isinstance(index, int): return val return type(self)(val.start, val.stop, val.step) def __iter__(self): return iter(self._range) def __reversed__(self): return reversed(self._range) def __bool__(self): return bool(self._range) def count(self, integer) -> int: return self._range.count(integer) def index(self, integer) -> int: return self._range.index(integer) # TESZT r = Range(-10, +10, +2) print(r, repr(r)) # Range(-10, 10, 2) Range(-10, 10, 2) print(r.start, r.stop, r.step) # -10 10 2 print(list(r)) # [-10, -8, -6, -4, -2, 0, 2, 4, 6, 8] print(list(reversed(r))) # [8, 6, 4, 2, 0, -2, -4, -6, -8, -10] print(r[2], r[-1], r[4:7]) # -6 8 Range(-2, 4, 2) print(len(r)) # 10 print(bool(r), bool(Range(0, 0))) # True False print(-2 in r, -20 in r) # True False print(r.count(-20), r.count(4)) # 0 1 print(r.index(4)) # 7 |
Ebben a bejegyzésben most csak azokat a metódusokat implementáltuk, amelyek olyan Range osztályt valósítanak meg, amely funkcionálisan a range beépített típus képességeivel rendelkezik.
A következő bejegyzésben fogunk továbblépni, és megvalósítani azon metódusokat, amelyek lehetővé teszik, hogy a Range példányok a +, – és * operátorokkal is használhatók legyenek.
E bejegyzés témájához a Python tudásépítés lépésről lépésre című e-könyv következő részei kapcsolódnak: a „Beépített konténerobjektumok” fejezet „range” alfejezete. az „Osztály vigyázz! – típuslétrehozás osztályokkal” fejezet, a „Mágikus metódusok egyedi igényre szabott osztályokban” fejezet, valamint a „Kompozíció, delegálás és öröklés” fejezet.