Az előző bejegyzésben egy feladat kapcsán olyan specializált Range osztály definiálását kezdtük el, amely példányai a beépített range típushoz hasonlóak, de a cél, hogy a következő képességekkel is rendelkezzenek:
1) két azonos elemszámú Range, vagy azonos elemszámú Range és range objektum összeadható a + operátorral, vagy egymásból kivonható a – operátorral. Az eredményül kapott új Range objektum elemei a két sorozat azonos indexű elemeinek összege, illetve különbsége.
2) egy Range objektumhoz egy egész szám adható a ’+’ operátorral, illetve vonható ki a ’–’ operátorral. Az eredményül kapott új Range objektum elemei az eredeti Range elemei növelve, illetve csökkentve az adott egésszel.
3) egy Range objektum megszorozható egy egész számmal a ’*’ operátorral. Az eredményül kapott új Range objektum elemei az eredeti Range elemei szorozva az adott egésszel.
4) Egy Range példány + vagy – előjellel látható el. Az előbbi esetben önmagát kapjuk vissza, az utóbbi esetben egy olyan új Range objektumot, amelynek elemei az eredeti Range példány elemeinek -1-szerese.
Legutóbb addig jutottunk, hogy definiáltuk a Range osztályban a sorozat típusú konténerekre jellemző __repr__, __len__, __contains__, __getitem__, __bool__, count() és index() metódusokat. A fent felsorolt képességeket most fogjuk megvalósítani az osztályban.
Ahhoz, hogy a Range által képviselt véges, egész számokat tartalmazó számtani sorozatok elemenkénti összeadásából, illetve egy egész számmal történő elemenkénti szorzással előálló sorozatok start, step és stop argumentumait helyesen tudjuk meghatározni, nem árt egy előzetes matematikai átgondolás. Ezt foglalja össze a következő ábra:

Láthatjuk, hogy az összeadás vagy egésszel való szorzás eredményeként kapott sorozatoknak a start és step értékét könnyű megkapni, viszont a stop érték meghatározásához kell némi megfontolás.
Ha ezt átgondoltuk és megértettük, akkor a ’+’ operátornak megfelelő __add__ metódus, valamint a ’*’ operátornak megfelelő __mul__ metódus implementálása nem nehéz. Ezt láthatjuk az alábbi kódsorokban. Mivel az összeadás és szorzás kommutatív művelet, ezért a fordított kiértékelést végző __radd__ és __rmul__ metódusokat is implementáltuk.
|
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 |
from __future__ import annotations class Range: # Az előzőekben definiált __init__, __repr__, __len__, __contains__, __getitem__, __bool__, # count és index metódusokat, valamint a property definíciókat nem tüntettük fel. @staticmethod def _sgn(x): return 1 if x >= 0 else -1 def __add__(self, rn: int | Range | range) -> Range: if isinstance(rn, int): start = self.start + rn step = self.step stop = start + (len(self) - 1) * self.step + 1 * self._sgn(step) return type(self)(start,stop, step) elif isinstance(rn, (Range, range)): if len(self) != len(rn): raise ValueError('Az argumentumnak megegyező elemszámúnak kell lenni.') if self.step + rn.step == 0: return type(self)(0, 0) start = self.start + rn.start step = self.step + rn.step stop = self.start + rn.start + (len(self) - 1) * (self.step + rn.step) + 1 * self._sgn(step) return type(self)(start, stop, step) def __radd__(self, rn) -> Range: return self.__add__(rn) def __mul__(self, c: int) -> Range: start = self.start * c step = self.step * c stop = start + (len(self) - 1) * step + 1 * self._sgn(step) return type(self)(start, stop, step) def __rmul__(self, n: int) -> Range: return self.__mul__(n) |
Az összeadás és az egész számmal való szorzás rendelkezésre állásával a kivonás ’–’ operátorához tartozó __sub__ valamint a negálást végző __neg__ metódust is viszonylag egyszerűen megvalósíthatjuk:
|
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 |
class Range: # Az előzőekben definiált __init__, __repr__, __len__, __contains__, __getitem__, __bool__, # count és index metódusokat, valamint a property definíciókat nem tüntettük fel. @staticmethod def _sgn(x): return 1 if x >= 0 else -1 def __add__(self, rn: int | Range | range) -> Range:... def __radd__(self, rn) -> Range:... def __mul__(self, c: int) -> Range:... def __rmul__(self, n: int) -> Range:... def __sub__(self, rn: int | Range | range) -> Range: if isinstance(rn, int): return self + rn * -1 if isinstance(rn, (Range, range)): return self + type(self)(rn.start, rn.stop, rn.step) * -1 def __pos__(self) -> Range: return self def __neg__(self) -> Range: return self * -1 def to_range(self) -> range: return self._range def __eq__(self, rng: Range | range): if isinstance(rng, Range): return self._range == rng.to_range() elif isinstance(rng, range): return self._range == rng else: raise TypeError('Az argumentum típusa Range vagy range lehet.') def __hash__(self): return hash((self.start, self.stop, self.step)) |
A fenti kódban a teljesség kedvéért az egyenlőségvizsgálathoz szükséges __eq__ metódust is láthatjuk, valamint, hogy a Range példány halmaz eleme, illetve szótár kulcsa is lehessen, implementáltuk a __hash__ metódust is.
Mindezek után a Range osztály teljes definíciója a következő:
|
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# Python 3.10+ szükséges. from __future__ import annotations 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) @staticmethod def _sgn(x): return 1 if x >= 0 else -1 def __add__(self, rn: int | Range | range) -> Range: if isinstance(rn, int): start = self.start + rn step = self.step stop = start + (len(self) - 1) * self.step + 1 * self._sgn(step) return type(self)(start,stop, step) elif isinstance(rn, (Range, range)): if len(self) != len(rn): raise ValueError('Az argumentumnak megegyező elemszámúnak kell lenni.') if self.step + rn.step == 0: return type(self)(0, 0) start = self.start + rn.start step = self.step + rn.step stop = self.start + rn.start + (len(self) - 1) * (self.step + rn.step) + 1 * self._sgn(step) return type(self)(start, stop, step) def __radd__(self, rn) -> Range: return self.__add__(rn) def __mul__(self, c: int) -> Range: start = self.start * c step = self.step * c stop = start + (len(self) - 1) * step + 1 * self._sgn(step) return type(self)(start, stop, step) def __rmul__(self, n: int) -> Range: return self.__mul__(n) def __sub__(self, rn: int | Range | range) -> Range: if isinstance(rn, int): return self + rn * -1 if isinstance(rn, (Range, range)): return self + type(self)(rn.start, rn.stop, rn.step) * -1 def __pos__(self) -> Range: return self def __neg__(self) -> Range: return self * -1 def to_range(self) -> range: return self._range def __eq__(self, rng: Range | range): if isinstance(rng, Range): return self._range == rng.to_range() elif isinstance(rng, range): return self._range == rng else: raise TypeError('Az argumentum típusa Range vagy range lehet.') def __hash__(self): return hash((self.start, self.stop, self.step)) |
Annak bemutatására, hogy az így kialakított Range osztály teljesíti a fent sorolt elvárásokat, néhány tesztfuttatást látunk alább. /Az itt szereplő segédfüggvény az eredmények ellenőrzését segítő áttekinthető megjelenítést szolgálja/
|
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 |
# TESZT def range_list_str(r: Range|range, text: str = ''): r_str, r_list_str = map(str, (r, list(r))) return '{:30} -> {:20}'.format(text + r_str, r_list_str) print(range_list_str((Range(7, 17, 3)))) print(range_list_str(-1 * (Range(7, 17, 3) + 10))) # Eredmény: # Range(7, 17, 3) -> [7, 10, 13, 16] # Range(-17, -27, -3) -> [-17, -20, -23, -26] print(range_list_str(Range(-5, -16, -5))) print(range_list_str(Range(-2, +3, +2))) print(range_list_str((Range(-5, -16, -5) + Range(-2, +3, +2))*2-10)) # Eredmény: # Range(-5, -16, -5) -> [-5, -10, -15] # Range(-2, 3, 2) -> [-2, 0, 2] # Range(-24, -37, -6) -> [-24, -30, -36] print(range_list_str(Range(6, -4, -3))) print(range_list_str((range(-3, 10, 4)))) print(range_list_str(Range(6, -4, -3) - range(-3, 10, 4))) # Eredmény: # Range(6, -4, -3) -> [6, 3, 0, -3] # range(-3, 10, 4) -> [-3, 1, 5, 9] # Range(9, -13, -7) -> [9, 2, -5, -12] |
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, a „Kompozíció, delegálás és öröklés” fejezet, valamint „Osztályok operandus szerepben” fejezet.