Tegyük fel, hogy egy cég egy új termékkel jelent meg a piacon. Az előzetes marketing kampány során számos érdeklődő nevét és email címét gyűjtötték be. A potenciális vásárlóknak egy termékmintát szeretnének küldeni az akciós ajánlatuk keretében. Ehhez azonban nem elég az email címek ismerete, hanem a postai kézbesítési címük is kellene. Van ugyan egy nyilvántartásuk meglévő ügyfeleik postacíméről, de ebben nem minden olyan vásárló szerepel, akik most az új termék iránt is érdeklődnek. Mivel nincs már idő a kézbesítési címek begyűjtésére, azért az a döntés született, hogy azon érdeklődők esetén, akik szerepelnek a meglévő postacím nyilvántartásban azoknak erre a címre küldik az ajánlatot a termékmintával. Akik pedig nem szerepelnek ebben az adatbázisban, azoknál az állami lakcímnyilvántartásból kérik le a lakcímüket, és oda postázzák az ajánlatot.
A feladat, hogy egy adott névhez meghatározzuk a kézbesítési címet.
Modellezzük le a cég postacímnyilvántartását és az állami lakcímnyilvántartást egy-egy dict típusú szótárral, ahol a kulcsok az ügyfelek/vevők neve, a hozzá társított érték pedig a kézbesítési cím, illetve a lakcím.
A feladat megoldása nem nehéz, mert csak annyit kell tenni, hogy a céges postacímeket tartalmazó szótár get() vagy setdefault() metódusát meghívjuk egy adott névvel, és default értéknek a központi lakcímnyilvántartásnak megfelelő szótár adott névhez tartozó értékét adjuk meg.
Azt, hogy a get() és setdefault() közül melyiket használjuk attól függ, hogy a céges postacímeket tartalmazó szótár tartalmát bővíteni akarjuk-e az új, potenciális jelöltekkel úgy, hogy kézbesítési címként a lakcímük szerepel. Mindkét esetre megírjuk a programkódokat, amelyek alább láthatók.
|
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 |
# Az állampolgárok lakcímnyilvántartását modellező szótár. home_address_register = dict(anna='Budapest Nyírfa u. 23.', béla='Budapest Rákóczi út 42.', cecil='Pécs Fő út 3.', dénes='Szeged Kertész u. 12.', erika='Győr Széna u. 17.', ferenc='Miskolc Patak u. 4.') # A cég nyilvántartása az eddig regisztrált ügyfeleinek kézbesítési címéről. mailing_address_register = dict(anna='Budapest Bazalt u. 15.', cecil='Pécs Losonc u. 22.', ferenc='Miskolc Déli u. 11.') # Az új termék iránt érdeklődők nevei, akiknek az ajánlatot kell küldeni. customer_names = ('dénes', 'anna', 'ferenc', 'erika') print(f'{" get ":-^60}') for name in customer_names: mailing_address = mailing_address_register.get(name, home_address_register.get(name)) print(f'Az ajánlatot kapja {name.capitalize()}, \tpostacíme: {mailing_address}') print('Aktuális kézbesítési címek:\n', mailing_address_register) print(f'{" setdefault ":-^60}') for name in customer_names: mailing_address = mailing_address_register.setdefault(name, home_address_register.get(name)) print(f'Az ajánlatot kapja {name.capitalize()}, \tpostacíme: {mailing_address}') print('Aktuális kézbesítési címek:\n', mailing_address_register) # Eredmények: # --------------------------- get ---------------------------- # Az ajánlatot kapja Dénes, postacíme: Szeged Kertész u. 12. # Az ajánlatot kapja Anna, postacíme: Budapest Bazalt u. 15. # Az ajánlatot kapja Ferenc, postacíme: Miskolc Déli u. 11. # Az ajánlatot kapja Erika, postacíme: Győr Széna u. 17. # Aktuális kézbesítési címek: # {'anna': 'Budapest Bazalt u. 15.', 'cecil': 'Pécs Losonc u. 22.', 'ferenc': 'Miskolc Déli u. 11.'} # ------------------------ setdefault ------------------------ # Az ajánlatot kapja Dénes, postacíme: Szeged Kertész u. 12. # Az ajánlatot kapja Anna, postacíme: Budapest Bazalt u. 15. # Az ajánlatot kapja Ferenc, postacíme: Miskolc Déli u. 11. # Az ajánlatot kapja Erika, postacíme: Győr Széna u. 17. # Aktuális kézbesítési címek: # {'anna': 'Budapest Bazalt u. 15.', 'cecil': 'Pécs Losonc u. 22.', 'ferenc': 'Miskolc Déli u. 11.', # 'dénes': 'Szeged Kertész u. 12.', 'erika': 'Győr Széna u. 17.'} |
Az eredményekből látható, hogy ami a konkrét feladatot illeti, az mindkét esetben egyformán teljesül. Az eltérés, hogy setdefault() esetén a céges nyilvántartás bővült.
Ez a két megoldás, bár a feladat követelményét teljesíti, futási idő szempontjából nem optimális. Ugyanis a default értékek (a lakcímek) mindkét esetben akkor is le lesznek kérdezve és előállítva, ha egyébként arra nem lenne szükség, mert a kulcs (a vevő neve) már szerepel a szótárban (a céges postacím nyilvántartásban). Ha a lekérdezések ideje érzékelhetően hosszú, akkor minél nagyobb a postacímnyilvántartás, azaz minél több címet akarunk megkapni, a teljes idő annál hosszabb lesz.
Ezen úgy lehetne segíteni, ha a default értékek előállítása csak akkor történne, ha a kulcs nincs a szótárban. Erre szolgálna a collections modul defaultdict típusú szótára. Azonban az most nem alkalmazható, mert annak default_factory attribútuma, amely a default érték előállítását végzi, csak egy olyan hívható objektum lehet, amely nem fogad argumentumot. Márpedig esetünkben a default érték (lakcím) függ a kulcstól (ügyfélnév).
Ezért egy egyéni, a defaultdict-hez hasonló szótárszerű objektum osztályát fogjuk definiálni, amely a dict típust örökli, továbbá, amelynek példányosításakor – a defaultdict-hez hasonlóan – első argumentumként egy, a kulcsot fogadni képes egyparaméteres default_factory nevű hívható objektum adható meg, és végül a __missing__ speciális metódust úgy írja felül, hogy abban a default érték előállítása a default_factory meghívásával történik átadva a hiányzó kulcsot. /A dict típusú szótár __missing__ speciális metódusa akkor kerül automatikusan meghívásra, ha kulccsal történő lekérdezés esetén a kulcs nem szerepel a szótárban./
Ennek a CustomDefaultDict nevű osztálynak a definíciója és az ezt használó kódsorokat és eredményeket láthatjuk alább.
|
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 |
from typing import Callable, Any, Hashable class CustomDefaultDict(dict): """Olyan defaultdict-hez hasonló szótár, amelynél a default_factory argumentum lehet: 1) egy egyparaméteres hívható objektum, amely kulccsal történő értékkikérés esetén, ha a kulcs nem szerepel a szótárban, meg lesz meghívva a kulccsal mint argumentummal. A hívás eredménye lesz a hiányzó kulcshoz tartozó érték. 2) akármilyen nem hívható objektum. Ez lesz a hiányzó kulcshoz tartozó érték. Bármelyik módon is áll elő a default_factory által szolgáltatott érték, az a hiányzó kulcshoz társítva új elemként kerül felvételre a szótárba. Ha a default_factory értéke None, akkor hiányzó kulcs esetén KeyError kivétel dobódik. """ def __init__(self, default_factory: Callable[[Hashable], Any] | Any = None, *args, **kw): super().__init__(*args, **kw) self.default_factory = default_factory def __missing__(self, key): """Ha kulccsal történő értékkikérés esetén a megadott kulcs nincs a szótárban, akkor ez a metódus lesz automatikusan meghívva. Ha a default_factory attribútum nem None, akkor az általa meghatározott érték lesz e metódus visszatérési értéke, ami egyúttal az értékkikérés eredménye is. """ if self.default_factory is not None: default_value = self.default_factory(key) if callable(self.default_factory) else self.default_factory self[key] = default_value return default_value raise KeyError(key) # Az állampolgárok lakcímnyilvántartását modellező szótár. home_address_register = dict(anna='Budapest Nyírfa u. 23.', béla='Budapest Rákóczi út 42.', cecil='Pécs Fő út 3.', dénes='Szeged Kertész u. 12.', erika='Győr Széna u. 17.', ferenc='Miskolc Patak u. 4.') # A cég nyilvántartása az eddig regisztrált ügyfeleinek kézbesítési címéről. mailing_address_register = dict(anna='Budapest Bazalt u. 15.', cecil='Pécs Losonc u. 22.', ferenc='Miskolc Déli u. 11.') # Az új termék iránt érdeklődők nevei, akiknek az ajánlatot kell küldeni. customer_names = ('dénes', 'anna', 'ferenc', 'erika') mailing_address_register = CustomDefaultDict(home_address_register.get, **mailing_address_register) for name in customer_names: mailing_address = mailing_address_register[name] print(f'Az ajánlatot kapja {name.capitalize()}, \tpostacíme: {mailing_address}') print('Aktuális kézbesítési címek:\n', mailing_address_register) # Eredmény: # Az ajánlatot kapja Dénes, postacíme: Szeged Kertész u. 12. # Az ajánlatot kapja Anna, postacíme: Budapest Bazalt u. 15. # Az ajánlatot kapja Ferenc, postacíme: Miskolc Déli u. 11. # Az ajánlatot kapja Erika, postacíme: Győr Széna u. 17. # Aktuális kézbesítési címek: # {'anna': 'Budapest Bazalt u. 15.', 'cecil': 'Pécs Losonc u. 22.', 'ferenc': 'Miskolc Déli u. 11.', # 'dénes': 'Szeged Kertész u. 12.', 'erika': 'Győr Széna u. 17.'} |
Látható, hogy a feladat szempontjából ugyanazt az eredményt kaptuk, mint a setdefault() esetében (a céges nyilvántartás is bővült), de a futási idő most kedvezőbb lesz, mert a lakcímek csak akkor lesznek lekérve, ha a céges nyilvántartásban az adott nevű ügyfél még nem szerepel.
Megjegyezzük, hogy bár a konkrét feladat szempontjából nem releváns, de a CustomDefaultDict úgy lett kialakítva, hogy nemhívható objektumot is fogadhat a default_factory. Ezzel, a get() és setdefault()-hoz hasonlóan, egy meghatározott objektumot tud biztosítani default értékként, ellentétben a defaultdict-tel, ahol ehhez egy konstans értéket visszaadó paraméternélküli függvényt kell átadni.
A Python tudásépítés lépésről lépésre című e-könyvben a dict típusú szótár nyilvános metódusainak ismertetése a „Beépített típusok nyilvános metódusai” fejezetben található, a defaultdict típussal pedig a „Készétel fogyasztás – a szabványos könyvtár moduljainak használata” fejezet „Speciális konténer típusok” alfejezete foglalkozik.