Skip to main content
https://travis-ci.org/eukaryote/uberdict.svg?branch=master

uberdict.udict is a Python dict class that supports attribute-style access and hierarchical keys:

my_udict.result.status.code  # is equivalent to:
my_udict['result']['status']['code']

my_udict.get('result.status.code', default)  # is equivalent to:
my_udict.get('result', {}).get('status', {}).get('code', default)

Tested under py27, py32, py33, py34, py35, py36, py37, and pypy (Python2 and Python 3.5 pypy versions).

Key Features

Easy Conversion from/to plain dict

The __init__ method signature matches that of the stdlib’s dict, so it can be used as a drop-in replacement for dict. If you want to create a udict from a plain dict as a deep copy that converts every plain dict value at any level to an equivalent udict, use the udict.fromdict class method (and for the reverse direction, use the todict instance method of a udict instance):

d = {
    'result': {
        'status': {
            'code': 200,
            'reason': 'OK'
        }
    }
}

# shallow udict copy, like plain `dict` (the `result` value is the original `dict`)
ud = udict(d)

# deep udict copy that recursively converts `dict` to `udict`:
ud = udict.fromdict(d)

# convert back to plain `dict` (recursively)
d = ud.todict()

Attribute-Style Access

The values in a udict may be accessed as if they were attributes on the udict, like normal Python objects:

d = {
    'result': {
        'status': {
            'code': 200,
            'reason': 'OK'
        }
    }
}
ud = udict.fromdict(d)

assert ud.result.status.code == ud['result']['status']['code']

# setting an attribute on the `udict` instance works like a normal dict insertion
ud.message = udict(lang='en', body='Hello, World!')

assert 'message' in ud
assert ud['message'] == ud.message

The standard Python attr methods (hasattr, getattr, setattr, and delattr) work as expected.

# hasattr/getattr/setattr/delattr work as expected
d = udict()
assert not hasattr(d, 'foo')
d.foo = 'foo'
d['bar'] = 'bar'
assert hasattr(d, 'foo')
assert hasattr(d, 'bar')
setattr(d, 'baz', 'bazbaz')
assert 'baz' in d
assert d['baz'] == 'bazbaz'
delattr(d, 'baz')
assert 'baz' not in d
del d['foo']  # works too
assert 'foo' not in d
assert not hasattr(d, 'foo')

Note: getattr and related functions don’t interpret a . in keys in any special way, so you can always insert a key containing a . using setattr, and can retrieve the value for a key containing a . by using getattr.

d = {
    'a': {
        'b': 'a->b'
    },
    'a.b': 'a.b'
}
ud = udict.fromdict(d)
setattr(ud, 'a.b', None)  # doesn't touch 'a'
assert ud['a.b'] is None
assert ud.a == d['a']
assert ud.a.b == 'a->b'

Dict-Style Access and Hierarchical Keys

Because a udict is a dict, you can of course access it like a dict:

ud = udict({'foo': 1})
assert 'foo' in ud
ud['foo'] = 2
ud['foo'] += 1
assert ud.get('bar', 42) == 42
del ud['foo']

When a udict instance contains nested udict instances, you can do the normal dict operations with dotted keys that traverse multiple levels of the hierarchical structure:

ud = udict.fromdict({
    'result': {
        'status': {
            'code': 200,
            'reason': 'OK'
        }
    }
})

assert ud['result.status.reason'] == 'OK'

# ud['result.status.reason'] would raise a `KeyError` if the `result` had
# no `status` or the `status` weren't a `dict`.
# use `get` if you're unsure of existence:
assert ud.get('result.foo.bar') is None
assert ud.get('result.foo.bar', 42) == 42

# dotted keys work as expected for other dict-style operations too:
ud['result.status.code'] = 400
assert 'result.status' in ud and 'result.status.reason' in ud
del ud['result.status.code']

dict-compatible

Since a udict is a dict, it behaves like a dict even when used with brittle code that requires a dict instance rather than something that “quacks” like a dict. For example, the stdlib’s pretty printing module, pprint, generates a pretty, indented representation of a udict that is identical to the one it generates for a plain dict, but pprint doesn’t use the dict-style representation for non-dicts even if they support all the dict methods and register themselves as a collections.Mapping.

The __init__ method signature matches that of the stdlib’s dict, so it can be used as a drop-in replacement for dict with no code-changes needed apart from using udict instead of dict (assuming a suitable import).

The str and repr are identical as for a plain dict also, and a udict is == to an “equivalent” dict

Notes

Avoiding Ambiguity of Dotted Keys

Consider the following udict:

ud = udict.fromdict({
    'a': {
        'b': 'a->b'
    },
    'a.b': 'a.b'
})

When doing ud[‘a.b’], you might reasonably expect that to evaluate to ‘a.b’, because there is a top-level ‘a.b’ key. But it would also be reasonable to expect ud[‘a.b’] to evaluate to ‘a->b’, since a dotted key is interpreted as a key that traverses a path from the base udict through a sequence of one more child dict values, as described above.

In order to avoid such ambiguities, dict-style access like ud[‘a.b’] or ud.get(‘a.b’) is always interpreted as if it were ud[‘a’][‘b’] or ud.get(‘a’, {}).get(‘b’), respectively. That means you could never access the top-level ‘a.b’ in the udict above using dict-style access. You’ll either get the value of a nested udict, get a KeyError (or default value in case of udict.get), or get a TypeError in some cases (following normal Python dict behavior). To access the top-level ‘a.b’ mapping, use getattr(ud, ‘a.b’) instead. The attribute-style accessors (hasattr, getattr, setattr, and delattr) always interpret a key literally, with no special treatment of keys that contain dots.

Thus, the simple rule to remember is:

dict-style access with a dotted key is *always* interpreted hierarchically,
and attribute-style access is *always* interpreted non-hierarchically.

Reasoning about udict Operations

The following table shows how accessing a value on a udict corresponds to one or more operations on a plain dict that yield the same result.

udict operation

dict operation(s)

ud[‘a’]

d[‘a’]

ud.get(‘a’)

d.get(‘a’)

ud.get(‘a’, 42)

d.get(‘a’, 42)

ud.a

d[‘a’]

getattr(ud, ‘a’)

d[‘a’]

getattr(ud, ‘a’, 42)

d.get(‘a’, 42)

ud[‘a.b’]

d[‘a’][‘b’]

ud.get(‘a.b’)

d.get(‘a’, {}).get(‘b’)

ud.get(‘a.b’, 42)

d.get(‘a’, {}).get(‘b’, 42)

getattr(ud, ‘a.b’)

d[‘a.b’]

getattr(ud, ‘a.b’, 42)

d.get(‘a.b’, 42)

ud.a.b

d[‘a’][‘b’]

The only significant difference between operations on the left-side and those on the right-side above is when an exception is raised due to there being no suitable mapping (and no default as there might be with get and getattr). In such cases, attribute-style access on a udict yields an AttributeError (matching standard Python behavior for attribute access), whereas the equivalent operation on a dict would yield a KeyError.

Changes

Version 0.4.3 (2017-07-23)

  • doc changes to get description formatted correctly on both github and pypi

Version 0.4.0 (2017-07-23)

  • support python 2.7 and 3.4+, as well as pypy (pypy2 and dev 3.5 pypy)

  • 100% test coverage

  • making available on pypi

Version 0.3.0 (2014-08-09)

  • added support for dir method to improve interactive use (exposes stored keys as well as the normal instance and class attributes that would be expected)

  • updates to ensure that __missing__ is only used from __getitem__, and never from methods like get or by inadvertently using __getitem__ from another method

  • more tests

Version 0.2.0 (2014-07-27)

  • main class is now ‘uberdict.udict’ (was ‘uberdict.UberDict’)

  • changes to how dotted keys are handled (dots have no special meaning for ‘getattr’, ‘setattr’, ‘hasattr’, ‘delattr’ but do for ‘get’ and ‘__getitem__’ and friends)

  • improved README docs and examples

  • more tests

Metadata

Release files for uberdict 0.4.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for uberdict 0.4.3
File Interpreter ABI Platform
uberdict-0.4.3-py2.py3-none-any.whl Python 2, Python 3 none any Details

Release files / uberdict-0.4.3-py2.py3-none-any.whl

Download URL uberdict-0.4.3-py2.py3-none-any.whl
Size 12.0 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
ffaa3e39e41f0bcc8c97302ba858d79f175fb02510b07085226f2982f13eae94
BLAKE2b-256 checksum
How to use checksums
6ba0b8911b1d3e7f5705a8c71fb416def18a920770377c1708a31caf87f64efe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Release history Release notifications | RSS feed

This release

0.4.3 This release

1 release file

0.4.2

1 release file

0.4.1

1 release file

0.4.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page