Skip to content

Latest commit

 

History

History
171 lines (145 loc) · 5.85 KB

README.md

File metadata and controls

171 lines (145 loc) · 5.85 KB

MetaDict

Enabling dot notation and IDE autocompletion

InstallationFeaturesDocumentationCompetitorsCitation

Python Version PyPI version CircleCI codecov License


MetaDict is designed to behave exactly like a dict while enabling (nested) attribute-style key access/assignment with IDE autocompletion support.

Many libraries claim to do the same, but fail in different ways (see Competitors).

Installation

$ pip install metadict

Features

  • Attribute-style key access and assignment (dot notation) with IDE autocompletion support
    from metadict import MetaDict
    
    cfg = MetaDict()
    cfg.optimizer = 'Adam'
    print(cfg.optimizer)
    >> Adam
    autocompletion demo
  • Nested key assignment similar to defaultdict from collections
    cfg = MetaDict(nested_assignment=True)
    cfg.model.type = 'Transformer' 
    print(cfg.model.type)
    >> Transformer
    
    # or restrict nested assignment via context manager
    cfg = MetaDict()
    with cfg.enabling_nested_assignment() as cfg:
        cfg.model.type = 'Transformer'
    cfg.new_model.type = 'MLP'
    >> AttributeError: 'MetaDict' object has no attribute 'new_model'
  • Is a dict
    dict_config = {'model': 'Transformer',
                   'optimizer': 'Adam'}    
    cfg = MetaDict(dict_config)
    print(isinstance(cfg, dict))
    >> True
    print(cfg == dict_config)
    >> True
  • Inbuilt json support
    import json
        
    cfg = MetaDict({'model': 'Transformer'})
    print(json.loads(json.dumps(cfg)))
    >> {'model': 'Transformer'}
  • Recursive conversion to dict
    cfg = MetaDict({'models': [{'name': 'Transformer'}, {'name': 'MLP'}]})
    print(cfg.models[0].name)
    >> Transformer
    
    cfg_dict = cfg.to_dict()
    print(type(cfg_dict['models'][0]))
    >> <class 'dict'>
    
    # Note: Appending a `dict` to a list within a `MetaDict` does not convert the `dict`.
    # MetaDict does not overwrite `list` so intercepting `append`. `extend`, etc. is currently not possible.
    # Simply wrap the appended or extended `dict` as a `MetaDict`.
    cfg.models.append({'name': 'RNN'})
    print(isinstance(cfg.models[-1], MetaDict))
    >> False
    
    cfg.models.append(MetaDict({'name': 'RNN'}))
    print(isinstance(cfg.models[-1], MetaDict))
    >> True
  • No namespace conflicts with inbuilt methods like items(), update(), etc.
    cfg = MetaDict()
    # Key 'items' is assigned as in a normal dict, but a UserWarning is raised
    cfg.items = [1, 2, 3]
    >> UserWarning: 'MetaDict' object uses 'items' internally. 'items' can only be accessed via `obj['items']`.
    print(cfg)
    >> {'items': [1, 2, 3]}
    print(cfg['items'])
    >> [1, 2, 3]
    
    # But the items method is not overwritten!
    print(cfg.items)
    >> <bound method Mapping.items of {'items': [1, 2, 3]}>
    print(list(cfg.items()))
    >> [('items', [1, 2, 3])]
  • References are preserved
    params = [1, 2, 3]    
    cfg = MetaDict({'params': params})
    print(cfg.params is params)
    >> True
    
    model_dict = {'params': params}
    cfg = MetaDict(model=model_dict)
    print(cfg.model.params is params)
    >> True
    
    # Note: dicts are recursively converted to MetaDicts, thus...
    print(cfg.model is model_dict)
    >> False
    print(cfg.model == model_dict)
    >> True

Documentation

Check the Test Cases for a complete overview of all MetaDict features.

Competitors

  • Addict
    • No key autocompletion in IDE
    • Nested key assignment cannot be turned off
    • Newly assigned dict objects are not converted to support attribute-style key access
    • Shadows inbuilt type Dict
  • Prodict
    • No key autocompletion in IDE without defining a static schema (similar to dataclass)
    • No recursive conversion of dict objects when embedded in list or other inbuilt iterables
  • AttrDict
    • No key autocompletion in IDE
    • Converts list objects to tuple behind the scenes
  • Munch
    • Inbuilt methods like items(), update(), etc. can be overwritten with obj.items = [1, 2, 3]
    • No recursive conversion of dict objects when embedded in list or other inbuilt iterables
  • EasyDict
    • Only strings are valid keys, but dict accepts all hashable objects as keys
    • Inbuilt methods like items(), update(), etc. can be overwritten with obj.items = [1, 2, 3]
    • Inbuilt methods don't behave as expected: obj.pop('unknown_key', None) raises an AttributeError

Citation

@article{metadict,
  title = {MetaDict - Enabling dot notation and IDE autocompletion},
  author = {Hillebrand, Lars},
  year = {2022},
  publisher = {GitHub},
  journal = {GitHub repository},
  howpublished = {\url{https://github.com/LarsHill/metadict}},
}