"""
For customized dict.
"""
from collections.abc import MutableMapping
from collections import namedtuple
# ==================================================
[docs]
class Dict(MutableMapping):
# ==================================================
def __init__(self, name_field, *args, **kwargs):
"""
Named tuple dict.
Args:
name_field (namedtuple): Dict name, field.
"""
self._key_type = name_field
if len(args) == 1 and type(args[0]) == dict:
self._data = {self._key_type(*tuple(k)): v for k, v in args[0].items()}
else:
self._data = dict(*args, **kwargs)
# ==================================================
def __getitem__(self, key):
key = tuple(key) if isinstance(key, (list, tuple)) else (key,) # for single value.
return self._data[key]
# ==================================================
def __setitem__(self, key, value):
key = tuple(key) if isinstance(key, (list, tuple)) else (key,) # for single value.
self._data[self._key_type(*key)] = value
# ==================================================
def __delitem__(self, key):
key = tuple(key) if isinstance(key, (list, tuple)) else (key,) # for single value.
del self._data[key]
# ==================================================
def __iter__(self):
return iter(self._data)
# ==================================================
def __len__(self):
return len(self._data)
# ==================================================
[docs]
def get(self, key, default=None):
return self._data.get(key, default)
# ==================================================
[docs]
def named_keys(self):
"""
Keys as named tuple.
Returns:
- (dict_keys) -- named tuple keys.
"""
return self._data.keys()
# ==================================================
[docs]
def keys(self):
if len(self.field) == 1:
return dict.fromkeys(k[0] for k in self._data.keys()).keys()
else:
return dict.fromkeys(tuple(k) for k in self._data.keys()).keys()
# ==================================================
[docs]
def values(self):
return self._data.values()
# ==================================================
[docs]
def named_items(self):
"""
Items with named tuple keys.
Returns:
- (dict_items) -- items with named tuple keys.
"""
return self._data.items()
# ==================================================
[docs]
def items(self):
return dict(zip(self.keys(), self.values())).items()
# ==================================================
@property
def name(self):
"""
Key field name.
Returns:
- (str) -- key field name.
"""
return self._key_type.__name__
# ==================================================
@property
def field(self):
"""
Field names.
Returns:
- (list) -- field names.
"""
return self._key_type._fields
# ==================================================
@property
def key_type(self):
"""
Key type.
Returns:
- (namedtuple) -- key type.
"""
return self._key_type
# ==================================================
[docs]
@staticmethod
def select_key(keys, **conditions):
"""
Select dict by conditions.
Args:
keys (dict_keys): named keys.
conditions (dict): key field name and value(s) to match.
- single value: full match.
- list/tuple: any of given values.
Returns:
- (list) -- list of keys.
"""
return [
key
for key in keys
if all(
getattr(key, attr) in value if isinstance(value, (list, tuple)) else getattr(key, attr) == value
for attr, value in conditions.items()
)
]
# ==================================================
[docs]
def select(self, **conditions):
"""
Select dict by conditions.
Args:
**conditions: key field name and value(s) to match.
- single value: full match.
- list/tuple: any of given values.
Returns:
- (Dict) -- selected Dict.
"""
selected = Dict.select_key(self._data.keys(), **conditions)
return Dict(self._key_type, {key: self._data[key] for key in selected})
# ==================================================
[docs]
@staticmethod
def sort_key(keys, key_type=None, *attributes):
"""
Sort key.
Args:
keys (dict_keys): named_keys.
key_type (namedtuple, optional): key type.
Returns:
- (list) -- sorted keys.
Note:
- attributes: tuple for sort property.
- ("key_name", custum order list, ascending?)
- ("key_name", custum order list)
- ("key_name", ascending?)
- "key_name"
"""
if not attributes:
if key_type is None:
raise ValueError("key_type is required when attributes is empty")
attributes = key_type._fields
def sort_key(key):
values = []
for attr in attributes:
if isinstance(attr, tuple):
if len(attr) == 3:
attr_name, order, asc = attr
elif len(attr) == 2:
if isinstance(attr[1], list):
attr_name, order = attr
asc = True
else:
attr_name, asc = attr
order = None
else:
raise ValueError("Invalid attribute tuple format.")
else:
attr_name, order, asc = attr, None, True
value = getattr(key, attr_name)
if order is not None:
idx = order.index(value) if value in order else float("inf")
values.append(idx if asc else -idx)
else:
values.append(value if asc else -value)
return tuple(values)
return sorted(keys, key=sort_key)
# ==================================================
[docs]
def sort(self, *attributes):
"""
Sort dict.
Returns:
- (Dict) -- sorted Dict.
Note:
- attributes: tuple for sort property.
- ("key_name", custum order list, ascending?)
- ("key_name", custum order list)
- ("key_name", ascending?)
- "key_name"
"""
sorted_keys = self.sort_key(self._data.keys(), self._key_type, *attributes)
return Dict(self._key_type, {key: self[key] for key in sorted_keys})
# ==================================================
def __repr__(self):
items = (f"{tuple(key)}: {value}" for key, value in self._data.items())
return "{" + ", ".join(items) + "}"
# ==================================================
def __getstate__(self):
return {
"name": self._key_type.__name__,
"field": self._key_type._fields,
"data": {tuple(k): v for k, v in self._data.items()},
}
# ==================================================
def __setstate__(self, state):
self._key_type = namedtuple(state["name"], state["field"])
self._data = {self._key_type(*k): v for k, v in state["data"].items()}