diff --git a/examples/.md b/examples/.md deleted file mode 100644 index e69de29..0000000 diff --git a/examples/udict.md b/examples/udict.md new file mode 100644 index 0000000..3033026 --- /dev/null +++ b/examples/udict.md @@ -0,0 +1,303 @@ +# UDict + +## Introduction + +UDict is class which is simplifying working with Python dicts. +It has many methods, properties, magic methods. + +Firstly, import `UDict` from `ufpy` +```python +from ufpy import UDict +``` + +## Create UDict + +For creating UDict you should use this code: +```python +d = UDict({'id': 2, 'content': 'hello, world'}) +# for string keys you can use this way: +d = UDict(id=2, content='hello, world') +``` + +You can also define `default` value for items when you use item's getter: +```python +d = UDict(id=2, content='hello, world', default=0) +``` + +## Get items + +For getting items you should use the way you use in lists and dicts: +use `UDict[key]` syntax: +```python +d['id'] # 2 +``` + +You can also use index of key or slice of indexes of keys: + +> [!CAUTION] +> In this class first index is 1 + +```python +d[1] # 2 +d[:] # u{'id': 2, 'content': 'hello, world'} (UDict object) +``` + +You can also use `get()` method. +You can get item by its key using `key` kwarg, +you can get item by its index using `index` kwarg, +and you get item key by its value using `value` kwarg. + +Also you can define `default` only for this calling of `get()` using `default` kwarg. + +Example: +```python +d = UDict({2: 3, 1: 4}, default=None) +d[2] # 3 +d.get(index=2) # 4 +d.get(key=1) # also 4 +d.get(value=3) # 2 + +d.get(key=3) # None +d.get(key=3, default='null') # 'null' +``` + +## Set items + +For setting items you should use the way you use in lists and dicts: +use `UDict[key] = value` syntax: +```python +d['id'] = 3 +``` + +Also, you can use indexes and slices when you are setting items: +```python +d[1] = 2 +d[2:6:2] = 8 +d[:3] = 1, 2, 3 +``` + +## Delete items + +For deleting items you should use the way you use in lists and dicts: +use `del UDict[key]` syntax: +```python +del d['id'] +``` + +Also, you can use indexes and slices: +```python +del d[1] +del d[:] # all UDict will become empty +``` + +## Get length of dict + +You can get length of dict using inbuilt `len()` function + +```python +d = UDict(hello=1, hi=2) +len(d) # 2 +``` + +## Iterate dict: `keys`, `values`, `items` properties + +You can iterate dict using `for key, value in UDict` syntax. + +```python +d = UDict(hello=1, hi=2) +for key, value in d: + print(key, value) + +# out: +# +# hello 1 +# hi 2 +``` + +Also, you can iterate `items` property for this result + +```python +for key, value in d.items: + ... +``` + +If you want iterate only all keys or values, use `keys` or `values` properties + +```python +for key in d.keys: + ... + +for value in d.values: + ... +``` + +## Check that dict is empty or not empty + +You can use `is_empty()` method to check that UDict is empty: +```python +d = UDict() +print(d.is_empty()) # True + +d['hello'] = 'world' +print(d.is_empty()) # False +``` + +You also can use `if UDict` or `bool(UDict)` syntax: +```python +d = UDict() +print(bool(d)) # False + +d['hello'] = 'world' +print(bool(d)) # True + +if d: + print('True!') + +# out: True! +``` + +## Check that key or item in dict + +You can check that key in dict: +```python +d = UDict(hi=1, hello=2) + +print('hi' in d) # True +print('hii' in d) # False +``` + +You can also check that item in dict: +```python +d = UDict(hi=1, hello=2) + +print(('hi', 1) in d) # True +print(('hi', 11) in d) # False +``` + +## Convert to `str` type and using in `print` + +### Print dict + +`print` uses `repr()` therefore `UDict` supports `repr()` + +```python +d = UDict(hi=1, hello=2) +print(d) # u{'hi': 1, 'hello': 2} +print(repr(d)) # u{'hi': 1, 'hello': 2} +``` + +### Convert to str + +You can use inbuilt `str()` class for converting `UDict` to `str` type + +```python +d = UDict(hi=1, hello=2) +print(str(d)) # {'hi': 1, 'hello': 2} +``` + +## Comparing dicts + +You can compare `UDict`s using inbuilt compare operators +(`==`, `!=`, `>`, `>=`, `<`, `<=`): + +> [!NOTE] +> When you use equal and not equal compare operator, dicts are comparing by its items and length, +> but in other compare operators dicts are comparing only by its length. +> +> For example, `d > d2 -> len(d) > len(d2)`, etc. + +```python +d = UDict(hi=1, hello=2) +d2 = UDict(hi=1, hello=2) +d3 = UDict(hi=1, hello=2, world=3) + +print(d == d2) # True +print(d != d2) # False +print(d < d3) # True +print(d <= d3) # True +print(d3 > d) # True +print(d3 >= d) # True +``` + +## Math operations + +You can use inbuilt math operators (`+`, `-`, `*`, `/`, `+=`, `-=`, `*=`, `/=`) +with `UDict`s: + +> [!NOTE] +> When you use sum and sub math operators (`+`, `-`) dicts are summing or subtracting, but +> when you use other math operators dict will be multiplying or dividing by integer or dict. +> +>When dict it works like this: +> +> d * {'hello': 2, 'hi': 0.5} +> +> d['hello'] * 2 +> +> d['hi'] * 0.5 + +```python +d = UDict(hi=1, hello=2) +print(d + {'world': 3}) # u{'hi': 1, 'hello': 2, 'world': 3} +print(d - {'hello': 2}) # u{'hi': 1} + +print(d * 2) # u{'hi': 2, 'hello': 4} +print(d * {'hi': 2}) # u{'hi': 2, 'hello': 2} + +print(d / 2) # u{'hi': 0.5, 'hello': 1} +print(d / {'hi': 2}) # u{'hi': 0.5, 'hello': 2} +``` + +## Negative dict + +You can use unary minus with dicts: +```python +d = UDict(hi=1, hello=2) +print(-d) # u{'hi': -1, 'hello': -2} +``` + +## Reverse dict + +You can reverse dict using `reverse()` or `reversed()` method. +> [!CAUTION] +> When you use `reverse()`, dict updates in contrast to `reversed()`. Be careful! +> +> ```python +> # reverse() +> d = UDict(b=1, a=2) +> print(d.reverse()) # u{'a': 2, 'b': 1} +> print(d) # u{'a': 2, 'b': 1} +> +> # reversed() +> d = UDict(b=1, a=2) +> print(d.reversed()) # u{'a': 2, 'b': 1} +> print(d) # u{'b': 1, 'a': 2} +> ``` + +```python +d.reverse() +d.reversed() +``` + +Also, you can use `~` operator and `reversed()` class. They are equivalents of `UDict.reversed()`: +```python +~d +reversed(d) +``` + +## Sort dict + +You can use `sort()` and `sorted()` methods for sorting UDict. +`sort()` is updating dict, `sorted()` - isn't. + +```python +# sort() +d = UDict(b=1, a=2) +print(d.sort()) # u{'a': 2, 'b': 1} +print(d) # u{'a': 2, 'b': 1} + +# sorted() +d = UDict(b=1, a=2) +print(d.sorted()) # u{'a': 2, 'b': 1} +print(d) # u{'b': 1, 'a': 2} +``` diff --git a/tests/test_udict.py b/tests/test_udict.py new file mode 100644 index 0000000..f473428 --- /dev/null +++ b/tests/test_udict.py @@ -0,0 +1,162 @@ +import unittest + +from ufpy import UDict + + +class UDictTestCase(unittest.TestCase): + def test_init(self): + d = UDict(hello=1, hi=2, default=10) + d2 = UDict({'hello': 1, 'hi': 2}) + self.assertEqual(d.default, 10) + self.assertDictEqual(d.dictionary, d2.dictionary) + self.assertEqual(d, d2) + + def test_keys_values_items(self): + d = UDict(hello=1, hi=2) + + self.assertEqual(d.keys, list(d.dictionary.keys())) + self.assertEqual(d.values, list(d.dictionary.values())) + self.assertEqual(d.items, list(d.dictionary.items())) + + def test_call(self): + d = {'hello': 1, 'hi': 2} + ud = UDict(d) + + d2 = d + + for k, v in d2.items(): + d2[k] = v * 2 + + ud2 = ud(lambda k, v: v * 2) + + self.assertDictEqual(d2, ud2.dictionary) + + def test_reverse(self): + d = {'hello': 1, 'hi': 2} + ud = UDict(d) + + keys, values = list(d.keys())[::-1], list(d.values())[::-1] + d2 = dict(list(zip(keys, values))) + + self.assertDictEqual(d2, ud.reversed().dictionary) + self.assertEqual(reversed(ud), ud.reversed()) + self.assertEqual(~ud, ud.reverse()) + + def test_sort(self): + d = {'hi': 2, 'hello': 1} + ud = UDict(d) + sd = {'hello': 1, 'hi': 2} + sud = UDict(sd) + + self.assertDictEqual(ud.sorted().dictionary, sd) + self.assertEqual(sorted(ud), sud.items) + self.assertEqual(ud.sorted(), ud.sort()) + + def test_get_item(self): + d = UDict(hello=1, hi=2) + + self.assertEqual(d['hi'], 2) + self.assertEqual(d[1], 1) + self.assertEqual(d[1:2], d.values) + + self.assertEqual(d[-2:], d.values) + + self.assertEqual(d[1], d['hello']) + + def test_set_item(self): + d = UDict(hello=1, hi=2) + d['hi'] = 3 + self.assertDictEqual(d.dictionary, {'hello': 1, 'hi': 3}) + + d[1] = 4 + self.assertDictEqual(d.dictionary, {'hello': 4, 'hi': 3}) + + d[:] = [1, 2] + self.assertDictEqual(d.dictionary, {'hello': 1, 'hi': 2}) + + def test_del_item(self): + d = UDict(hello=1, hi=2) + del d[1] + self.assertDictEqual(d.dictionary, {'hi': 2}) + del d[:] + self.assertDictEqual(d.dictionary, {}) + + def test_get(self): + d = UDict({2: 1, 4: 91, 1: 12}, default=None) + self.assertEqual(d.get(index=1), d.get(key=2)) + self.assertEqual(d.get(index=2), d.get(key=4)) + self.assertEqual(d.get(index=3), d.get(key=1)) + + self.assertEqual(d.get(value=1), 2) + self.assertEqual(d.get(value=91), 4) + self.assertEqual(d.get(value=12), 1) + + self.assertEqual(d.get(key=3, default='missing'), 'missing') + self.assertEqual(d.get(key=3), None) + self.assertEqual(d.get(value=3, default='missing'), 'missing') + self.assertEqual(d.get(value=3), None) + + def test_len_and_iter(self): + d = UDict(hello=1, hi=2) + self.assertEqual(len(d), 2) + + l = [] + for k, v in d: + l.append((k, v)) + + self.assertEqual(l, [ + ('hello', 1), ('hi', 2) + ]) + + def test_nonzero(self): + d = UDict(hello=1, hi=2) + self.assertTrue(bool(d)) + + def test_contains(self): + d = UDict(hello=1, hi=2) + self.assertTrue('hello' in d) + self.assertTrue('hi' in d) + + self.assertTrue(('hello', 1) in d) + self.assertFalse(('hello', 2) in d) + + self.assertTrue(('hi', 2) in d) + self.assertFalse(('hi', 1) in d) + + def test_str_and_repr(self): + d = {'hello': 1, 'hi': 2} + ud = UDict(d) + + self.assertEqual(str(ud), str(d)) + self.assertEqual(repr(ud), f'u{repr(d)}') + + def test_cmp_and_eq(self): + d = {'hello': 1, 'hi': 2} + ud = UDict(d) + + ud2 = UDict(hello=1, hi=2, world=3) + + self.assertTrue(ud2 != ud) + self.assertTrue(ud2 > ud) + self.assertTrue(ud2 >= ud) + self.assertTrue(ud < ud2) + self.assertTrue(ud <= ud2) + + self.assertEqual(d, ud) + + def test_math_operations(self): + d = UDict(hello=1, hi=2) + + self.assertEqual(d + {'world': 3}, UDict(hello=1, hi=2, world=3)) + self.assertEqual(d - {'hi': 2}, UDict(hello=1)) + + self.assertEqual(d * 2, UDict(hello=2, hi=4)) + self.assertEqual(d / 2, UDict(hello=0.5, hi=1)) + + def test_neg(self): + d = UDict(hello=1, hi=2) + self.assertEqual((-d).dictionary, {'hello': -1, 'hi': -2}) + + +if __name__ == '__main__': + unittest.main() diff --git a/ufpy/__init__.py b/ufpy/__init__.py index e69de29..ba61b7c 100644 --- a/ufpy/__init__.py +++ b/ufpy/__init__.py @@ -0,0 +1,6 @@ +__version__ = '1.0-alpha' + +from .cmp import * +from .math_op import * +from .udict import * +from .utils import * diff --git a/ufpy/cmp.py b/ufpy/cmp.py new file mode 100644 index 0000000..0335af3 --- /dev/null +++ b/ufpy/cmp.py @@ -0,0 +1,57 @@ +__all__ = ( + 'cmp_generator', + 'Comparable', +) + +from typing import Generic, Protocol, Type, TypeVar + +T = TypeVar('T', bound=type) + +def cmp_generator(t: Type[T]) -> Type[T]: + ''' + Decorator for auto generate compare magic methods (`__eq__()`, `__ne__()`, `__lt__()`, `__le__()`, `__gt__()`, `__ge__()`). + You should add `__cmp__()` method in your class. If your object more that other, `__cmp__()` should return positive number. + If less that other, `__cmp__()` should return negative number. If equals, `__cmp__()` should return zero. + + `Note:` + If you defined one of compare methods, they won't redefine. + + Example: + ```py + @cmp_generator + class A: + def __init__(self, num: int): + self.num = num + def __cmp__(self, other: int) -> int: + return self.num - self.other + + a = A(2) + print(a > 1) # True + print(a < 2) # False + print(a >= 3) # False + print(a <= 4) # True + ``` + + Online docs: soon! + ''' + + if not '__eq__' in t.__dict__: + t.__eq__ = lambda self, x: t.__cmp__(self, x) == 0 + t.__ne__ = lambda self, x: not t.__eq__(self, x) + + if not '__lt__' in t.__dict__: + t.__lt__ = lambda self, x: t.__cmp__(self, x) < 0 + if not '__le__' in t.__dict__: + t.__le__ = lambda self, x: t.__cmp__(self, x) <= 0 + + if not '__gt__' in t.__dict__: + t.__gt__ = lambda self, x: t.__cmp__(self, x) > 0 + if not '__ge__' in t.__dict__: + t.__ge__ = lambda self, x: t.__cmp__(self, x) >= 0 + + return t + +CT = TypeVar('CT') + +class Comparable(Protocol, Generic[CT]): + def __cmp__(self, other: CT) -> int: ... diff --git a/ufpy/math_op.py b/ufpy/math_op.py new file mode 100644 index 0000000..32c9a81 --- /dev/null +++ b/ufpy/math_op.py @@ -0,0 +1,69 @@ +__all__ = ( + 'i_generator', + 'r_generator', +) + +from typing import Type, TypeVar + + +T = TypeVar('T') + +def i_generator(t: Type[T]) -> Type[T]: + if '__add__' in t.__dict__: + t.__iadd__ = t.__add__ + if '__sub__' in t.__dict__: + t.__isub__ = t.__sub__ + if '__mul__' in t.__dict__: + t.__imul__ = t.__mul__ + if '__floordiv__' in t.__dict__: + t.__ifloordiv__ = t.__floordiv__ + if '__div__' in t.__dict__: + t.__idiv__ = t.__div__ + if '__truediv__' in t.__dict__: + t.__itruediv__ = t.__truediv__ + if '__mod__' in t.__dict__: + t.__imod__ = t.__mod__ + if '__pow__' in t.__dict__: + t.__ipow__ = t.__pow__ + if '__lshift__' in t.__dict__: + t.__ilshift__ = t.__lshift__ + if '__rshift__' in t.__dict__: + t.__irshift__ = t.__rshift__ + if '__and__' in t.__dict__: + t.__iand__ = t.__and__ + if '__or__' in t.__dict__: + t.__ior__ = t.__or__ + if '__xor__' in t.__dict__: + t.__ixor__ = t.__xor__ + + return t + +def r_generator(t: Type[T]) -> Type[T]: + if '__add__' in t.__dict__: + t.__radd__ = t.__add__ + if '__sub__' in t.__dict__: + t.__rsub__ = t.__sub__ + if '__mul__' in t.__dict__: + t.__rmul__ = t.__mul__ + if '__floordiv__' in t.__dict__: + t.__rfloordiv__ = t.__floordiv__ + if '__div__' in t.__dict__: + t.__rdiv__ = t.__div__ + if '__truediv__' in t.__dict__: + t.__rtruediv__ = t.__truediv__ + if '__mod__' in t.__dict__: + t.__rmod__ = t.__mod__ + if '__pow__' in t.__dict__: + t.__rpow__ = t.__pow__ + if '__lshift__' in t.__dict__: + t.__rlshift__ = t.__lshift__ + if '__rshift__' in t.__dict__: + t.__rrshift__ = t.__rshift__ + if '__and__' in t.__dict__: + t.__rand__ = t.__and__ + if '__or__' in t.__dict__: + t.__ror__ = t.__or__ + if '__xor__' in t.__dict__: + t.__rxor__ = t.__xor__ + + return t diff --git a/ufpy/typ/__init__.py b/ufpy/typ/__init__.py new file mode 100644 index 0000000..d5820fa --- /dev/null +++ b/ufpy/typ/__init__.py @@ -0,0 +1,2 @@ +from .protocols import * +from .type_alias import * diff --git a/ufpy/typ/protocols.py b/ufpy/typ/protocols.py new file mode 100644 index 0000000..16d99ef --- /dev/null +++ b/ufpy/typ/protocols.py @@ -0,0 +1,39 @@ +__all__ = ( + 'SupportsGetItem', + 'SupportsGet', + 'SupportsSetItem', + 'SupportsDelItem', + 'LikeDict', +) + +from typing import Protocol, Generic, TypeVar + +KT = TypeVar('KT') +VT = TypeVar('VT') +DV = TypeVar('DV') + + +class SupportsGetItem(Protocol, Generic[KT, VT]): + def __getitem__(self, key: KT) -> VT: ... + + +class SupportsGet(Protocol, Generic[KT, VT]): + def get(self, key: KT, default: DV) -> VT | DV: ... + + +class SupportsSetItem(Protocol, Generic[KT, VT]): + def __setitem__(self, key: KT, value: VT) -> None: ... + + +class SupportsDelItem(Protocol, Generic[KT, VT]): + def __delitem__(self, key: KT) -> None: ... + + +class LikeDict( + SupportsGet[KT, VT], + SupportsGetItem[KT, VT], + SupportsSetItem[KT, VT], + SupportsDelItem[KT, VT], + Generic[KT, VT] +): + ... diff --git a/ufpy/typ/type_alias.py b/ufpy/typ/type_alias.py new file mode 100644 index 0000000..69e576d --- /dev/null +++ b/ufpy/typ/type_alias.py @@ -0,0 +1,9 @@ +from .protocols import LikeDict + +__all__ = ( + 'AnyCollection', + 'AnyDict' +) + +type AnyCollection[T] = tuple[T, ...] | list[T] +type AnyDict[KT, VT] = dict[KT, VT] | LikeDict[KT, VT] diff --git a/ufpy/udict.py b/ufpy/udict.py new file mode 100644 index 0000000..4a6c094 --- /dev/null +++ b/ufpy/udict.py @@ -0,0 +1,309 @@ +from typing import Generic, Iterator, overload, TypeVar, Callable + +from .cmp import cmp_generator +from .math_op import i_generator, r_generator +from .typ import AnyDict, AnyCollection +from .utils import set_items_for_several_keys, get_items_for_several_keys, del_items_for_several_keys + +__all__ = ( + 'UDict', +) + +KT = TypeVar('KT') +VT = TypeVar('VT') +CDV = TypeVar('CDV') +DV = TypeVar('DV') + +class _ClassDefault: ... + +@cmp_generator +@i_generator +@r_generator +class UDict(Generic[KT, VT, CDV]): + @overload + def __init__(self, dictionary: AnyDict[KT, VT]): ... + @overload + def __init__(self, dictionary: AnyDict[KT, VT], *, default: CDV): ... + @overload + def __init__(self, **kwargs: VT): ... + @overload + def __init__(self, *, default: CDV, **kwargs: VT): ... + + def __init__(self, dictionary: AnyDict[KT, VT] = None, *, default: CDV = None, **kwargs: VT): + if isinstance(dictionary, UDict): + dictionary = dictionary.dictionary + self.__dict = dictionary if dictionary else kwargs + self.__default = default + + # dictionary + @property + def dictionary(self) -> dict[KT, VT]: + return self.__dict + + @dictionary.setter + def dictionary(self, value: AnyDict[KT, VT]): + if isinstance(value, UDict): + value = value.dictionary + self.__dict = value + + # keys + @property + def keys(self) -> list[KT]: + return list(self.__dict.keys()) + + @keys.setter + def keys(self, value: AnyCollection[KT]): + self.__dict = dict(list(zip(value, self.values))) + + # values + @property + def values(self) -> list[VT]: + return list(self.__dict.values()) + + @values.setter + def values(self, value: AnyCollection[VT]): + self.__dict = dict(list(zip(self.keys, value))) + + # items + @property + def items(self) -> list[tuple[KT, VT]]: + return list(zip(self.keys, self.values)) + + @items.setter + def items(self, value: AnyCollection[tuple[KT, VT] | list[KT | VT]]): + self.__dict = dict(value) + + # default + @property + def default(self) -> CDV: + return self.__default + + @default.setter + def default(self, value: CDV): + self.__default = value + + # call + def __call__(self, func: Callable[[KT, VT], VT]) -> 'UDict[KT, VT, CDV]': + new_dict = self.__dict + for k, v in self: + new_dict[k] = func(k, v) + return UDict(new_dict, default=self.__default) + + # reverse integers + def __neg__(self) -> 'UDict[KT, VT, CDV]': + return self(lambda k, v: -v) + + # reverse + def reverse(self) -> 'UDict[KT, VT, CDV]': + self.__dict = self.reversed().__dict + return self + + def reversed(self) -> 'UDict[KT, VT, CDV]': + keys, values = list(self.__dict.keys())[::-1], list(self.__dict.values())[::-1] + return UDict(dict(list(zip(keys, values)))) + + def __invert__(self) -> 'UDict[KT, VT, CDV]': + return self.reversed() + + def __reversed__(self) -> 'UDict[KT, VT, CDV]': + return self.reversed() + + # sort + def sort(self) -> 'UDict[KT, VT, CDV]': + self.__dict = self.sorted().__dict + return self + + def sorted(self) -> 'UDict[KT, VT, CDV]': + keys = sorted(list(self.__dict.keys())) + values = get_items_for_several_keys(self.__dict, keys) + return UDict(dict(list(zip(keys, values)))) + + # get/set/del items + def __get_keys_from_slice_or_int(self, key: KT | int | slice) -> list[KT]: + if isinstance(key, int) and key not in self.__dict: + if key == 0: + raise IndexError("You can't use 0 as index in UDict. Use 1 index instead.") + return [list(self.__dict.keys())[key - 1]] + if isinstance(key, slice): + start, stop, step = key.indices(len(self) + 1) + if start == 0: + start += 1 + if stop == len(self) + 1: + stop -= 1 + indexes = list(range(start, stop + 1, step)) + return [list(self.__dict.keys())[i - 1] for i in indexes] + return [key] + + def __getitem__(self, key: KT | int | slice) -> 'UDict[KT, VT, DV] | VT': + keys = self.__get_keys_from_slice_or_int(key) + + l = get_items_for_several_keys(self.__dict, keys, self.__default) + return l if len(l) > 1 else l[0] + + def __setitem__(self, key: KT | int | slice, value: VT | list[VT]) -> None: + keys = self.__get_keys_from_slice_or_int(key) + values = [value] if not isinstance(value, (list, tuple)) else value + + if len(keys) > len(values): + values.extend([values[-1] for _ in range(len(keys) - len(values) + 1)]) + + self.__dict = set_items_for_several_keys(self.__dict, keys, values) + + def __delitem__(self, key: KT | int | slice) -> None: + keys = self.__get_keys_from_slice_or_int(key) + + self.__dict = del_items_for_several_keys(self.__dict, keys) + + # get + @overload + def get(self, *, key: KT) -> VT | CDV: ... + @overload + def get(self, *, key: KT, default: DV) -> VT | DV: ... + @overload + def get(self, *, index: int) -> VT | CDV: ... + @overload + def get(self, *, index: int, default: DV) -> VT | DV: ... + @overload + def get(self, *, value: VT) -> KT | CDV: ... + @overload + def get(self, *, value: VT, default: DV) -> KT | DV: ... + + def get( + self, *, key: KT = None, index: int = None, value: VT = None, + default: DV | CDV = _ClassDefault + ) -> KT | VT | CDV | DV: + """ + Get a value with key or it's index. + + If value is defined, returns key + + :param key: Key of value in dict (optional) + :param index: Index of value in dict (optional) + :param value: Value in dict (optional) + :param default: Default value (if none -> UDict.default) (optional) + :return: Value or default value + + :exception ValueError: You defined 0 or 2 or 3 params + :exception IndexError: index is bigger that length of dict + """ + if key and index and value: + raise ValueError( + 'You defined both key, index and value params. Please cancel the definition one of this params.' + ) + if not key and not index and not value: + raise ValueError( + "You don't defined neither key, not index, not value params." + + " Please cancel the definition one of this params." + ) + if key and value: + raise ValueError('You defined both key and value params. Please cancel the definition one of this params.') + if key and index: + raise ValueError('You defined both key and index params. Please cancel the definition one of this params.') + if index and value: + raise ValueError( + 'You defined both index and value params. Please cancel the definition one of this params.' + ) + + if index and index > len(self): + raise IndexError('Index is bigger that length of UDict.') + + if default == _ClassDefault: + default = self.__default + + if value: + if value not in self.values: + return default + i = self.values.index(value) + return self.keys[i] + return self.__dict.get(self.keys[index-1], default) if index else self.__dict.get(key, default) + + # Len, iterator and reversed version + def __len__(self) -> int: + return len(self.items) + + def __iter__(self) -> Iterator[tuple[KT, VT]]: + return iter(self.items) + + # Booleans + def is_empty(self) -> bool: + return len(self) == 0 + + def __nonzero__(self) -> bool: + return not self.is_empty() + + def __contains__(self, item: tuple[KT, VT] | list[KT | VT] | KT) -> bool: + if isinstance(item, (list, tuple)): + k, v = item + return k in self.__dict and self.__dict.get(k, self.__default) == v + return item in self.__dict + + # Transform to other types + def __str__(self) -> str: + return str(self.__dict) + + def __repr__(self) -> str: + return f'u{self.__dict}' + + def __hash__(self) -> int: + return hash(self.__repr__()) + + # Comparing + def __cmp__(self, other: 'dict[KT, VT] | UDict[KT, VT, CDV]') -> int: + return len(self) - len(other) + + def __eq__(self, other: 'dict[KT, VT] | UDict[KT, VT, CDV]') -> bool: + if isinstance(other, UDict): + other = other.dictionary + return self.__dict == other + + # Math operations + def __add__(self, other: 'dict[KT, VT] | UDict[KT, VT, CDV]') -> 'UDict[KT, VT, CDV]': + new_dict = self.__dict.copy() + + if isinstance(other, UDict): + other: dict[KT, VT] = other.dictionary + + for k, v in other.items(): + new_dict[k] = v + return UDict(new_dict) + + def __sub__(self, other: 'dict[KT, VT] | UDict[KT, VT, CDV]') -> 'UDict[KT, VT, CDV]': + new_dict = self.__dict.copy() + + if isinstance(other, UDict): + other: dict[KT, VT] = other.dictionary + + for k, v in other.items(): + if new_dict.get(k) == v: + del new_dict[k] + return UDict(new_dict) + + def __mul__( + self, other: 'dict[KT, float | int] | UDict[KT, float | int, DV] | float | int' + ) -> 'UDict[KT, VT, CDV]': + new_dict = self.__dict.copy() + + if isinstance(other, UDict): + other: dict[KT, VT] = other.dictionary + if isinstance(other, (int, float)): + other = dict([(k, other) for k in new_dict.keys()]) + + for k, v in other.items(): + new_dict[k] *= v + + return UDict(new_dict) + + def __truediv__( + self, other: 'dict[KT, float | int] | UDict[KT, float | int, DV] | float | int' + ) -> 'UDict[KT, VT, CDV]': + new_dict = self.__dict.copy() + + if isinstance(other, UDict): + other: dict[KT, VT] = other.dictionary + if isinstance(other, (int, float)): + other = dict([(k, other) for k in new_dict.keys()]) + + for k, v in other.items(): + new_dict[k] /= v + + return UDict(new_dict) diff --git a/ufpy/utils.py b/ufpy/utils.py new file mode 100644 index 0000000..a975752 --- /dev/null +++ b/ufpy/utils.py @@ -0,0 +1,33 @@ +__all__ = ( + 'get_items_for_several_keys', + 'set_items_for_several_keys', + 'del_items_for_several_keys', +) + +from typing import TypeVar + +from .typ import SupportsGet, SupportsSetItem, SupportsDelItem, AnyCollection + +KT = TypeVar('KT') +VT = TypeVar('VT') +DV = TypeVar('DV') + + +def get_items_for_several_keys(o: SupportsGet[KT, VT], keys: AnyCollection[KT], default: DV = None) -> list[VT | DV]: + return [o.get(k, default) for k in keys] + + +def set_items_for_several_keys( + o: SupportsSetItem[KT, VT], keys: AnyCollection[KT], values: AnyCollection[VT] +) -> SupportsSetItem[KT, VT]: + res = o + for i, k in enumerate(keys): + res[k] = values[i] + return res + + +def del_items_for_several_keys(o: SupportsDelItem[KT, VT], keys: AnyCollection[KT]) -> SupportsDelItem[KT, VT]: + res = o + for k in keys: + del res[k] + return res