How to use dot notation for dict in python?
Asked Answered
R

17

162

I'm very new to python and I wish I could do . notation to access values of a dict.

Lets say I have test like this:

>>> test = dict()
>>> test['name'] = 'value'
>>> print(test['name'])
value

But I wish I could do test.name to get value. Infact I did it by overriding the __getattr__ method in my class like this:

class JuspayObject:

    def __init__(self,response):
        self.__dict__['_response'] = response

    def __getattr__(self,key): 
        try:
            return self._response[key]
        except KeyError,err:
            sys.stderr.write('Sorry no key matches')

and this works! when I do:

test.name // I get value.

But the problem is when I just print test alone I get the error as:

'Sorry no key matches'

Why is this happening?

Riha answered 29/4, 2013 at 12:57 Comment(6)
You need to call super class getattr when you ask for attribute that's not in your dict.Vivianne
@DavidHeffernan Does an old-style class like the OP's example even have a superclass?Sharpie
@Sharpie No idea. If not, use a new style class. Who still uses old style classes anyway?Vivianne
@DavidHeffernan Well, there's still quite a lot of old-style classes in the standard Python lib, e.g. cgi.py.Sharpie
See also: #2352681Proprietary
See also: 1) use JMESpath on python dictionary 2) use JMESpath on python dictionary 3) use JMESpath on python dictionaryProprietary
E
280

This functionality already exists in the standard libraries, so I recommend you just use their class.

>>> from types import SimpleNamespace
>>> d = {'key1': 'value1', 'key2': 'value2'}
>>> n = SimpleNamespace(**d)
>>> print(n)
namespace(key1='value1', key2='value2')
>>> n.key2
'value2'

Adding, modifying and removing values is achieved with regular attribute access, i.e. you can use statements like n.key = val and del n.key.

To go back to a dict again:

>>> vars(n)
{'key1': 'value1', 'key2': 'value2'}

The keys in your dict should be string identifiers for attribute access to work properly.

Simple namespace was added in Python 3.3. For older versions of the language, argparse.Namespace has similar behaviour.

Eastwardly answered 29/4, 2013 at 13:15 Comment(4)
Fabric also has a nice minimal implementation, which I also posted here.Kerianne
Would be nice if you could recurse this.. i.e. accessing myobj.subdict.subdictOutbrave
@Outbrave There's a 3rd party lib providing that functionality, checkout out python-box.Eastwardly
Beware, while 'key' in d works when d is a dict, that doesn't work when d is a SimpleNamespace 😭Drawstring
E
60

I assume that you are comfortable in Javascript and want to borrow that kind of syntax... I can tell you by personal experience that this is not a great idea.

It sure does look less verbose and neat; but in the long run it is just obscure. Dicts are dicts, and trying to make them behave like objects with attributes will probably lead to (bad) surprises.

If you need to manipulate the fields of an object as if they were a dictionary, you can always resort to use the internal __dict__ attribute when you need it, and then it is explicitly clear what you are doing. Or use getattr(obj, 'key') to have into account the inheritance structure and class attributes too.

But by reading your example it seems that you are trying something different... As the dot operator will already look in the __dict__ attribute without any extra code.

Edlin answered 29/4, 2013 at 13:7 Comment(4)
Example of a bad surprise: if you have a key in the dict which happens to be a python keyword, e.g. the string 'for', then attribute access fails and there's no elegant way to handle this case properly. The whole idea is fundamentally broken from the start.Eastwardly
On the other hand, auto-completion makes for more efficient, less error-prone code. That would work perfectly If the dict structure can be predefined.Borders
Javascript is elegant.Metatherian
@QianChen JavaScript can be elegant, and also can be a mess (like most languages). What is guaranteed to be a mess is trying to use Python as if it was JavaScript (or any two other languages whatsoever). If there's an expected way to do something in a given language, deviating from that is only going to cause pain.Edlin
H
22

In addition to this answer, one can add support for nested dicts as well:

from types import SimpleNamespace

class NestedNamespace(SimpleNamespace):
    def __init__(self, dictionary, **kwargs):
        super().__init__(**kwargs)
        for key, value in dictionary.items():
            if isinstance(value, dict):
                self.__setattr__(key, NestedNamespace(value))
            else:
                self.__setattr__(key, value)

nested_namespace = NestedNamespace({
    'parent': {
        'child': {
            'grandchild': 'value'
        }
    },
    'normal_key': 'normal value',
})


print(nested_namespace.parent.child.grandchild)  # value
print(nested_namespace.normal_key)  # normal value

Note that this does not support dot notation for dicts that are somewhere inside e.g. lists.

Hatchery answered 23/1, 2019 at 17:36 Comment(0)
P
14

Could you use a named tuple?

from collections import namedtuple
Test = namedtuple('Test', 'name foo bar')
my_test = Test('value', 'foo_val', 'bar_val')
print(my_test)
print(my_test.name)

Prickle answered 29/4, 2013 at 13:9 Comment(2)
This is an interesting way to access a data structure via dotted notation, but it doesn't seem particularly compatible with JSON or dict. There are libs that use named tuples under the covers that do provide JSON and dict support. Try github.com/dsc/bunch or github.com/kennknowles/python-jsonpath-rwGurango
for python >= 3.6, consider from typing import NamedTupleTatum
I
7
class convert_to_dot_notation(dict):
    """
    Access dictionary attributes via dot notation
    """

    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__


test = {"name": "value"}
data = convert_to_dot_notation(test)
print(data.name)
Isomorph answered 26/10, 2022 at 21:44 Comment(0)
S
6

__getattr__ is used as a fallback when all other attribute lookup rules have failed. When you try to "print" your object, Python look for a __repr__ method, and since you don't implement it in your class it ends up calling __getattr__ (yes, in Python methods are attributes too). You shouldn't assume which key getattr will be called with, and, most important, __getattr__ must raise an AttributeError if it cannot resolve key.

As a side note: don't use self.__dict__ for ordinary attribute access, just use the plain attribute notation:

class JuspayObject:

    def __init__(self,response):
        # don't use self.__dict__ here
        self._response = response

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

Now if your class has no other responsability (and your Python version is >= 2.6 and you don't need to support older versions), you may just use a namedtuple : http://docs.python.org/2/library/collections.html#collections.namedtuple

Sibert answered 29/4, 2013 at 14:22 Comment(0)
W
5

You can use the built-in method argparse.Namespace():

import argparse

args = argparse.Namespace()
args.name = 'value'

print(args.name)
# 'value'

You can also get the original dict via vars(args).

Wengert answered 26/1, 2021 at 1:39 Comment(0)
S
3

You have to be careful when using __getattr__, because it's used for a lot of builtin Python functionality.

Try something like this...

class JuspayObject:

    def __init__(self,response):
        self.__dict__['_response'] = response

    def __getattr__(self, key):
        # First, try to return from _response
        try:
            return self.__dict__['_response'][key]
        except KeyError:
            pass
        # If that fails, return default behavior so we don't break Python
        try:
            return self.__dict__[key]
        except KeyError:
            raise AttributeError, key

>>> j = JuspayObject({'foo': 'bar'})
>>> j.foo
'bar'
>>> j
<__main__.JuspayObject instance at 0x7fbdd55965f0>
Sharpie answered 29/4, 2013 at 13:15 Comment(2)
__getattr__ is only used as a fallback if no other lookup rule matched, so no need to lookup self.__dict__[key], it's already been done if __getattr__ is called. Just raising AttributeError if the lookup on self._response['key'] failed is enough.Sibert
@brunodesthuilliers Looks like you're right. Strange. Maybe this only used to be necessary back in the days of Python v1.5.Sharpie
B
3

Here is a simple, handy dot notation helper example that is working with nested items:

def dict_get(data:dict, path:str, default = None):
    pathList = re.split(r'\.', path, flags=re.IGNORECASE)
    result = data
    for key in pathList:
        try:
            key = int(key) if key.isnumeric() else key 
            result = result[key]
        except:
            result = default
            break
    
    return result

Usage example:

my_dict = {"test1": "str1", "nested_dict": {"test2": "str2"}, "nested_list": ["str3", {"test4": "str4"}]}
print(dict_get(my_dict, "test1"))
# str1
print(dict_get(my_dict, "nested_dict.test2"))
# str2
print(dict_get(my_dict, "nested_list.1.test4"))
# str4
Ballroom answered 6/7, 2021 at 21:12 Comment(1)
After much searching, this was the best answer i found. under usage example, you should rename the function name to dict_get instead of data_dict_get , thanksLudlow
F
2

With a small addition to this answer you can support lists as well:

class NestedNamespace(SimpleNamespace):
def __init__(self, dictionary, **kwargs):
    super().__init__(**kwargs)
    for key, value in dictionary.items():
        if isinstance(value, dict):
            self.__setattr__(key, NestedNamespace(value))
        elif isinstance(value, list):
            self.__setattr__(key, map(NestedNamespace, value))
        else:
            self.__setattr__(key, value)
Flap answered 5/5, 2021 at 10:18 Comment(1)
Unfortunately, while your coding might be correct, it is not directly related to the question. The question was why printing the object itself returns an error.Crunch
H
1

I use the dotted_dict package:

    >>> from dotted_dict import DottedDict
    >>> test = DottedDict()
    >>> test.name = 'value'
    >>> print(test.name)
    value

Advantages over SimpleNamespace

(See @win's answer.) DottedDict is an actual dict:

>>> isinstance(test, dict)
True

This allows, for example, checking for membership:

>>> 'name' in test
True

whereas for SimpleNamespace you need something much less readable like hasattr(test, 'name').

Don't use DotMap

I found this out the hard way. If you reference a non-member it adds it rather than throwing an error. This can lead to hard to find bugs in code:

>>> from dotmap import DotMap
>>> dm = DotMap()
>>> 'a' in dm
False
>>> x = dm.a
>>> 'a' in dm
True
Hive answered 13/5, 2021 at 19:31 Comment(0)
S
1

2022 answer: I've created the dotwiz package -- this is a fast, tiny library that seems to perform really well in most cases.

>>> from dotwiz import DotWiz
>>> test = DotWiz(hello='world')
>>> test.works = True
>>> test
✫(hello='world', works=True)
>>> test.hello
'world'
>>> assert test.works
Savor answered 23/6, 2022 at 21:39 Comment(0)
L
1

This feature is baked into OmegaConf:

from omegaconf import OmegaConf

your_dict = {"k" : "v", "list" : [1, {"a": "1", "b": "2", 3: "c"}]}
adot_dict = OmegaConf.create(your_dict)

print(adot_dict.k)
print(adot_dict.list)

Installation is:

pip install omegaconf

This lib comes in handy for configurations, which it is actually made for:

from omegaconf import OmegaConf
cfg = OmegaConf.load('config.yml')
print(cfg.data_path)
Lions answered 22/11, 2022 at 10:26 Comment(0)
H
0
#!/usr/bin/env python3


import json
from sklearn.utils import Bunch
from collections.abc import MutableMapping


def dotted(inpt: MutableMapping,
           *args,
           **kwargs
           ) -> Bunch:
    """
    Enables recursive dot notation for ``dict``.
    """

    return json.loads(json.dumps(inpt),
                      object_hook=lambda x:
                      Bunch(**{**Bunch(), **x}))
Husking answered 7/3, 2022 at 21:32 Comment(2)
Please explain your code.Psychosurgery
The goal is to pass a function or class allowing non-recursive "dot notation" to the object_hook parameter of json.loads function. I used sklearn.utils.Bunch here, but types.SimpleNamespace in this example https://mcmap.net/q/25216/-how-to-use-dot-notation-for-dict-in-python would likely work as well.Husking
C
0

You can make hacks adding dot notation to Dicts mostly work, but there are always namespace problems. As in, what does this do?

x = DotDict()
x["values"] = 1989
print(x. values)

I use pydash, which is a Python port of JS's lodash, to do these things a different way when the nesting gets too ugly.

Cuprum answered 22/11, 2022 at 16:8 Comment(0)
I
0

You don't need to import anything with this:

nsdict = lambda d: type ('', (), d) () ;

# license: agpl-3.0

Uses:

# iife e.g. 

n = (
(lambda a: 
(lambda b: 
(lambda c: 
    
    nsdict (dict (a=a, c=staticmethod(c) ) )

) (c = lambda: a+b)
) (b = a + 1)
) (a = 1) ) ;

print(n.a) ; # ~> 1
print(n.c()) ; # ~> 3
# n.b ; # !> object has no attribute 'b'

Tested at rustpython demo.

Intensity answered 20/2, 2024 at 9:34 Comment(0)
L
-1

Add a __repr__() method to the class so that you can customize the text to be shown on

print text

Learn more here: https://web.archive.org/web/20121022015531/http://diveintopython.net/object_oriented_framework/special_class_methods2.html

Lipson answered 29/4, 2013 at 13:0 Comment(1)
While this would technically solve the specific case mentioned in the question, hacking up built-in behaviour to patch other hacks of builtin behaviour is objectively bad code.Hydrangea

© 2022 - 2025 — McMap. All rights reserved.