How to use a dot "." to access members of dictionary?
Asked Answered
T

37

475

How do I make Python dictionary members accessible via a dot "."?

For example, instead of writing mydict['val'], I'd like to write mydict.val.

Also I'd like to access nested dicts this way. For example

mydict.mydict2.val 

would refer to

mydict = { 'mydict2': { 'val': ... } }
Towrey answered 28/2, 2010 at 18:51 Comment(14)
Many of the situations where people use nested dicts would be just as well or better served by dicts with tuples as keys, where d[a][b][c] is replaced by d[a, b, c].Ploce
Can you please expand on replacing d[a][b][c] by d[a, b, c]? I didn't yet understand how it can be done.Towrey
It's not magic: foo={}; foo[1,2,3] = "one, two, three!"; foo.keys() => [(1,2,3)]Pontifex
Wow. Wow again. I didn't know tuples could be keys of dict. Wow third time.Towrey
Any object that is "hashable" may be used as the key of a dict. Most immutable objects are also hashable, but only if all of their contents are hashable. The code d[1, 2, 3] works because "," is the "create a tuple operator"; it's the same as d[(1, 2, 3)]. Parentheses are often optional around the declaration of a tuple.Limited
stackoverflow.com/questions/3031219/… has an answer that does more than you wantSupercargo
One situation where a tuple isn't just as well, json.dumps, TypeError: Keys must be a stringDraper
pypi.python.org/pypi/easydictOwensby
also see stackoverflow.com/questions/4984647/…Stereochromy
Have you considered the case where the key has a dot by itself - {"my.key":"value"}? Or when the key is a keyword, like "from"? I have considered it a couple of times, and it's more problems and troubleshooting than perceived benefits.Assortment
If you are not required to use dict, you can use namedtuple (docs.python.org/2/library/…) instead - this way you can access its members via the dot ( ".").Kaiserslautern
Cross-linking this question to a very similar one: How to use dot notation for dict in python? which has some interesting answers using SimpleNamespace from the built-in types module.Crosscurrent
See also: stackoverflow.com/questions/23463684/…Sniffle
@TodorMinakov having a dot in a dict key is as usual as coding in an upside down screen - and as easy to reverse too. Also, having an object with a from_whatever attribute doesn't trigger any error whatsoever.Comose
C
202

You can do it using this class I just made. With this class you can use the Map object like another dictionary(including json serialization) or with the dot notation. I hope to help you:

class Map(dict):
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    """
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                self[k] = v

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

Usage examples:

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
# Or
m['new_key'] = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']
Chimera answered 19/8, 2015 at 23:3 Comment(12)
To work on Python 3 I updated .iteritems() to .items()Casebook
Note that this will behave differently from common expectations in that it won't raise AttributeError if the attribute does not exist. Instead it will return None.Psychologize
Recommend adding getstate and setstate so that deep copy and other systems can support it.Mesenchyme
You can simplify your constructor to self.update(*args,**kwargs). Also, you can add __missing__(self,key): value=self[key]= type(self)(); return value. Then you can add missing entries using dot notation. If you want it to be pickable, you can add __getstate__ and __setstate__Subcortex
what is the use of __setitem__? It seems that without these lines the class works still wellLudie
@Turtle you can read more about it here diveintopython.net/object_oriented_framework/…Chimera
This would make hasattr(Map, 'anystring') is true. which means the hasattr would always return True due to overriding __getattr__`Trend
Hey @Chimera , @ Kugel , Is there any conda package available for this code.It would be very much helpfulNicknickel
@Nicknickel there is not a package for that yet, but feel free to create one if you wish and you could include the link here as a comment.Chimera
It appears that setting m.new_key = { 'foo': 'bar' } means that m.new_key.foo doesn't return bar. Maybe something like; def __setitem__( self, key, value ): super( GenericObject, self ).__setitem__( key, value ) if isinstance( value, dict ): self.__dict__.update( {key: GenericObject( value ) } ) else: self.__dict__.update( {key: value} ) is more appropriate?Ines
Is there anyway to get this to represent itself as a dict for functions that test for type()? - I tried adding self.__class__ = dict but it doesn't seem to help.Chihli
For List support and infinite nesting, See this answerComeuppance
S
520

I've always kept this around in a util file. You can use it as a mixin on your own classes too.

class dotdict(dict):
    """dot.notation access to dictionary attributes"""
    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

mydict = {'val':'it works'}
nested_dict = {'val':'nested works too'}
mydict = dotdict(mydict)
mydict.val
# 'it works'

mydict.nested = dotdict(nested_dict)
mydict.nested.val
# 'nested works too'
Sallyanne answered 15/5, 2014 at 22:29 Comment(27)
Very simple answer, great! Do you happen to know what I would need to to in order to have tab-completion in IPython work? The class would need to implement __dir__(self), but somehow I cannot get it to work.Hydromagnetics
+1 for simplicity. but doesn't seem to work on nested dicts. d = {'foo': {'bar': 'baz'}}; d = dotdict(d); d.foo.bar throws an attribute error, but d.foo work fine.Corell
Yep this does not work for complex nested structures.Stanstance
I found this code, it has error raise: goodcode.io/articles/python-dict-objectConceptionconceptual
This is really neat. You could probably use a defaultdict, like I did below to get nested access.Leahleahey
it does not auto-dotdict its members, so you'd have to do d = {'foo': dotdict({'bar': 'baz'})}; d = dotdict(d); and d.foo.bar worksSallyanne
@Corell you could simply return dotdict type object in the getter method to recursively access attributes with dot notation like: python class DotDict(dict): """dot.notation access to dictionary attributes""" def __getattr__(*args): val = dict.get(*args) return DotDict(val) if type(val) is dict else val __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ Tor
Any reason why you use dict.get and not dict.__getitem__?Periphrasis
After experimenting with it, it seems get is indeed a bad idea since it will return None instead of raising an error for missing items ...Periphrasis
Great idea: as far as "style" goes, I would rather have dot notion on dicts rather than brackets with big ugly strings nested in them. Will be using, thanks.Outwear
Is there a way to retain the dot access when passing the DotDict to a function func(**mydict) and accepting it like def func(**mydict): print(mydict.val)? Currently, this throws an AttributeError: 'dict' object has no attribute 'val'.Mckinley
@Tor thanks for your code that works. How can you write for the nested setter??Cadence
@tmthyjames, another way to allow for nesting: gist.github.com/miku/dc6d06ed894bc23dfd5a364b7def5ed8Celom
For those wanting tab-completion of the items instead of the methods, just add __dir__ = dict.keysEconomy
This is excellent solution, but you can achieve similar result by using dataclasses library.Supination
Doesn't work if the dict contains lists or even lists of dicts.Comeuppance
Very elegant - one helpful addition though! You should reimplement __getattr__ to raise an AttributeError rather than KeyError if not found, so getattr(d, 'nonexistant', default) works properly. It's easy: def __getattr__(self, key:str)->Any: try: return self.__getitem__(key) except KeyError as ex: raise AttributeError(key) from ex`Paludal
@TrevorGross after some thought, I would personally say I disagree. The first reason is that it's a bit slower to implement it this way, since it involves an extra function call. The other reason though is that I think in is actually shorter and more pythonic to use in general - i.e., 'key' in d instead of getattr(d, 'key').Sennacherib
Hey @rv.kvetch - __getattr__ isn't just for getattr(), it's also what defines access for dot notation. So, in the example, mydict.not_exist will raise a KeyError because that's what dict.get() does. Generally though, any_cls_instance.not_exist raises AttributeError, and you'd expect the same from this dict class for compatibility. Making getattr(d, 'key', default) work is just a nice effect (d.get('key', default) is the equivalent). A single function call is negligible. This all won't matter in many implementations, but it does where object compatibility is required.Paludal
right, that's certainly good point, but I still think d.get('key', default) is the way to go here; also it's a little bit shorter to write out I guess. Though I totally get wanting the hasattr and getattr to work, but I feel either a in check or get would probably be a little better.Sennacherib
If you need it to work like a dictionary then yes. If you need it to work like a class then no. I think you're still missing the point - it's not only about personal use of getattr, it's about being able to pass it to anything that take a class as an input, not a dict, and potentially relies on EAFP (very common in python). getattr is just one such function that replies on EAFP - any python library, or your own code from before, might also use it.Paludal
why python is not doing this by default?Jaguar
I wanted to do a parallel with what I'm learning in JavaScript with object and method in it. Whith your solution I get same result, associating a lambda to a key. a good way to put several functions in a dict. e.g my_dict.my_key(arg)Jaguar
Is it possible to have the dotdict object be compatible with vars() ? I tried to add __dict__ = dict to the dotdict class, but that doesn't seem to workBolinger
not working with pickle.dump. Why?Contemplation
A slightly better version to enable tab-completion: def __dir__(self): return dir(dict) + list(self.keys())Amberambergris
To use pickle for saving and loading, two more methods to implement: def __getstate__(self): return self.__dict__ and def __setstate__(self, d): self.__dict__.update(d). See here.Contemplation
C
202

You can do it using this class I just made. With this class you can use the Map object like another dictionary(including json serialization) or with the dot notation. I hope to help you:

class Map(dict):
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    """
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                self[k] = v

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

Usage examples:

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
# Or
m['new_key'] = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']
Chimera answered 19/8, 2015 at 23:3 Comment(12)
To work on Python 3 I updated .iteritems() to .items()Casebook
Note that this will behave differently from common expectations in that it won't raise AttributeError if the attribute does not exist. Instead it will return None.Psychologize
Recommend adding getstate and setstate so that deep copy and other systems can support it.Mesenchyme
You can simplify your constructor to self.update(*args,**kwargs). Also, you can add __missing__(self,key): value=self[key]= type(self)(); return value. Then you can add missing entries using dot notation. If you want it to be pickable, you can add __getstate__ and __setstate__Subcortex
what is the use of __setitem__? It seems that without these lines the class works still wellLudie
@Turtle you can read more about it here diveintopython.net/object_oriented_framework/…Chimera
This would make hasattr(Map, 'anystring') is true. which means the hasattr would always return True due to overriding __getattr__`Trend
Hey @Chimera , @ Kugel , Is there any conda package available for this code.It would be very much helpfulNicknickel
@Nicknickel there is not a package for that yet, but feel free to create one if you wish and you could include the link here as a comment.Chimera
It appears that setting m.new_key = { 'foo': 'bar' } means that m.new_key.foo doesn't return bar. Maybe something like; def __setitem__( self, key, value ): super( GenericObject, self ).__setitem__( key, value ) if isinstance( value, dict ): self.__dict__.update( {key: GenericObject( value ) } ) else: self.__dict__.update( {key: value} ) is more appropriate?Ines
Is there anyway to get this to represent itself as a dict for functions that test for type()? - I tried adding self.__class__ = dict but it doesn't seem to help.Chihli
For List support and infinite nesting, See this answerComeuppance
O
182

Install dotmap via pip

pip install dotmap

It does everything you want it to do and subclasses dict, so it operates like a normal dictionary:

from dotmap import DotMap

m = DotMap()
m.hello = 'world'
m.hello
m.hello += '!'
# m.hello and m['hello'] now both return 'world!'
m.val = 5
m.val2 = 'Sam'

On top of that, you can convert it to and from dict objects:

d = m.toDict()
m = DotMap(d) # automatic conversion in constructor

This means that if something you want to access is already in dict form, you can turn it into a DotMap for easy access:

import json
jsonDict = json.loads(text)
data = DotMap(jsonDict)
print data.location.city

Finally, it automatically creates new child DotMap instances so you can do things like this:

m = DotMap()
m.people.steve.age = 31

Comparison to Bunch

Full disclosure: I am the creator of the DotMap. I created it because Bunch was missing these features

  • remembering the order items are added and iterating in that order
  • automatic child DotMap creation, which saves time and makes for cleaner code when you have a lot of hierarchy
  • constructing from a dict and recursively converting all child dict instances to DotMap
Ouch answered 11/2, 2015 at 19:57 Comment(8)
:-) can you make it work with keys that have already dot in the name? {"test.foo": "bar"} can be accessed via mymap.test.foo That would be fantastic. It will take some regressesion to convert a flat map to a deep map then apply DotMap to it, but it's worth it!Cahoot
Neat. Any way to make tab listing / completion work with the keys in Jupyter notebook? Dot-style access is most valuable for interactive use.Desex
@Desex Cool product. Never heard of it before, so I'm not sure how to make its autocomplete work. I agree using DotMap with autocomplete works best. I use Sublime Text, which autocompletes previously typed keywords.Ouch
I find that it lacks dictionary extraction for things like **kwargs or c = {**a, **b}. In fact, it fails quietly, it behaves like an empty dictionary when extracting.Amand
@SimonStreicher I tested this with m = DotMap(); m.a = 2; m.b = 3; print('{a} {b}'.format(**m)); and I got the expected 2 3. If you have a proven broken case that works for dict() but not DotMap(), please submit your code to the Issues tab in GitHub.Ouch
@ChrisRedford hmm, my Linux Mint 17, with Python version Python 3.6.2 :: Anaconda custom (64-bit) and dotmap version sys.version_info(major=3, minor=6, micro=2, releaselevel='final', serial=0) returns an unexpected KeyError: 'a' for your example. Furthermore (lambda **kwargs:(print(kwargs)))(**DotMap(a=5,b=2)) prints an empty dict {}. The rest seems to work fine, such as DotMap(a=5)['a'] returning 5 as expected. I assumed this was general, not just my system.Amand
@ChrisRedford I submitted my findings as requested: github.com/drgrib/dotmap/issues/26 , I hope this helps.Amand
@ChrisRedford wouldn't you just run toDict() prior to explosion with **kwargs to explode the native dict?Dusa
M
78

Use SimpleNamespace:

>>> from types import SimpleNamespace   
>>> d = dict(x=[1, 2], y=['a', 'b'])
>>> ns = SimpleNamespace(**d)
>>> ns.x
[1, 2]
>>> ns
namespace(x=[1, 2], y=['a', 'b'])
Melonie answered 25/12, 2019 at 17:57 Comment(5)
This approach works better. (with json loaded from file)Pahari
Does this account for nested dicts?Lowell
Not support nested Dict. docs.python.org/3.3/library/types.html#types.SimpleNamespaceTreytri
Nice, I have a function that takes the output from Argparse.parse_args() which returns a namespace instead of a dictionary. I was looking for a nice way to test that function. This is perfect.Dharma
PyCharm code completion does not work for SimpleNamespace, unlike regular classes. This is a deal-breaker for me https://mcmap.net/q/25226/-pycharm-code-completion-does-not-work-for-simplenamespace/3753826Missy
P
60

Derive from dict and and implement __getattr__ and __setattr__.

Or you can use Bunch which is very similar.

I don't think it's possible to monkeypatch built-in dict class.

Pidgin answered 28/2, 2010 at 18:55 Comment(3)
What does monkeypatch mean exactly? I have heard about it but not used. (Sorry that I ask such newbie questions, I am not that good with programming yet (I'm only 2nd year student.))Towrey
Monkeypatching is using the dynamicity of Python (or whatever language) to change something that would usually be defined in source code. It especially applies to changing the definition of classes after they are created.Ploce
If you're using this functionality a lot, beware of the speed of Bunch. I was using it pretty frequently and it ended up consuming a third of my request time. Check out my answer for a more detailed explanation of this.Mefford
L
29

Fabric has a really nice, minimal implementation. Extending that to allow for nested access, we can use a defaultdict, and the result looks something like this:

from collections import defaultdict

class AttributeDict(defaultdict):
    def __init__(self):
        super(AttributeDict, self).__init__(AttributeDict)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(key)

    def __setattr__(self, key, value):
        self[key] = value

Make use of it as follows:

keys = AttributeDict()
keys.abc.xyz.x = 123
keys.abc.xyz.a.b.c = 234

That elaborates a bit on Kugel's answer of "Derive from dict and and implement __getattr__ and __setattr__". Now you know how!

Leahleahey answered 22/12, 2016 at 2:29 Comment(2)
Nice to include a defaultdict - however this seems to only work when starting a dict from scratch. If we need to convert an existing dict to a "dotdict" recursively. Here's an alternative dotdict which allow to convert existing dict object recursively: gist.github.com/miku/…Celom
I'd be very afraid to use this class as it REALLY does not adheres to the Liskov substitution principle. But that's not the main issue, many problems can arise, mostly revolving around accidental attribute creation, some of which could be solved by a more complex implementation while some couldn't. The updated implementation in your repo is quite different, but not in terms of laying out a minefield for the naive.Megalo
J
24

I tried this:

class dotdict(dict):
    def __getattr__(self, name):
        return self[name]

you can try __getattribute__ too.

make every dict a type of dotdict would be good enough, if you want to init this from a multi-layer dict, try implement __init__ too.

Jessamine answered 9/2, 2012 at 3:50 Comment(2)
I found it useful to embed this inside a function by placing def docdict(name): before it and then ` if isinstance(name, dict): return DotDict(name) return name `Thomasinathomasine
great simple example.. I extended this a little bit so that a nested dict is easily chained, similar to @DanielMoskovich, but also returns leaf nodes correctly for int, string, etc... or null if not found class dotdict(dict): def __getattr__(self, name): if name not in self: return None elif type(self[name]) is dict: return JsonDot(self[name]) else: return self[name]Oubliette
A
21

I recently came across the 'Box' library which does the same thing.

Installation command : pip install python-box

Example:

from box import Box

mydict = {"key1":{"v1":0.375,
                    "v2":0.625},
          "key2":0.125,
          }
mydict = Box(mydict)

print(mydict.key1.v1)

I found it to be more effective than other existing libraries like dotmap, which generate python recursion error when you have large nested dicts.

link to library and details: https://pypi.org/project/python-box/

Anestassia answered 3/10, 2019 at 4:46 Comment(0)
P
15

Don't. Attribute access and indexing are separate things in Python, and you shouldn't want them to perform the same. Make a class (possibly one made by namedtuple) if you have something that should have accessible attributes and use [] notation to get an item from a dict.

Ploce answered 28/2, 2010 at 19:1 Comment(13)
Thanks for the answer. But take a look at this question that I also just asked: stackoverflow.com/questions/2352252/… This seems like a good idea to use . instead of [] to access complicated data structures in Mako templates.Towrey
@bodacydo: Mako template parsing is intentionally a simplification of Python syntax with many, many features discarded. Don't confuse Mako syntax with Python syntax. They're entirely different with entirely different purposes.Pettifogging
I can see a use case for this; in fact, I did it just a couple weeks ago. In my case I wanted an object that I could access attributes with dot notation. I found it very easy to simply inherit from dict so I get all the dict features built-in, but the public interface to this object uses dot notation (it's essentially a read-only interface to some static data). My users are much happier with 'foo.bar' than with 'foo["bar"]' and I'm happy that I can piggy-back off of the features of the dict datatype.Pontifex
I went with using Bunch from ActiveState code.activestate.com/recipes/… This is good enough for now (until I learn good Python style).Towrey
@Bryan Oakley: So you built a namedtuple for them? That seems like too much work to me. Why not just create a namedtuple?Pettifogging
@S.Lott: I was using an older version of python that doesn't have namedtuple.Pontifex
You already know good Python style: we're telling you, don't pretend that the values of a dict are attributes. It's bad practice. For example, what if you want to store a value with the same name as an existing attribute of a dict, like "items" or "get" or "pop"? Probably something confusing. So don't do it!Limited
Oops, I forgot about attributes like 'items', 'get' or 'pop. Thanks for bringing up this important example!Towrey
@Gabe, it has been a long time... but I think it is worth saying. It is not "good enough in JS": it is "horrible enough in JS". It gets funny when you store keys/attr that have the same name as other important attributes in the prototypic chain.Regardant
No, I'm not sorry for necrobumping. Using attribute access for key retrieval could just work cleanly if not for the ingrained ABC methods, like items or keys. If you have a dot access dict class and all dot accesses are key accesses then that's a clean interface which is more usable and extendable than the bracket access. All non-underscore-prefixed ABC methods should have a global alternative, so that getattr would never meet non-underscore-prefixed names that are not intended as keys. For this to work, new globals should be introduced (a lot) and we alsoneed anoptional prefix for globalsMegalo
For example for v in .values(mydict) instead of for v in mydict.values(). This way you could use a name id alongside the builtin id by referring to the builtin as .id (even though that's not the point of this). I used to have a 'How dare he???' feeling when a name of mine clashed with a builtin. Now, after many years, I just usually don't care (unless it's a type).Megalo
I disagree with the rather dogmatic statement being made here. While it is generally true, the . notation is much clearer and easier to read. I can see plenty of use cases where the chances of it causing an issue are very small.Gissing
I agree with this answer. You shouldn't muck around with builtin members like __getattr__. However, I suggest that using VIEWS is a very pythonic way to handle this problem. For example, take a look at the objview example here goodcode.io/articles/python-dict-objectRudnick
G
15

If you want to pickle your modified dictionary, you need to add few state methods to above answers:

class DotDict(dict):
    """dot.notation access to dictionary attributes"""
    def __getattr__(self, attr):
        return self.get(attr)
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

    def __getstate__(self):
        return self

    def __setstate__(self, state):
        self.update(state)
        self.__dict__ = self
Gowen answered 1/5, 2016 at 15:3 Comment(4)
Thanks for the comment about pickling. I was driven crazy by this error and only realized that it was because of this issue!Lonely
Also happens when you use copy.deepcopy. This addition is needed.Mesenchyme
Simplification: __getattr__ = dict.getGruelling
With Python 3.10, I get the following error regarding pickling: PicklingError: Can't pickle <class '__main__.dot_dict'>: attribute lookup dot_dict on __main__ failedGawain
I
15

You can achieve this using SimpleNamespace

from types import SimpleNamespace
# Assign values
args = SimpleNamespace()
args.username = 'admin'

# Retrive values
print(args.username)  # output: admin
Indulgence answered 7/12, 2021 at 22:23 Comment(3)
Duplicated of the previous answer by Dmitry Zotikov https://mcmap.net/q/20631/-how-to-use-a-dot-quot-quot-to-access-members-of-dictionaryMissy
It is similar, but The way I represent it is different. Ex:1 d = [], d.append({key: value}) Ex: 2 d = [{key: value}] Output is the same, but the usage is different.Indulgence
Please note that a SimpleNamespace is not a subclass of dict, so this will not work with json.dumps, in case you're trying to make your objects JSON-serializableGangway
S
13

To build upon epool's answer, this version allows you to access any dict inside via the dot operator:

foo = {
    "bar" : {
        "baz" : [ {"boo" : "hoo"} , {"baba" : "loo"} ]
    }
}

For instance, foo.bar.baz[1].baba returns "loo".

class Map(dict):
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.items():
                    if isinstance(v, dict):
                        v = Map(v)
                    if isinstance(v, list):
                        self.__convert(v)
                    self[k] = v

        if kwargs:
            for k, v in kwargs.items():
                if isinstance(v, dict):
                    v = Map(v)
                elif isinstance(v, list):
                    self.__convert(v)
                self[k] = v

    def __convert(self, v):
        for elem in range(0, len(v)):
            if isinstance(v[elem], dict):
                v[elem] = Map(v[elem])
            elif isinstance(v[elem], list):
                self.__convert(v[elem])

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]
Sundried answered 31/5, 2017 at 2:11 Comment(3)
Python 3: replace iteritems() with items() and xrange() with range()Stepdaughter
This is a great update to epool's solution and deepak's update. No external dependencies and works well. Hopefully it bubbles up to the top to save folks some sleuthing.Simulant
Another version of this which works for more typesComeuppance
D
11

Building on Kugel's answer and taking Mike Graham's words of caution into consideration, what if we make a wrapper?

class DictWrap(object):
  """ Wrap an existing dict, or create a new one, and access with either dot 
    notation or key lookup.

    The attribute _data is reserved and stores the underlying dictionary.
    When using the += operator with create=True, the empty nested dict is 
    replaced with the operand, effectively creating a default dictionary
    of mixed types.

    args:
      d({}): Existing dict to wrap, an empty dict is created by default
      create(True): Create an empty, nested dict instead of raising a KeyError

    example:
      >>>dw = DictWrap({'pp':3})
      >>>dw.a.b += 2
      >>>dw.a.b += 2
      >>>dw.a['c'] += 'Hello'
      >>>dw.a['c'] += ' World'
      >>>dw.a.d
      >>>print dw._data
      {'a': {'c': 'Hello World', 'b': 4, 'd': {}}, 'pp': 3}

  """

  def __init__(self, d=None, create=True):
    if d is None:
      d = {}
    supr = super(DictWrap, self)  
    supr.__setattr__('_data', d)
    supr.__setattr__('__create', create)

  def __getattr__(self, name):
    try:
      value = self._data[name]
    except KeyError:
      if not super(DictWrap, self).__getattribute__('__create'):
        raise
      value = {}
      self._data[name] = value

    if hasattr(value, 'items'):
      create = super(DictWrap, self).__getattribute__('__create')
      return DictWrap(value, create)
    return value

  def __setattr__(self, name, value):
    self._data[name] = value  

  def __getitem__(self, key):
    try:
      value = self._data[key]
    except KeyError:
      if not super(DictWrap, self).__getattribute__('__create'):
        raise
      value = {}
      self._data[key] = value

    if hasattr(value, 'items'):
      create = super(DictWrap, self).__getattribute__('__create')
      return DictWrap(value, create)
    return value

  def __setitem__(self, key, value):
    self._data[key] = value

  def __iadd__(self, other):
    if self._data:
      raise TypeError("A Nested dict will only be replaced if it's empty")
    else:
      return other
Draper answered 29/8, 2012 at 22:15 Comment(0)
J
8

Use __getattr__, very simple, works in Python 3.4.3

class myDict(dict):
    def __getattr__(self,val):
        return self[val]


blockBody=myDict()
blockBody['item1']=10000
blockBody['item2']="StackOverflow"
print(blockBody.item1)
print(blockBody.item2)

Output:

10000
StackOverflow
Jacksnipe answered 19/9, 2018 at 7:49 Comment(0)
W
7

I like the Munch and it gives lot of handy options on top of dot access.

import munch

temp_1 = {'person': { 'fname': 'senthil', 'lname': 'ramalingam'}}

dict_munch = munch.munchify(temp_1)

dict_munch.person.fname

Wesle answered 5/11, 2017 at 16:2 Comment(0)
A
6

The language itself doesn't support this, but sometimes this is still a useful requirement. Besides the Bunch recipe, you can also write a little method which can access a dictionary using a dotted string:

def get_var(input_dict, accessor_string):
    """Gets data from a dictionary using a dotted accessor-string"""
    current_data = input_dict
    for chunk in accessor_string.split('.'):
        current_data = current_data.get(chunk, {})
    return current_data

which would support something like this:

>> test_dict = {'thing': {'spam': 12, 'foo': {'cheeze': 'bar'}}}
>> output = get_var(test_dict, 'thing.spam.foo.cheeze')
>> print output
'bar'
>>
Ause answered 23/9, 2011 at 20:30 Comment(2)
This is similar to my original plan. Simple, neat, and with no dependencies. Works also on nested cases. I modified the last line though to return None instead of {} for consistency with the dict.get() for non-existing keys: return current_data or None. Cheers!Corena
i adapted your answer for my case, and used a pipe since some of the keys had dots, and sometimes they were lists def _get_value_by_pipe_notation(path: str, search: dict): current = search for chunk in path.split('|'): if isinstance(current, dict): current = current.get(chunk, {}) elif isinstance(current, list): current = current[int(chunk)] else: return "#error#" if isinstance(current, (int, float)): return current elif len(current): return current else: return "#notfound#"Numen
S
6

I dislike adding another log to a (more than) 10-year old fire, but I'd also check out the dotwiz library, which I've recently released - just this year actually.

It's a relatively tiny library, which also performs really well for get (access) and set (create) times in benchmarks, at least as compared to other alternatives.

Install dotwiz via pip

pip install dotwiz

It does everything you want it to do and subclasses dict, so it operates like a normal dictionary:

from dotwiz import DotWiz

dw = DotWiz()
dw.hello = 'world'
dw.hello
dw.hello += '!'
# dw.hello and dw['hello'] now both return 'world!'
dw.val = 5
dw.val2 = 'Sam'

On top of that, you can convert it to and from dict objects:

d = dw.to_dict()
dw = DotWiz(d) # automatic conversion in constructor

This means that if something you want to access is already in dict form, you can turn it into a DotWiz for easy access:

import json
json_dict = json.loads(text)
data = DotWiz(json_dict)
print data.location.city

Finally, something exciting I am working on is an existing feature request so that it automatically creates new child DotWiz instances so you can do things like this:

dw = DotWiz()
dw['people.steve.age'] = 31

dw
# ✫(people=✫(steve=✫(age=31)))

Comparison with dotmap

I've added a quick and dirty performance comparison with dotmap below.

First, install both libraries with pip:

pip install dotwiz dotmap

I came up with the following code for benchmark purposes:

from timeit import timeit

from dotwiz import DotWiz
from dotmap import DotMap


d = {'hey': {'so': [{'this': {'is': {'pretty': {'cool': True}}}}]}}

dw = DotWiz(d)
# ✫(hey=✫(so=[✫(this=✫(is=✫(pretty={'cool'})))]))

dm = DotMap(d)
# DotMap(hey=DotMap(so=[DotMap(this=DotMap(is=DotMap(pretty={'cool'})))]))

assert dw.hey.so[0].this['is'].pretty.cool == dm.hey.so[0].this['is'].pretty.cool

n = 100_000

print('dotwiz (create):  ', round(timeit('DotWiz(d)', number=n, globals=globals()), 3))
print('dotmap (create):  ', round(timeit('DotMap(d)', number=n, globals=globals()), 3))
print('dotwiz (get):  ', round(timeit("dw.hey.so[0].this['is'].pretty.cool", number=n, globals=globals()), 3))
print('dotmap (get):  ', round(timeit("dm.hey.so[0].this['is'].pretty.cool", number=n, globals=globals()), 3))

Results, on my M1 Mac, running Python 3.10:

dotwiz (create):   0.189
dotmap (create):   1.085
dotwiz (get):   0.014
dotmap (get):   0.335
Sennacherib answered 23/6, 2022 at 21:17 Comment(0)
H
4

This also works with nested dicts and makes sure that dicts which are appended later behave the same:

class DotDict(dict):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Recursively turn nested dicts into DotDicts
        for key, value in self.items():
            if type(value) is dict:
                self[key] = DotDict(value)

    def __setitem__(self, key, item):
        if type(item) is dict:
            item = DotDict(item)
        super().__setitem__(key, item)

    __setattr__ = __setitem__
    __getattr__ = dict.__getitem__
Hemialgia answered 1/8, 2019 at 21:7 Comment(0)
M
3

I ended up trying BOTH the AttrDict and the Bunch libraries and found them to be way to slow for my uses. After a friend and I looked into it, we found that the main method for writing these libraries results in the library aggressively recursing through a nested object and making copies of the dictionary object throughout. With this in mind, we made two key changes. 1) We made attributes lazy-loaded 2) instead of creating copies of a dictionary object, we create copies of a light-weight proxy object. This is the final implementation. The performance increase of using this code is incredible. When using AttrDict or Bunch, these two libraries alone consumed 1/2 and 1/3 respectively of my request time(what!?). This code reduced that time to almost nothing(somewhere in the range of 0.5ms). This of course depends on your needs, but if you are using this functionality quite a bit in your code, definitely go with something simple like this.

class DictProxy(object):
    def __init__(self, obj):
        self.obj = obj

    def __getitem__(self, key):
        return wrap(self.obj[key])

    def __getattr__(self, key):
        try:
            return wrap(getattr(self.obj, key))
        except AttributeError:
            try:
                return self[key]
            except KeyError:
                raise AttributeError(key)

    # you probably also want to proxy important list properties along like
    # items(), iteritems() and __len__

class ListProxy(object):
    def __init__(self, obj):
        self.obj = obj

    def __getitem__(self, key):
        return wrap(self.obj[key])

    # you probably also want to proxy important list properties along like
    # __iter__ and __len__

def wrap(value):
    if isinstance(value, dict):
        return DictProxy(value)
    if isinstance(value, (tuple, list)):
        return ListProxy(value)
    return value

See the original implementation here by https://stackoverflow.com/users/704327/michael-merickel.

The other thing to note, is that this implementation is pretty simple and doesn't implement all of the methods you might need. You'll need to write those as required on the DictProxy or ListProxy objects.

Mefford answered 22/7, 2015 at 16:46 Comment(0)
H
3

This solution is a refinement upon the one offered by epool to address the requirement of the OP to access nested dicts in a consistent manner. The solution by epool did not allow for accessing nested dicts.

class YAMLobj(dict):
    def __init__(self, args):
        super(YAMLobj, self).__init__(args)
        if isinstance(args, dict):
            for k, v in args.iteritems():
                if not isinstance(v, dict):
                    self[k] = v
                else:
                    self.__setattr__(k, YAMLobj(v))


    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(YAMLobj, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(YAMLobj, self).__delitem__(key)
        del self.__dict__[key]

With this class, one can now do something like: A.B.C.D.

Hannus answered 24/11, 2015 at 2:45 Comment(2)
use .items() for python3Gissing
just note : To work on Python 3 I updated .iteritems() to .items()Formalize
C
3
def dict_to_object(dick):
    # https://mcmap.net/q/20740/-how-to-convert-a-nested-python-dict-to-object

    class Struct:
        def __init__(self, **entries):
            self.__dict__.update(entries)

    return Struct(**dick)

If one decides to permanently convert that dict to object this should do. You can create a throwaway object just before accessing.

d = dict_to_object(d)
Crofter answered 1/2, 2016 at 4:52 Comment(1)
def attr(**kwargs): o = lambda: None o.__dict__.update(**kwargs) return oGoatsbeard
S
3

Using namedtuple allows dot access.

It is like a lightweight object which also has the properties of a tuple.

It allows to define properties and access them using the dot operator.

from collections import namedtuple
Data = namedtuple('Data', ['key1', 'key2'])

dataObj = Data(val1, key2=val2) # can instantiate using keyword arguments and positional arguments

Access using dot operator

dataObj.key1 # Gives val1
datObj.key2 # Gives val2

Access using tuple indices

dataObj[0] # Gives val1
dataObj[1] # Gives val2

But remember this is a tuple; not a dict. So the below code will give error

dataObj['key1'] # Gives TypeError: tuple indices must be integers or slices, not str

Refer: namedtuple

Schram answered 1/2, 2021 at 13:29 Comment(0)
C
3

For infinite levels of nesting of dicts, lists, lists of dicts, and dicts of lists.

It also supports pickling

This is an extension of this answer.

class DotDict(dict):
    # https://mcmap.net/q/20631/-how-to-use-a-dot-quot-quot-to-access-members-of-dictionary
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])

    Iterable are assumed to have a constructor taking list as input.
    """

    def __init__(self, *args, **kwargs):
        super(DotDict, self).__init__(*args, **kwargs)

        args_with_kwargs = []
        for arg in args:
            args_with_kwargs.append(arg)
        args_with_kwargs.append(kwargs)
        args = args_with_kwargs

        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.items():
                    self[k] = v
                    if isinstance(v, dict):
                        self[k] = DotDict(v)
                    elif isinstance(v, str) or isinstance(v, bytes):
                        self[k] = v
                    elif isinstance(v, Iterable):
                        klass = type(v)
                        map_value: List[Any] = []
                        for e in v:
                            map_e = DotDict(e) if isinstance(e, dict) else e
                            map_value.append(map_e)
                        self[k] = klass(map_value)



    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(DotDict, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(DotDict, self).__delitem__(key)
        del self.__dict__[key]

    def __getstate__(self):
        return self.__dict__

    def __setstate__(self, d):
        self.__dict__.update(d)


if __name__ == "__main__":
    import pickle
    def test_map():
        d = {
            "a": 1,
            "b": {
                "c": "d",
                "e": 2,
                "f": None
            },
            "g": [],
            "h": [1, "i"],
            "j": [1, "k", {}],
            "l":
                [
                    1,
                    "m",
                    {
                        "n": [3],
                        "o": "p",
                        "q": {
                            "r": "s",
                            "t": ["u", 5, {"v": "w"}, ],
                            "x": ("z", 1)
                        }
                    }
                ],
        }
        map_d = DotDict(d)
        w = map_d.l[2].q.t[2].v
        assert w == "w"

        pickled = pickle.dumps(map_d)
        unpickled = pickle.loads(pickled)
        assert unpickled == map_d

        kwargs_check = DotDict(a=1, b=[dict(c=2, d="3"), 5])
        assert kwargs_check.b[0].d == "3"

        kwargs_and_args_check = DotDict(d, a=1, b=[dict(c=2, d="3"), 5])
        assert kwargs_and_args_check.l[2].q.t[2].v == "w"
        assert kwargs_and_args_check.b[0].d == "3"



    test_map()

Comeuppance answered 11/1, 2022 at 10:15 Comment(4)
With Python 3.10 I get the following error: _pickle.PicklingError: Can't pickle <class '__main__.DotDict'>: attribute lookup DotDict on __main__ failedGawain
@Gawain if you pickle on one version and unpickle on a different python version you will have issues.Comeuppance
I'm using only one Python version though.Gawain
@Gawain Can't be sure what the problem is w/o full code and stack trace, please open a new question.Comeuppance
V
2

It is an old question but I recently found that sklearn has an implemented version dict accessible by key, namely Bunch https://scikit-learn.org/stable/modules/generated/sklearn.utils.Bunch.html#sklearn.utils.Bunch

Vassallo answered 25/5, 2021 at 8:3 Comment(0)
S
2

Simplest solution.

Define a class with only pass statement in it. Create object for this class and use dot notation.

class my_dict:
    pass

person = my_dict()
person.id = 1 # create using dot notation
person.phone = 9999
del person.phone # Remove a property using dot notation

name_data = my_dict()
name_data.first_name = 'Arnold'
name_data.last_name = 'Schwarzenegger'

person.name = name_data
person.name.first_name # dot notation access for nested properties - gives Arnold
Schram answered 26/2, 2022 at 16:21 Comment(1)
Note: this is more of a skeleton than a fully-formed answer to the OP, as it deviates significantly from routinely expected behaviors of a regular python dict(). For example, compare the output of pprint.pprint(person) using this approach, versus what you would have gotten had you used a regular python dict. Admittedly, you could fix that difference by adding a __repr__ method to the my_dict class, but that means you are already no longer using this "Simplest solution" anymore. Moreover, that's just the beginning of what you'd have to change in order to mimic dict().Sniffle
B
1

One simple way to get dot access (but not array access), is to use a plain object in Python. Like this:

class YourObject:
    def __init__(self, *args, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

...and use it like this:

>>> obj = YourObject(key="value")
>>> print(obj.key)
"value"

... to convert it to a dict:

>>> print(obj.__dict__)
{"key": "value"}
Bhutan answered 8/5, 2018 at 6:46 Comment(0)
C
1

The answer of @derek73 is very neat, but it cannot be pickled nor (deep)copied, and it returns None for missing keys. The code below fixes this.

Edit: I did not see the answer above that addresses the exact same point (upvoted). I'm leaving the answer here for reference.

class dotdict(dict):
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)
Chanel answered 8/11, 2019 at 17:28 Comment(1)
or you can set __getattr__ = dict.__getitem__Pastorate
A
1

I just needed to access a dictionary using a dotted path string, so I came up with:

def get_value_from_path(dictionary, parts):
    """ extracts a value from a dictionary using a dotted path string """

    if type(parts) is str:
        parts = parts.split('.')

    if len(parts) > 1:
        return get_value_from_path(dictionary[parts[0]], parts[1:])

    return dictionary[parts[0]]

a = {'a':{'b':'c'}}
print(get_value_from_path(a, 'a.b')) # c
Ameliorate answered 26/3, 2021 at 17:33 Comment(0)
P
1

The implemention used by kaggle_environments is a function called structify.

class Struct(dict):
    def __init__(self, **entries):
        entries = {k: v for k, v in entries.items() if k != "items"}
        dict.__init__(self, entries)
        self.__dict__.update(entries)

    def __setattr__(self, attr, value):
        self.__dict__[attr] = value
        self[attr] = value

# Added benefit of cloning lists and dicts.
def structify(o):
    if isinstance(o, list):
        return [structify(o[i]) for i in range(len(o))]
    elif isinstance(o, dict):
        return Struct(**{k: structify(v) for k, v in o.items()})
    return o

This may be useful for testing AI simulation agents in games like ConnectX

from kaggle_environments import structify

obs  = structify({ 'remainingOverageTime': 60, 'step': 0, 'mark': 1, 'board': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]})
conf = structify({ 'timeout': 2, 'actTimeout': 2, 'agentTimeout': 60, 'episodeSteps': 1000, 'runTimeout': 1200, 'columns': 7, 'rows': 6, 'inarow': 4, '__raw_path__': '/kaggle_simulations/agent/main.py' })

def agent(obs, conf):
  action = obs.step % conf.columns
  return action
Pabulum answered 11/7, 2022 at 1:36 Comment(1)
this is a short and nice implementation, and out-of-the-box provides easy access to dict keys via attribute (dot) access. however, in case performance is a concern, note that a custom implementation (like dotwiz, see my post above) is ~2x faster for constructing a dot-access object.Sennacherib
G
0

Not a direct answer to the OP's question, but inspired by and perhaps useful for some.. I've created an object-based solution using the internal __dict__ (In no way optimized code)

payload = {
    "name": "John",
    "location": {
        "lat": 53.12312312,
        "long": 43.21345112
    },
    "numbers": [
        {
            "role": "home",
            "number": "070-12345678"
        },
        {
            "role": "office",
            "number": "070-12345679"
        }
    ]
}


class Map(object):
    """
    Dot style access to object members, access raw values
    with an underscore e.g.

    class Foo(Map):
        def foo(self):
            return self.get('foo') + 'bar'

    obj = Foo(**{'foo': 'foo'})

    obj.foo => 'foobar'
    obj._foo => 'foo'

    """

    def __init__(self, *args, **kwargs):
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self.__dict__[k] = v
                    self.__dict__['_' + k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                self.__dict__[k] = v
                self.__dict__['_' + k] = v

    def __getattribute__(self, attr):
        if hasattr(self, 'get_' + attr):
            return object.__getattribute__(self, 'get_' + attr)()
        else:
            return object.__getattribute__(self, attr)

    def get(self, key):
        try:
            return self.__dict__.get('get_' + key)()
        except (AttributeError, TypeError):
            return self.__dict__.get(key)

    def __repr__(self):
        return u"<{name} object>".format(
            name=self.__class__.__name__
        )


class Number(Map):
    def get_role(self):
        return self.get('role')

    def get_number(self):
        return self.get('number')


class Location(Map):
    def get_latitude(self):
        return self.get('lat') + 1

    def get_longitude(self):
        return self.get('long') + 1


class Item(Map):
    def get_name(self):
        return self.get('name') + " Doe"

    def get_location(self):
        return Location(**self.get('location'))

    def get_numbers(self):
        return [Number(**n) for n in self.get('numbers')]


# Tests

obj = Item({'foo': 'bar'}, **payload)

assert type(obj) == Item
assert obj._name == "John"
assert obj.name == "John Doe"
assert type(obj.location) == Location
assert obj.location._lat == 53.12312312
assert obj.location._long == 43.21345112
assert obj.location.latitude == 54.12312312
assert obj.location.longitude == 44.21345112

for n in obj.numbers:
    assert type(n) == Number
    if n.role == 'home':
        assert n.number == "070-12345678"
    if n.role == 'office':
        assert n.number == "070-12345679"
Genitalia answered 31/1, 2017 at 7:48 Comment(0)
P
0

Here's my version of @derek73 answer. I use dict.__getitem__ as __getattr__ so it still throws KeyError, and im renaming dict public methods with "" prefix (surrounded with "" leads to special methods name conflict, like __get__ which would be treated as a descriptor method). Anyway you can't get completely clear namespace for keys as attributes due to crucial dict base methods, so the solution isn't perfect but you can have keys - attributes like get, pop, items etc.

class DotDictMeta(type):                                                          
    def __new__(                                                                  
        cls,                                                                      
        name,                                                                     
        bases,                                                                    
        attrs,                                         
        rename_method=lambda n: f'__{n}__',                            
        **custom_methods,                                                         
    ):                                                                            
        d = dict                                                                  
        attrs.update(                                                             
            cls.get_hidden_or_renamed_methods(rename_method),           
            __getattr__=d.__getitem__,                                            
            __setattr__=d.__setitem__,                                            
            __delattr__=d.__delitem__,                                            
            **custom_methods,                                                     
        )                                                                         
        return super().__new__(cls, name, bases, attrs)                           
                                                                                  
    def __init__(self, name, bases, attrs, **_):                                  
        super().__init__(name, bases, attrs)                                      
                                                                                  
    @property                                                                     
    def attribute_error(self):                                                    
        raise AttributeError                                                      
                                                                                  
    @classmethod                                                                  
    def get_hidden_or_renamed_methods(cls, rename_method=None):                  
        public_methods = tuple(                                                   
            i for i in dict.__dict__.items() if not i[0].startswith('__')         
        )                                                                         
        error = cls.attribute_error                                               
        hidden_methods = ((k, error) for k, v in public_methods)                  
        yield from hidden_methods                                                 
        if rename_method:                                                       
            renamed_methods = ((rename_method(k), v) for k, v in public_methods) 
            yield from renamed_methods                                             
                                                                                  
                                                                                  
class DotDict(dict, metaclass=DotDictMeta):                                       
    pass  

                                                                    
                                                                              

You can remove dict methods from DotDict namespace and keep using dict class methods, its useful also when you want to operate on other dict instances and want to use the same methods without extra check whether its DotDict or not, eg.

dct = dict(a=1)
dot_dct = DotDict(b=2)
foo = {c: i for i, c in enumerate('xyz')}
for d in (dct, dot_dct):
    # you would have to use dct.update and dot_dct.__update methods
    dict.update(d, foo)
    
assert dict.get(dot, 'foo', 0) is 0
Pastorate answered 3/8, 2021 at 22:29 Comment(0)
C
0

I just dug this up from a project I was working on a long time ago. It could probably be optimized a bit, but here it goes.

class DotNotation(dict):
    
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

    def __init__(self, data):
        if isinstance(data, str):
            data = json.loads(data)
    
        for name, value in data.items():
            setattr(self, name, self._wrap(value))

    def __getattr__(self, attr):
        def _traverse(obj, attr):
            if self._is_indexable(obj):
                try:
                    return obj[int(attr)]
                except:
                    return None
            elif isinstance(obj, dict):
                return obj.get(attr, None)
            else:
                return attr
        if '.' in attr:
            return reduce(_traverse, attr.split('.'), self)
        return self.get(attr, None)

    def _wrap(self, value):
        if self._is_indexable(value):
            # (!) recursive (!)
            return type(value)([self._wrap(v) for v in value])
        elif isinstance(value, dict):
            return DotNotation(value)
        else:
            return value
    
    @staticmethod
    def _is_indexable(obj):
        return isinstance(obj, (tuple, list, set, frozenset))


if __name__ == "__main__":
    test_dict = {
        "dimensions": {
            "length": "112",
            "width": "103",
            "height": "42"
        },
        "meta_data": [
            {
                "id": 11089769,
                "key": "imported_gallery_files",
                "value": [
                    "https://example.com/wp-content/uploads/2019/09/unnamed-3.jpg",
                    "https://example.com/wp-content/uploads/2019/09/unnamed-2.jpg",
                    "https://example.com/wp-content/uploads/2019/09/unnamed-4.jpg"
                ]
            }
        ]
    }
    dotted_dict = DotNotation(test_dict)
    print(dotted_dict.dimensions.length) # => '112'
    print(getattr(dotted_dict, 'dimensions.length')) # => '112'
    print(dotted_dict.meta_data[0].key) # => 'imported_gallery_files'
    print(getattr(dotted_dict, 'meta_data.0.key')) # => 'imported_gallery_files'
    print(dotted_dict.meta_data[0].value) # => ['link1','link2','link2']
    print(getattr(dotted_dict, 'meta_data.0.value')) # => ['link1','link2','link3']
    print(dotted_dict.meta_data[0].value[2]) # => 'link3'
    print(getattr(dotted_dict, 'meta_data.0.value.2')) # => 'link3'
Crosslet answered 5/9, 2021 at 21:17 Comment(0)
O
0

My 2 cents: for my own purposes I developed minydra, a simple command-line parser which includes a custom class MinyDict (inspired by addict):


In [1]: from minydra import MinyDict

In [2]: args = MinyDict({"foo": "bar", "yes.no.maybe": "idontknow"}).pretty_print(); args
╭──────────────────────────────╮
│ foo          : bar           │
│ yes.no.maybe : idontknow     │
╰──────────────────────────────╯
Out[2]: {'foo': 'bar', 'yes.no.maybe': 'idontknow'}

In [3]: args.resolve().pretty_print(); args
╭──────────────────────────╮
│ foo : bar                │
│ yes                      │
│ │no                      │
│ │ │maybe : idontknow     │
╰──────────────────────────╯
Out[3]: {'foo': 'bar', 'yes': {'no': {'maybe': 'idontknow'}}}

In [4]: args.yes.no.maybe
Out[4]: "idontknow"

In [5]: "foo" in args
Out[5]: True

In [6]: "rick" in args
Out[6]: False

In [7]: args.morty is None
Out[7]: True

In [8]: args.items()
Out[8]: dict_items([('foo', 'bar'), ('yes', {'no': {'maybe': 'idontknow'}})])

It goes further than addict by adding dumping/loading methods to/from json yaml and pickle and also has a strict mode in MinyDict.update() to prevent the creation of new keys (this is useful to prevent typos in the command-line)

Omniumgatherum answered 31/10, 2021 at 17:38 Comment(0)
C
0

One could use dotsi, for full list, dict and recursive support, with some extension methods

pip install dotsi

and

>>> import dotsi
>>> 
>>> d = dotsi.Dict({"foo": {"bar": "baz"}})     # Basic
>>> d.foo.bar
'baz'
>>> d.users = [{"id": 0, "name": "Alice"}]   # List
>>> d.users[0].name
'Alice'
>>> d.users.append({"id": 1, "name": "Becca"}); # Append
>>> d.users[1].name
'Becca'
>>> d.users += [{"id": 2, "name": "Cathy"}];    # `+=`
>>> d.users[2].name
'Cathy'
>>> d.update({"tasks": [{"id": "a", "text": "Task A"}]});
>>> d.tasks[0].text
'Task A'
>>> d.tasks[0].tags = ["red", "white", "blue"];
>>> d.tasks[0].tags[2];
'blue'
>>> d.tasks[0].pop("tags")                      # `.pop()`
['red', 'white', 'blue']
>>> 
>>> import pprint
>>> pprint.pprint(d)
{'foo': {'bar': 'baz'},
 'tasks': [{'id': 'a', 'text': 'Task A'}],
 'users': [{'id': 0, 'name': 'Alice'},
           {'id': 1, 'name': 'Becca'},
           {'id': 2, 'name': 'Cathy'}]}
>>> 
>>> type(d.users)       # dotsi.Dict (AKA dotsi.DotsiDict)
<class 'dotsi.DotsiList'>
>>> type(d.users[0])    # dotsi.List (AKA dotsi.DotsiList)
<class 'dotsi.DotsiDict'> 
>>> 
Comeuppance answered 7/2, 2022 at 13:10 Comment(2)
the performance is actually good, so I was surprised. however, the code is terribly written and probably could do with a code linting tool or similar. this lib does outperform most others on here, like box otherwise. In summary, just the code quality needs work imo.Sennacherib
@rv.kvetch code quality of what? the library internals?Comeuppance
G
0

If you're already using pandas, you can construct a pandas Series or DataFrame from which you would be able to access items via the dot syntax:

1-level dictionary:

import pandas as pd

my_dictionary = pd.Series({
  'key1': 'value1',
  'key2': 'value2'
})

print(my_dictionary.key1)
# Output: value1

2-level dictionary:

import pandas as pd

my_dictionary = pd.DataFrame({
  'key1': {
    'inner_key1': 'value1'
  },
  'key2': {
    'inner_key2': 'value2'
  }
})

print(my_dictionary.key1.inner_key1)
# Output: value1

Be aware that this probably works better with a normalised data structure (where each dictionary entry has the same structure). In the second example above, the resulting DataFrame is:

              key1    key2
inner_key1  value1     NaN
inner_key2     NaN  value2
Gawain answered 27/8, 2022 at 6:33 Comment(0)
J
0

I've created a solution that should be able to handle (almost) every list/dict/tuple/object's attribute accessing using dot notation, or regular (first level) access:

def get(obj, key: str | int, default=None):
    """
    Try to get index, key or a property
    for a given list, dict or object,
    returns default on error
    
    Args:
        - obj (mixed): dict, list, object
        - key (str):  either any key or dot accessor like: foo.bar.0
    Returns:
        - mixed | None
    """

    key = str(key)
    keys = [key]
    if '.' in key:
        keys = key.split('.')

    val = obj
    for k in keys:
        if not k:
            continue

        try:
            if isinstance(val, list):
                val = val[k]
            elif isinstance(val, dict):
                val = val.get(k, default)
            else:
                val = getattr(val, k, default)
        except (IndexError, KeyError, AttributeError, TypeError, ValueError) as e:
            if 'list indices must be integers or slices, not str' in str(e):
                try:
                    k = int(k)
                    val = val[k]
                except (ValueError, KeyError, IndexError):
                    val = default
            else:
                return default

    return val

https://gist.github.com/milosb793/80610e15f5c547e29558e466c65cbe22

Jabiru answered 20/9, 2023 at 9:58 Comment(0)
F
-3

A solution kind of delicate

class DotDict(dict):

    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

    def __getattr__(self, key):

        def typer(candidate):
            if isinstance(candidate, dict):
                return DotDict(candidate)

            if isinstance(candidate, str):  # iterable but no need to iter
                return candidate

            try:  # other iterable are processed as list
                return [typer(item) for item in candidate]
            except TypeError:
                return candidate

            return candidate

        return typer(dict.get(self, key))
Furst answered 20/3, 2019 at 9:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.