Schrijf je net lekker een stukje code. Heb je ineens behoefte aan een container. Dus je schrijft container = dict() en leeft verder.
Je stop een waarde als iets in de container: contaner['iets'] = waarde en je leeft weer vrolijk verder.
<vijf dagen later>
container is uitgebreid met 42 steutels waarvan 23 eigenlijk ook weer containers zijn en je leest je helemaal de blubber door de alle blokhaken en quotes. Auto aanvulling uit je editor werkt niet lekker meer, want string literals worden niet automatisch uitgeschreven. Pycharm heeft hier minder moeite mee, maar het werkt nog steeds niet lekker. En dus... Had je gehoopt dat je nooit was begonnen aan een dictionary, maar direct aan een eigen klasse.
Vanaf python3.7 heb je hier dataclasses voor. Dataclasses zijn super mooi, maar wel weer uitgebreid, en hebben hun eigen voor- en nadelen. Helemaal prachtig, maar het grootste nadeel is dat ze niet bestaan in python 3.6 en daarvoor. dotmap was er wel. DotMap maakt een dottable versie van een map object. Ofwel: een dict type die je met . notatie kunt uitlezen.
The catch
pip install dotmap
from dotmap import DotMap
dm = DotMap()
dm.bla = 'pir'
assert dm['bla'] == 'pir'
Heerlijk. En andersom werkt ook:
dm['spam'] = 'eggs'Super!
Het gaat echter fout bij:
>>> dm['items'] = 42
>>> dm.items
<bound method DotMap.items of DotMap(items=DotMap(...))>Dat komt omdat dict.items() een functie is. En aangezien die een hogere rang heeft in het ophalen van de eigenschappen, wordt die teruggleverd, in plaats van een verwijzing naar dotmap zelf. #woops
The Pro and Con
Nog zo'n catch is dat DotMap van zichzelf heel goed nieuwe items bij bedenkt:
>>> dm.bla.pir.zeut.reutel.fiets = 'ghehe'
>>> dm.bla.pir.zeut.reutel.keys()
odict_keys(['fiets'])
>>> dm.bla.pir.zeut.reutel.fiets
'ghehe'Super handig.
Tot het je bijt:
>>> i_dunno = dm.bla.pir.zeut.doesthisexist
>>> i_dunno
DotMap()Heel vervelend als je dan niets terug verwacht. Uiteraard is het wel een booleaans False argument, maar dit kan je aardig bijten.
Maar, dit kun je uitzetten. Dat is zo handig, dat ik daar een kleine twoliner voor heb gemaakt:
from functools import partial
EasyAccess = partial(DotMap, _dynamic=False)Door _dynamic op False te zetten worden items niet on the fly aangemaakt.
>>> ea = EasyAccess(dm)
>>> ea.bla.pir.zeut.reutel.fiets
'ghehe'
>>> ea.eat_this
Traceback (most recent call last):
...
KeyError: 'eat_this'Wanneer gaat dit nou 'shinen'? Bijvoorbeeld door gebruik te maken van yaml:
import yaml
with open('some.yaml','r') as handle:
some=EasyAccess(yaml.safe_loads(handle))
important_setting = some.valueimportant_setting zou een lege dotmap zijn als je hier geen _dynamic had gebruikt. Nu krijg je een fatsoenlijke error als je waarde niet in de config file staat.
Ditzelfde geldt natuurlijk ook voor json responses die terugkomen op van een GraphQL of RESTFull query.
Gebruik binnen educationwarehouse
Wij gebruiken in de backend best veel DotMap, ook al zijn ze soms verwarrend. Je moet de bovengenoemde uitzonderingen weten. Dan is het heel goed te gebruiken. EasyAccess is ontstaan na genoeg frustratie van items die je test die niet bestaan, programma's die doorlopen met foutieve waarden omdat ze pas at runtime echt gaan crashen, terwijl dat al veel eerder geconstateerd had kunnen worden. Vooral bij dependencies van config files.
Want eerlijk is eerlijk:
bla.die.pir.zeut = reutel.bla.meuk
# is veel leesbaarder én fijne typen als:
bla['die']['pir']['zeut'] = reutel['bla']['meuk']
