Simple Dictionary¶
Simple accretive dictionaries have an interface nearly equivalent to
dict
. Because of their accretive nature, they can be useful as
registries for extensions, handlers, and plugins, such that something that is
registered is guaranteed to remain registered throughout the lifetime of the
registry.
>>> from accretive import Dictionary
Let us illustrate this use case by first defining some handlers to register.
>>> def csv_reader( stream ): pass
...
>>> def env_reader( stream ): pass
...
>>> def hcl_reader( stream ): pass
...
>>> def ini_reader( stream ): pass
...
>>> def json_reader( stream ): pass
...
>>> def toml_reader( stream ): pass
...
>>> def xml_reader( stream ): pass
...
>>> def yaml_reader( stream ): pass
...
Initialization¶
Simple dictionaries can be initialized from zero or more other dictionaries or iterables over key-value pairs and zero or more keyword arguments.
>>> readers = Dictionary( { 'csv': csv_reader }, ( ( 'json', json_reader ), ( 'xml', xml_reader ) ), yaml = yaml_reader )
>>> readers
accretive.dictionaries.Dictionary( {'csv': <function csv_reader at 0x...>, 'json': <function json_reader at 0x...>, 'xml': <function xml_reader at 0x...>, 'yaml': <function yaml_reader at 0x...>} )
Immutability¶
Existing entries cannot be altered.
>>> readers[ 'xml' ] = toml_reader
Traceback (most recent call last):
...
accretive.exceptions.IndelibleEntryError: Cannot update or remove existing entry for 'xml'.
Or removed.
>>> del readers[ 'xml' ]
Traceback (most recent call last):
...
accretive.exceptions.IndelibleEntryError: Cannot update or remove existing entry for 'xml'.
(Seems like XML is here to stay.)
Updates¶
However, new entries can be added individually or in bulk. Bulk entry is via
the update
method.
>>> readers.update( ( ( 'env', env_reader ), ( 'hcl', hcl_reader ) ), { 'ini': ini_reader }, toml = toml_reader )
accretive.dictionaries.Dictionary( {'csv': <function csv_reader at 0x...>, 'json': <function json_reader at 0x...>, 'xml': <function xml_reader at 0x...>, 'yaml': <function yaml_reader at 0x...>, 'env': <function env_reader at 0x...>, 'hcl': <function hcl_reader at 0x...>, 'ini': <function ini_reader at 0x...>, 'toml': <function toml_reader at 0x...>} )
Note
The update
method returns the dictionary itself. This is different than
the behavior of dict
, which returns None
instead. Returning
the dictionary is a more useful behavior, since it allows for call chaining
as a fluent setter.
Copies¶
Copies can be made of simple dictionaries.
>>> dct1 = Dictionary( answer = 42 )
>>> dct2 = dct1.copy( )
Comparison¶
The copies are equivalent to their originals.
>>> dct1 == dct2
True
And to instances of other registered subclasses of
collections.abc.Mapping
which have equivalent data.
>>> dct2 == { 'answer': 42 }
True
Modifying a copy causes it to become non-equivalent, as expected.
>>> dct2[ 'question' ] = 'is reality a quine of itself?'
>>> dct1 == dct2
False
>>> dct2 != { 'answer': 42 }
True
Access of Absent Entries¶
As with dict
, a missing entry will raise a KeyError
.
>>> dct1[ 'question' ]
Traceback (most recent call last):
KeyError: 'question'
And, like dict
, the get
method allows for “soft” accesses which
provide a default value if an entry is missing.
>>> dct1.get( 'question' )
>>> dct1.get( 'question', 'what is the meaning of life?' )
'what is the meaning of life?'
Views¶
The usual methods for producing views on items, keys, and values exist.
>>> tuple( readers.keys( ) )
('csv', 'json', 'xml', 'yaml', 'env', 'hcl', 'ini', 'toml')
>>> tuple( readers.items( ) ) == tuple( zip( readers.keys( ), readers.values( ) ) )
True
Producer Dictionary¶
Producer dictionaries have an interface nearly equivalent to
collections.defaultdict
. The first argument to the initializer for
a producer dictionary must be a callable which can be invoked with no
arguments. This callable is used to create entries that are absent at lookup
time. Any additional arguments beyond the first one are treated the same as for
the simple dictionary. Most of their behaviors are the same as for the simple
dictionary, except as noted below.
>>> from accretive import ProducerDictionary
Initialization¶
A common use case is to automatically initialize a mutable data structure, such
as a list
, and add elements or entries to it by merely referencing
its corresponding key… without checking whether the entry exists or creating
the entry first.
>>> watch_lists = ProducerDictionary( list )
>>> watch_lists
accretive.dictionaries.ProducerDictionary( <class 'list'>, {} )
Production of Absent Entries¶
>>> watch_lists[ 'FBI: Most Wanted' ]
[]
>>> watch_lists
accretive.dictionaries.ProducerDictionary( <class 'list'>, {'FBI: Most Wanted': []} )
>>> watch_lists[ 'Santa Claus: Naughty' ].append( 'Calvin' )
>>> watch_lists
accretive.dictionaries.ProducerDictionary( <class 'list'>, {'FBI: Most Wanted': [], 'Santa Claus: Naughty': ['Calvin']} )
Updates¶
>>> watch_lists.update( { 'US Commerce: Do Not Call': [ 'me' ] }, Tasks = set( ) )
accretive.dictionaries.ProducerDictionary( <class 'list'>, {'FBI: Most Wanted': [], 'Santa Claus: Naughty': ['Calvin'], 'US Commerce: Do Not Call': ['me'], 'Tasks': set()} )
Access of Absent Entries¶
The get
method behaves the same as it does on the simple dictionary. I.e.,
it does not implcitly create new entries in a producer dictionary. This is the
same behavior as collections.defaultdict
.
>>> watch_lists.get( 'TSA: No Fly' )
>>> watch_lists.get( 'TSA: No Fly', 'Richard Reid' )
'Richard Reid'
>>> watch_lists
accretive.dictionaries.ProducerDictionary( <class 'list'>, {'FBI: Most Wanted': [], 'Santa Claus: Naughty': ['Calvin'], 'US Commerce: Do Not Call': ['me'], 'Tasks': set()} )
Copies¶
The copy
method creates a new producer dictionary, which is initialized
with the same producer and data as the dictionary on which the method is
invoked.
>>> ddct1 = ProducerDictionary( lambda: 42, { 'foo': 1, 'bar': 2 }, orb = True )
>>> ddct1
accretive.dictionaries.ProducerDictionary( <function <lambda> at 0x...>, {'foo': 1, 'bar': 2, 'orb': True} )
>>> ddct2 = ddct1.copy( )
>>> ddct2
accretive.dictionaries.ProducerDictionary( <function <lambda> at 0x...>, {'foo': 1, 'bar': 2, 'orb': True} )
Comparison¶
Equality comparisons may be made against any registered subclass of
collections.abc.Mapping
. Note that the producer is excluded from
the equality comparison; only data is compared; this is the same behavior as
collections.defaultdict
.
>>> ddct2 == { 'foo': 1, 'bar': 2, 'orb': True }
True