"""
.. module:: DictionaryUtils
:synopsis: Collection of mixed utilities for Dictionaries
.. moduleauthor:: Marco Melis <marco.melis@unica.it>
"""
from collections.abc import MutableMapping
__all__ = ['load_dict', 'merge_dicts', 'invert_dict',
'LastInDict', 'SubLevelsDict']
[docs]def load_dict(file_path, values_dtype=str, encoding='ascii'):
"""Load dictionary from textfile.
Each file's line should be <key: value>
Parameters
----------
file_path : str
Full path to the file to read.
values_dtype : dtype
Datatype of the values. Default str (string).
encoding : str, optional
Encoding to use for reading the file. Default 'ascii'.
Returns
-------
dictionary : dict
Loaded dictionary with one key for each
line in the input text file.
"""
new_dict = {}
with open(file_path, mode='rt', encoding=encoding) as df:
for key_line in df:
# a line is 'key: value'
key_line_split = key_line.split(':')
try:
# Removing any space from key value before setting
new_dict[key_line_split[0]] = values_dtype(key_line_split[1].strip())
except IndexError:
raise ValueError("line '{:}' is not valid.".format(key_line))
return new_dict
[docs]def merge_dicts(*dicts):
"""Shallow copy and merge any number of input dicts.
Precedence goes to key value pairs in latter dicts.
Parameters
----------
dicts : dict1, dict2, ...
Any sequence of dict objects to merge.
Examples
--------
>>> from secml.utils import merge_dicts
>>> d1 = {'attr1': 100, 'attr2': 200}
>>> d2 = {'attr3': 300, 'attr1': 999} # Redefining `attr1`
>>> merge_dicts(d1, d2) # Value of `attr1` will be set according to `d2` dictionary
{'attr3': 300, 'attr2': 200, 'attr1': 999}
"""
result = {}
for dict_i in dicts:
result.update(dict_i)
return result
[docs]def invert_dict(d):
"""Returns a new dict with keys as values and values as keys.
Parameters
----------
d : dict
Input dictionary. If one value of the dictionary is a list or a tuple,
each element of the sequence will be considered separately.
Returns
-------
dict
The new dictionary with d keys as values and d values as keys.
In the case of duplicated d values, the value of the resulting key
of the new dictionary will be a list with all the corresponding d keys.
Examples
--------
>>> from secml.utils.dict_utils import invert_dict
>>> a = {'k1': 2, 'k2': 2, 'k3': 1}
>>> print(invert_dict(a))
{1: 'k3', 2: ['k1', 'k2']}
>>> a = {'k1': 2, 'k2': [2,3,1], 'k3': 1}
>>> print(invert_dict(a))
{1: ['k2', 'k3'], 2: ['k1', 'k2'], 3: 'k2'}
"""
def tolist(x): return [x] if not isinstance(x, (list, tuple)) else list(x)
new_d = {}
for k in d.items():
for v in tolist(k[1]):
i = k[0]
if v in new_d:
# If the key has already been set create a list for the values
i = tolist(i)
i = tolist(new_d[v]) + i
new_d[v] = i
return new_d
[docs]class LastInDict(MutableMapping):
"""Last In Dictionary.
A standard dictionary that keeps in memory the key of the last set item.
The setting behaviour is queue-like: a single element can be inserted
in the dictionary each time.
The last key can be changes manually calling `LastInDict.lastitem_id = key`.
Examples
--------
>>> from secml.utils import LastInDict
>>> li = LastInDict()
>>> li['key1'] = 123
>>> li['key2'] = 102030
>>> li.lastin_key
'key2'
>>> li.lastin
102030
"""
def __init__(self):
self._data = dict()
self._rw_lastin_key = None
@property
def lastin(self):
return self._data[self.lastin_key]
@property
def lastin_key(self):
return self._rw_lastin_key
@lastin_key.setter
def lastin_key(self, key):
if key not in self._data:
raise KeyError("unknown key '{:}'.".format(key))
self._rw_lastin_key = key
def __setitem__(self, key, value):
self._data[key] = value
self.lastin_key = key
def __getitem__(self, key):
return self._data[key]
def __delitem__(self, key):
self.lastin_key = None if self.lastin_key == key else self.lastin_key
del self._data[key]
def __len__(self):
return len(self._data)
def __iter__(self):
for key in self._data:
yield key
[docs]class SubLevelsDict(MutableMapping):
"""Sub-Levels Dictionary.
A standard dictionary that allows easy access to attributes of
contained objects at infinite deep.
Examples
--------
>>> from secml.utils import SubLevelsDict
>>> class Foo:
... attr2 = 5
>>> li = SubLevelsDict({'attr1': Foo()})
>>> print(type(li['attr1']))
<class 'dict_utils.Foo'>
>>> print(li['attr1.attr2'])
5
>>> li['attr1.attr2'] = 10 # Subattributes can be set in the same way
>>> print(li['attr1.attr2'])
10
"""
def __init__(self, data):
self._data = dict(data)
def __setitem__(self, key, value):
# Support for recursion, e.g. -> attr1.attr2
key = key.split('.')
# Setting a key element works like in dictionaries
if len(key) == 1:
self._data[key[0]] = value
return
# The first element of key is a key of the dictionary
data = self._data[key[0]]
# Now get the desired subattributes recursively,
# until the level before the last is reached
for key_split in key[1:-1]:
data = getattr(data, key_split)
# The last subattribute must be an attribute of the deepest level
if hasattr(data, key[-1]):
setattr(data, key[-1], value)
else:
raise AttributeError("'{:}' not found.".format('.'.join(key)))
def __getitem__(self, key):
# Support for recursion, e.g. -> attr1.attr2
key = key.split('.')
# The first element of key is a key of the dictionary
data = self._data[key[0]]
# Now get the desired subattributes recursively,
# until the last level is reached
for key_split in key[1:]:
data = getattr(data, key_split)
return data
def __delitem__(self, key):
if len(key.split('.')) != 1:
raise ValueError("only first-level attributes can be removed.")
del self._data[key]
def __len__(self):
return len(self._data)
def __contains__(self, key):
# Support for recursion, e.g. -> attr1.attr2
key = key.split('.')
# Check the first element, is a key of the dictionary
if key[0] not in self._data:
return False
# The first element of key is a key of the dictionary
data = self._data[key[0]]
# Now get the desired subattributes recursively
for key_split in key[1:]:
if not hasattr(data, key_split):
return False
data = getattr(data, key_split)
return True
def __iter__(self):
for key in self._data:
yield key
def __repr__(self):
return dict.__repr__(self._data)