AttrDict is a 2.6, 2.7, 3-compatible dictionary that allows its elements to be accessed both as keys and as attributes:
> from attrdict import AttrDict > a = AttrDict({'foo': 'bar'}) > a.foo 'bar' > a['foo'] 'bar'
With this, you can easily create convenient, heirarchical settings objects.
with open('settings.yaml', 'r') as fileobj: settings = AttrDict(yaml.safe_load(fileobj)) cursor = connect(**settings.db.credentials).cursor() cursor.execute("SELECT column FROM table");
AttrDict is in PyPI, so it can be installed directly using:
$ pip install attrdict
Or from Github:
$ git clone https://github.com/bcj/AttrDict $ cd AttrDict $ python setup.py install
Documentation is available at https://github.com/bcj/AttrDict
An empty AttrDict can be created with:
a = AttrDict()
Or, you can pass an existing dict
(or other type of Mapping
object):
a = AttrDict({'foo': 'bar'})
NOTE: Unlike dict
, AttrDict will not clone on creation. AttrDict's
internal dictionary will be the same instance as the dict passed in.
AttrDict can be used exactly like a normal dict:
> a = AttrDict() > a['foo'] = 'bar' > a['foo'] 'bar' > '{foo}'.format(**a) 'bar' > del a['foo'] > a.get('foo', 'default') 'default'
AttrDict can also have it's keys manipulated as attributes to the object:
> a = AttrDict() > a.foo = 'bar' > a.foo 'bar' > del a.foo
Both methods operate on the same underlying object, so operations are interchangeable. The only difference between the two methods is that where dict-style access would return a dict, attribute-style access will return an AttrDict. This allows recursive attribute-style access:
> a = AttrDict({'foo': {'bar': 'baz'}}) > a.foo.bar 'baz' > a['foo'].bar AttributeError: 'dict' object has no attribute 'bar'
There are some valid keys that cannot be accessed as attributes. To be accessed as an attribute, a key must:
- be a string
- start with an alphabetic character
- be comprised solely of alphanumeric characters and underscores
- not map to an existing attribute name (e.g., get, items)
To access these attributes while retaining an AttrDict wrapper (or to dynamically access any key as an attribute):
> a = AttrDict({'_foo': {'bar': 'baz'}}) > a('_foo').bar 'baz'
AttrDicts can be merged with eachother or other dict objects using the
+
operator. For conflicting keys, the right dict's value will be
preferred, but in the case of two dictionary values, they will be
recursively merged:
> a = {'foo': 'bar', 'alpha': {'beta': 'a', 'a': 'a'}} > b = {'lorem': 'ipsum', 'alpha': {'bravo': 'b', 'a': 'b'}} > AttrDict(a) + b {'foo': 'bar', 'lorem': 'ipsum', 'alpha': {'beta': 'a', 'bravo': 'b', 'a': 'b'}}
NOTE: AttrDict's add is not commutative, a + b != b + a
:
> a = {'foo': 'bar', 'alpha': {'beta': 'b', 'a': 0}} > b = {'lorem': 'ipsum', 'alpha': {'bravo': 'b', 'a': 1}} > b + AttrDict(a) {'foo': 'bar', 'lorem': 'ipsum', 'alpha': {'beta': 'a', 'bravo': 'b', 'a': }}
By default, items in non-string Sequences (e.g. lists, tuples) will be converted to AttrDicts:
> adict = AttrDict({'list': [{'value': 1}, 'value': 2]}) > for element in adict.list: > element.value 1 2
This will not occur if you access the AttrDict as a dictionary:
> adict = AttrDict({'list': [{'value': 1}, 'value': 2]}) > for element in adict['list']: > isinstance(element, AttrDict) False False
To disable this behavior globally, pass the attribute recursive=False
to
the constructor:
> adict = AttrDict({'list': [{'value': 1}, 'value': 2]}, recursive=False) > for element in adict['list']: > isinstance(element, AttrDict) False False
When merging an AttrDict with another mapping, this behavior will be disabled
if at least one of the merged items is an AttrDict that has set recursive
to False
.
AttrDict supports defaultdict-style automatic creation of attributes:
> adict = AttrDict(default_factory=list) > adict.foo []
Furthermore, if pass_key=True
, then the key will be passed to the function
used when creating the value:
> adict = AttrDict(default_factory=lambda value: value.upper(), pass_key=True) > adict.foo 'FOO'
A common usage for AttrDict is to use it in combination with settings files to create hierarchical settings. attrdict comes with a load function to make this easier:
from attrdict import load settings = load('settings.json')
By default, load
uses json.load
to load the settings file, but this can
be overrided by passing load_function=YOUR_LOAD_FUNCTION
.
load
supports loading from multiple files at once. This allows for
overriding of default settings, e.g.:
from attrdict import load from yaml import safe_load # config.yaml = # emergency: # email: everyone@example.com # message: Something went wrong # # user.yaml = # emergency: # email: user@example.com settings = load('config.yaml', 'user.yaml', load_function=safe_load) assert settings.email == 'user@example.com' assert settings.message == 'Something went wrong'
AttrDict is released under a MIT license.