Source code for better_dict.accessors

"""
Accessor classes for :class:`better_dict.core.BetterDict` dictionary.

Classes
-------
This module defines the following classes:

- :class:`.VLoc`: Class allows the key retrieval using the dictionary values.
- :class:`.ILoc`: Class allows access and setting values using index notation.

Notes
-----
The :class:`.VLoc` and :class:`.ILoc` classes can be used by any dictionary-like
class. To create a new class that inherits from :class:`.VLoc` or :class:`.ILoc`
you must define the following properties:

- ``iloc``: property needed to implement the :class:`.ILoc` class.
- ``vloc``: property needed to implement the :class:`.VLoc` class.

The following example shows how to implement them:

.. code-block:: python

    class MyDict(dict, VLoc, ILoc):
        @property
        def iloc(self):
            return ILoc(self)

        @property
        def vloc(self):
            return VLoc(self)

In the example above, the ``MyDict`` class inherits from the ``dict``,
:class:`.ILoc` and :class:`.VLoc` classes. By defining the properties
``iloc`` and ``vloc`` and passing ``self`` to the :class:`.ILoc` and
:class:`.VLoc`, the dictionary keys and values can be accessed by the
:class:`.ILoc` and :class:`.VLoc` classes.

"""
from __future__ import annotations

from typing import Any, Hashable, Iterable, List

import numpy as np

from better_dict.utils import (iterable_not_string, same_length)


[docs]class VLoc: """ Class for accessing the keys of a dictionary by value. Parameters ---------- data : BetterDict Dictionary to access. Examples -------- The following example shows how to use the :class:`.VLoc` class to access the keys of a dictionary by value. >>> from better_dict import BetterDict >>> d = BetterDict({'a': 1, 'b': 2, 'c': 3}) >>> d.vloc[1] ['a'] Notes ----- The :class:`.VLoc` class only allows access to the keys of the dictionary by value. It does not allow manipulations of the dictionary keys or values. """ def __init__(self, data): self.data = data
[docs] def __getitem__(self, value: object | Iterable[object]) -> List[Hashable]: """Get the keys of the dictionary that match the value(s). Parameters ---------- value : object | Iterable[object] Value(s) to match. Returns ------- List[Hashable] keys(s) of the dictionary that match the value(s). """ if not isinstance(value, (np.ndarray, list, tuple, set)): value = [value] return [_key for _key, _value in self.data.items() if any(v == _value for v in value)]
[docs] def inverse(self): """Get the inverse of the dictionary. Returns ------- BetterDict Inverse of the dictionary. """ return self.data.__class__( {value: key for key, value in self.data.items()} )
[docs]class ILoc: """ Class for manipulating :class:`.BetterDict` using index notation. Parameters ---------- data : BetterDict Dictionary to access as a :class:`.BetterDict` object. Methods ------- __getitem__(index: int | Iterable[int]) -> Any Get the value of the dictionary at the given index(es). __setitem__(index: int | Iterable[int], value: Any | Iterable[Any]) -> None Set the value(s) of the dictionary at the given index(es). Attributes ---------- data : BetterDict Dictionary to access as a :class:`.BetterDict` object. Examples -------- The following example shows how to use the :class:`.ILoc` class to access the values of a :class:`.BetterDict` dictionary using index notation: >>> from better_dict import BetterDict >>> d = BetterDict({'a': 1, 'b': 2, 'c': 3}) >>> d.iloc[0] 1 You can also use the :class:`.ILoc` class get multiple values at once: >>> d.iloc[0, 1] [1, 2] >>> d.iloc[[0, 1]] [1, 2] >>> d.iloc[1:] [2, 3] Class allows setting multiple values at once using index notation: >>> d.iloc[[0, 1]] = [10, 20] >>> d {'a': 10, 'b': 20, 'c': 3} """ def __init__(self, data): self.data = data
[docs] def __getitem__(self, index: int | Iterable[int]) -> Any: """Get the value of the dictionary at the given index(es). Parameters ---------- index : int | Iterable[int] Index(es) of the value(s) to get. Returns ------- Any Value(s) of the dictionary at the given index(es). """ values = list(self.data.values()) return ( [values[i] for i in index] if iterable_not_string(index) else values[index] )
[docs] def __setitem__( self, index: int | Iterable[int], value: Any | Iterable[Any], ) -> None: """ Set the value(s) of the dictionary at the given index(es). To set multiple values at once, you need to specify the same number of indices and values, for example: .. code-block:: python d.iloc[[0, 1]] = [10, 20] Parameters ---------- index : int | Iterable[int] Index(es) of the value(s) to set. value : Any | Iterable[Any] Value(s) to set. """ keys = list(self.data.keys()) if same_length(index, value): for _index, _value in zip(index, value): self.data[keys[_index]] = _value else: self.data[keys[index]] = value
@property def index(self) -> List[int]: """Get the indexes of the dictionary. Returns ------- List[int] Indexes of the dictionary. """ return list(range(len(self.data.keys())))