From 58762fbab9c94bae5609dbda358ac031338074cd Mon Sep 17 00:00:00 2001 From: bleudev Date: Wed, 22 May 2024 21:53:54 +0300 Subject: [PATCH 01/28] UDict part 1 --- .gitignore | 3 +++ ufpy/udict.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 ufpy/udict.py diff --git a/.gitignore b/.gitignore index 68bc17f..06f40ca 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,6 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# Vscode +.vscode diff --git a/ufpy/udict.py b/ufpy/udict.py new file mode 100644 index 0000000..12ee3f9 --- /dev/null +++ b/ufpy/udict.py @@ -0,0 +1,39 @@ +from typing import Generic, Literal, overload, TypeVar + +KT = TypeVar('KT') +VT = TypeVar('VT') + +class UDict(Generic[KT, VT]): + @overload + def __init__(self, dict: dict[KT, VT]): ... + @overload + def __init__(self, **kwargs: VT): ... + def __init__(self, dictionary = None, **kwargs): + self.__dict = dictionary if dictionary else kwargs + + @property + def dictionary(self) -> dict[KT, VT]: + return self.__dict + + @dictionary.setter + def dictionary(self, value: dict[KT, VT] | "UDict"): + if isinstance(value, UDict): + value = value.dictionary + self.__dict = value + + def __len__(self) -> int: + return len(self.__dict.keys()) + + def __compare(self, other: dict[KT, VT] | "UDict") -> int | Literal['eq']: + if isinstance(other, UDict): + other = other.dictionary + + if self.__dict == other: + return 'eq' + return len(self) - len(other.keys()) + + def __eq__(self, other: dict[KT, VT] | "UDict") -> bool: + return self.__compare(other) == 'eq' + + def __ne__(self, other: dict[KT, VT] | "UDict") -> bool: + return self.__compare(other) != 'eq' From 5c72f5b6fb74f33f2b866cc3847f9245b3e1578a Mon Sep 17 00:00:00 2001 From: bleudev Date: Wed, 22 May 2024 23:40:34 +0300 Subject: [PATCH 02/28] UDict compare and cmp_generator --- .gitignore | 3 +++ examples/.ipynb | 24 ++++++++++++++++++++++++ ufpy/__init__.py | 4 ++++ ufpy/cmp.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ ufpy/udict.py | 28 +++++++++++++++------------- 5 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 ufpy/cmp.py diff --git a/.gitignore b/.gitignore index 06f40ca..c2ffb04 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,6 @@ cython_debug/ # Vscode .vscode + +# Tests +tests.py diff --git a/examples/.ipynb b/examples/.ipynb index e69de29..bafd298 100644 --- a/examples/.ipynb +++ b/examples/.ipynb @@ -0,0 +1,24 @@ +{ + "cells": [], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/ufpy/__init__.py b/ufpy/__init__.py index e69de29..093a180 100644 --- a/ufpy/__init__.py +++ b/ufpy/__init__.py @@ -0,0 +1,4 @@ +__version__ = '1.0-alpha' + +from .cmp import * +from .udict import * diff --git a/ufpy/cmp.py b/ufpy/cmp.py new file mode 100644 index 0000000..abd9f10 --- /dev/null +++ b/ufpy/cmp.py @@ -0,0 +1,47 @@ +__all__ = ( + 'cmp_generator', +) + +def cmp_generator(t: type): + ''' + 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 diff --git a/ufpy/udict.py b/ufpy/udict.py index 12ee3f9..7ca51d4 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -1,22 +1,30 @@ from typing import Generic, Literal, overload, TypeVar +from .cmp import cmp_generator + +__all__ = ( + 'UDict', +) + KT = TypeVar('KT') VT = TypeVar('VT') +@cmp_generator class UDict(Generic[KT, VT]): @overload - def __init__(self, dict: dict[KT, VT]): ... + def __init__(self, dictionary: dict[KT, VT]): ... @overload def __init__(self, **kwargs: VT): ... def __init__(self, dictionary = None, **kwargs): self.__dict = dictionary if dictionary else kwargs + # dictionary @property def dictionary(self) -> dict[KT, VT]: return self.__dict @dictionary.setter - def dictionary(self, value: dict[KT, VT] | "UDict"): + def dictionary(self, value: "dict[KT, VT] | UDict"): if isinstance(value, UDict): value = value.dictionary self.__dict = value @@ -24,16 +32,10 @@ def dictionary(self, value: dict[KT, VT] | "UDict"): def __len__(self) -> int: return len(self.__dict.keys()) - def __compare(self, other: dict[KT, VT] | "UDict") -> int | Literal['eq']: + def __cmp__(self, other: "dict[KT, VT] | UDict") -> int: + return len(self) - len(other) + + def __eq__(self, other: "dict[KT, VT] | UDict") -> bool: if isinstance(other, UDict): other = other.dictionary - - if self.__dict == other: - return 'eq' - return len(self) - len(other.keys()) - - def __eq__(self, other: dict[KT, VT] | "UDict") -> bool: - return self.__compare(other) == 'eq' - - def __ne__(self, other: dict[KT, VT] | "UDict") -> bool: - return self.__compare(other) != 'eq' + return self.__dict == other From c0fb0fa9fa06b0460b36d38126fc3b1254e6be22 Mon Sep 17 00:00:00 2001 From: bleudev Date: Thu, 23 May 2024 01:49:37 +0300 Subject: [PATCH 03/28] i_generator + Comparable --- ufpy/cmp.py | 12 +++++++++++- ufpy/i.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 ufpy/i.py diff --git a/ufpy/cmp.py b/ufpy/cmp.py index abd9f10..0335af3 100644 --- a/ufpy/cmp.py +++ b/ufpy/cmp.py @@ -1,8 +1,13 @@ __all__ = ( 'cmp_generator', + 'Comparable', ) -def cmp_generator(t: type): +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. @@ -45,3 +50,8 @@ def __cmp__(self, other: int) -> int: 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/i.py b/ufpy/i.py new file mode 100644 index 0000000..2ee37e0 --- /dev/null +++ b/ufpy/i.py @@ -0,0 +1,38 @@ +__all__ = ( + 'i_generator', +) + +from typing import Type, TypeVar + + +T = TypeVar('T', bound=type) + +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 From e9fb2716950a5122e8a3bc2f2f38789a46350f13 Mon Sep 17 00:00:00 2001 From: bleudev Date: Thu, 23 May 2024 16:37:53 +0300 Subject: [PATCH 04/28] math operations + iter --- ufpy/__init__.py | 1 + ufpy/udict.py | 78 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/ufpy/__init__.py b/ufpy/__init__.py index 093a180..e76c2aa 100644 --- a/ufpy/__init__.py +++ b/ufpy/__init__.py @@ -1,4 +1,5 @@ __version__ = '1.0-alpha' from .cmp import * +from .i import * from .udict import * diff --git a/ufpy/udict.py b/ufpy/udict.py index 7ca51d4..fcefaae 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -1,6 +1,7 @@ -from typing import Generic, Literal, overload, TypeVar +from typing import Generic, Iterator, Literal, overload, TypeVar from .cmp import cmp_generator +from .i import i_generator __all__ = ( 'UDict', @@ -10,6 +11,7 @@ VT = TypeVar('VT') @cmp_generator +@i_generator class UDict(Generic[KT, VT]): @overload def __init__(self, dictionary: dict[KT, VT]): ... @@ -28,10 +30,36 @@ def dictionary(self, value: "dict[KT, VT] | UDict"): if isinstance(value, UDict): value = value.dictionary self.__dict = value + + # Get items + def __getitem__(self, key: KT | int | slice): ... + def __setitem__(self, key: KT | int | slice, value: VT): ... + def __delitem__(self, key: KT | int | slice): ... + + def __getattr__(self, name: str): ... + def __setattr__(self, name: str, value: VT): ... + def __delattr__(self, name: str): ... + + + # Len, iterator and reversed version def __len__(self) -> int: return len(self.__dict.keys()) + def __iter__(self) -> Iterator[tuple[KT, VT]]: + res = [] + + for k, v in self.__dict.items(): + res.append((k, v)) + + return iter(res) + + def __reversed__(self) -> 'UDict': ... + + # Booleans + def __contains__(self, item: tuple[KT, VT] | KT) -> bool: ... + + # Comparing def __cmp__(self, other: "dict[KT, VT] | UDict") -> int: return len(self) - len(other) @@ -39,3 +67,51 @@ def __eq__(self, other: "dict[KT, VT] | UDict") -> bool: if isinstance(other, UDict): other = other.dictionary return self.__dict == other + + # Math operations + def __add__(self, other: "dict[KT, VT] | UDict") -> "UDict": + new_dict = self.__dict + + if isinstance(other, UDict): + other = other.dictionary + + for k, v in other.items(): + new_dict[k] = v + return UDict(new_dict) + + def __sub__(self, other: "dict[KT, VT] | UDict") -> "UDict": + new_dict = self.__dict + + if isinstance(other, UDict): + other = 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] | float | int") -> "UDict": + new_dict = self.__dict + + if isinstance(other, UDict): + other = 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] | float | int") -> "UDict": + new_dict = self.__dict + + if isinstance(other, UDict): + other = 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) From dbb56280322429338304ea078d8286b4ef7ce816 Mon Sep 17 00:00:00 2001 From: bleudev Date: Fri, 24 May 2024 20:08:20 +0300 Subject: [PATCH 05/28] `__getitem__()` --- ufpy/__init__.py | 1 + ufpy/udict.py | 59 +++++++++++++++++++++++++++++++++++++++++------- ufpy/utils.py | 20 ++++++++++++++++ 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 ufpy/utils.py diff --git a/ufpy/__init__.py b/ufpy/__init__.py index e76c2aa..290010f 100644 --- a/ufpy/__init__.py +++ b/ufpy/__init__.py @@ -3,3 +3,4 @@ from .cmp import * from .i import * from .udict import * +from .utils import * diff --git a/ufpy/udict.py b/ufpy/udict.py index fcefaae..e30c560 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -2,6 +2,7 @@ from .cmp import cmp_generator from .i import i_generator +from .utils import get_items_for_several_keys __all__ = ( 'UDict', @@ -16,9 +17,14 @@ class UDict(Generic[KT, VT]): @overload def __init__(self, dictionary: dict[KT, VT]): ... @overload + def __init__(self, dictionary: dict[KT, VT], *, default: VT): ... + @overload def __init__(self, **kwargs: VT): ... - def __init__(self, dictionary = None, **kwargs): - self.__dict = dictionary if dictionary else kwargs + @overload + def __init__(self, *, default: VT, **kwargs: VT): ... + def __init__(self, dictionary = None, *, default = None, **kwargs): + self.__dict = dictionary if dictionary is not None else kwargs + self.__default = default # dictionary @property @@ -30,16 +36,49 @@ def dictionary(self, value: "dict[KT, VT] | UDict"): if isinstance(value, UDict): value = value.dictionary self.__dict = value + + # default + @property + def default(self) -> VT: + return self.__default + + @default.setter + def default(self, value: VT): + self.__default = value + + # Reverse + def reverse(self) -> "UDict[KT, VT]": + keys, values = list(self.__dict.keys())[::-1], list(self.__dict.values())[::-1] + self.__dict = dict(list(zip(keys, values))) + return UDict(self.__dict) + + def __neg__(self) -> "UDict[KT, VT]": + d = self + return d.reverse() # Get items - def __getitem__(self, key: KT | int | slice): ... - def __setitem__(self, key: KT | int | slice, value: VT): ... - def __delitem__(self, key: KT | int | slice): ... + def __get_keys_from_slice_or_int(self, key: KT | int | slice) -> list[KT]: + if isinstance(key, int) and key not in self.__dict: + return [list(self.__dict.keys())[key - 1]] + if isinstance(key, slice): + start, stop, step = key.indices(len(self) + 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] | 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): ... + # def __delitem__(self, key: KT | int | slice): ... - def __getattr__(self, name: str): ... - def __setattr__(self, name: str, value: VT): ... - def __delattr__(self, name: str): ... + + # def __getattr__(self, name: str): ... + # def __setattr__(self, name: str, value: VT): ... + # def __delattr__(self, name: str): ... # Len, iterator and reversed version @@ -59,6 +98,10 @@ def __reversed__(self) -> 'UDict': ... # Booleans def __contains__(self, item: tuple[KT, VT] | KT) -> bool: ... + # Transform to other types + def __repr__(self) -> str: + return f'''u{self.__dict}''' + # Comparing def __cmp__(self, other: "dict[KT, VT] | UDict") -> int: return len(self) - len(other) diff --git a/ufpy/utils.py b/ufpy/utils.py new file mode 100644 index 0000000..d04138d --- /dev/null +++ b/ufpy/utils.py @@ -0,0 +1,20 @@ +__all__ = ( + 'SupportsGetItem', + 'SupportsGet', + 'get_items_for_several_keys', +) + +from typing import Protocol, TypeVar, Generic + +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: ... + +def get_items_for_several_keys(o: SupportsGet[KT, VT], keys: tuple[KT, ...], default: DV = None) -> tuple[VT | DV, ...]: + return [o.get(k, default) for k in keys] From 8e850f9037c70706909944f96ef4dcaf9624c5e8 Mon Sep 17 00:00:00 2001 From: bleudev Date: Fri, 24 May 2024 22:33:55 +0300 Subject: [PATCH 06/28] Update `.gitignore` and realise `__setitem__()` --- .gitignore | 4 ++-- ufpy/udict.py | 21 ++++++++++++++++----- ufpy/utils.py | 23 +++++++++++++++++++++-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index c2ffb04..b32d943 100644 --- a/.gitignore +++ b/.gitignore @@ -157,10 +157,10 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ # Vscode -.vscode +#.vscode # Tests tests.py diff --git a/ufpy/udict.py b/ufpy/udict.py index e30c560..ecf5e19 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -1,5 +1,6 @@ -from typing import Generic, Iterator, Literal, overload, TypeVar +from typing import Generic, Iterator, overload, TypeVar +from . import set_items_for_several_keys from .cmp import cmp_generator from .i import i_generator from .utils import get_items_for_several_keys @@ -72,15 +73,25 @@ def __getitem__(self, key: KT | int | slice) -> "UDict[KT, VT] | VT": 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): ... + def __setitem__(self, key: KT | int | slice, value: VT | list[VT]): + keys = self.__get_keys_from_slice_or_int(key) + values = [value] if not isinstance(value, list) else value + + self.__dict = set_items_for_several_keys(self.__dict, keys, values) + + + # TODO: make __delitem__() # def __delitem__(self, key: KT | int | slice): ... - - + + # TODO: make __getattr__() # def __getattr__(self, name: str): ... + + # TODO: make __getattr__() # def __setattr__(self, name: str, value: VT): ... + + # TODO: make __delattr__() # def __delattr__(self, name: str): ... - # Len, iterator and reversed version def __len__(self) -> int: return len(self.__dict.keys()) diff --git a/ufpy/utils.py b/ufpy/utils.py index d04138d..9487923 100644 --- a/ufpy/utils.py +++ b/ufpy/utils.py @@ -2,19 +2,38 @@ 'SupportsGetItem', 'SupportsGet', 'get_items_for_several_keys', + 'set_items_for_several_keys', ) -from typing import Protocol, TypeVar, Generic +from typing import Protocol, TypeVar, Generic, Type + +type AnyCollection[T] = tuple[T, ...] | list[T] 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: ... -def get_items_for_several_keys(o: SupportsGet[KT, VT], keys: tuple[KT, ...], default: DV = None) -> tuple[VT | DV, ...]: + +class SupportsSetItem(Protocol, Generic[KT, VT]): + def __setitem__(self, key: KT, value: VT) -> None: ... + + +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] +) -> Type[SupportsSetItem[KT, VT]]: + res = o + for i, k in enumerate(keys): + res[k] = values[i] + return res From 62e72d6109141ac8eba7158d9d64a33e7e0d0277 Mon Sep 17 00:00:00 2001 From: bleudev Date: Fri, 24 May 2024 23:00:01 +0300 Subject: [PATCH 07/28] Fix `__setitem__()` --- ufpy/udict.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ufpy/udict.py b/ufpy/udict.py index ecf5e19..c3d65a5 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -1,9 +1,8 @@ from typing import Generic, Iterator, overload, TypeVar -from . import set_items_for_several_keys from .cmp import cmp_generator from .i import i_generator -from .utils import get_items_for_several_keys +from .utils import set_items_for_several_keys, get_items_for_several_keys __all__ = ( 'UDict', @@ -73,9 +72,12 @@ def __getitem__(self, key: KT | int | slice) -> "UDict[KT, VT] | VT": 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]): + 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) else value + 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) From 118a0d36b449ce72e525f7cbd00ff7881543ef48 Mon Sep 17 00:00:00 2001 From: bleudev Date: Fri, 24 May 2024 23:51:37 +0300 Subject: [PATCH 08/28] `__reversed__()`, `__nonzero__()`, `__delitem__()`, move all protocols to `protocols.py` --- ufpy/protocols.py | 27 ++++++++++++++++++++++++ ufpy/udict.py | 53 ++++++++++++++++++++++++++++------------------- ufpy/utils.py | 27 ++++++++++-------------- 3 files changed, 70 insertions(+), 37 deletions(-) create mode 100644 ufpy/protocols.py diff --git a/ufpy/protocols.py b/ufpy/protocols.py new file mode 100644 index 0000000..3e06d7c --- /dev/null +++ b/ufpy/protocols.py @@ -0,0 +1,27 @@ +__all__ = ( + 'SupportsGetItem', + 'SupportsGet', + 'SupportsSetItem', + 'SupportsDelItem', +) + +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: ... diff --git a/ufpy/udict.py b/ufpy/udict.py index c3d65a5..436b2b5 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -2,7 +2,7 @@ from .cmp import cmp_generator from .i import i_generator -from .utils import set_items_for_several_keys, get_items_for_several_keys +from .utils import set_items_for_several_keys, get_items_for_several_keys, del_items_for_several_keys __all__ = ( 'UDict', @@ -32,7 +32,7 @@ def dictionary(self) -> dict[KT, VT]: return self.__dict @dictionary.setter - def dictionary(self, value: "dict[KT, VT] | UDict"): + def dictionary(self, value: 'dict[KT, VT] | UDict'): if isinstance(value, UDict): value = value.dictionary self.__dict = value @@ -46,27 +46,31 @@ def default(self) -> VT: def default(self, value: VT): self.__default = value - # Reverse - def reverse(self) -> "UDict[KT, VT]": + # reverse + def reverse(self) -> 'UDict[KT, VT]': + self.__dict = self.reversed().__dict + return self + + def reversed(self) -> 'UDict[KT, VT]': keys, values = list(self.__dict.keys())[::-1], list(self.__dict.values())[::-1] - self.__dict = dict(list(zip(keys, values))) - return UDict(self.__dict) + return UDict(dict(list(zip(keys, values)))) - def __neg__(self) -> "UDict[KT, VT]": - d = self - return d.reverse() + def __neg__(self) -> 'UDict[KT, VT]': + return self.reversed() - # Get items + # 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: 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] | VT": + def __getitem__(self, key: KT | int | slice) -> 'UDict[KT, VT] | VT': keys = self.__get_keys_from_slice_or_int(key) l = get_items_for_several_keys(self.__dict, keys, self.__default) @@ -81,10 +85,12 @@ def __setitem__(self, key: KT | int | slice, value: VT | list[VT]) -> None: self.__dict = set_items_for_several_keys(self.__dict, keys, values) + def __delitem__(self, key: KT | int | slice): + keys = self.__get_keys_from_slice_or_int(key) - # TODO: make __delitem__() - # def __delitem__(self, key: KT | int | slice): ... + self.__dict = del_items_for_several_keys(self.__dict, keys) + # get/set/del attrs # TODO: make __getattr__() # def __getattr__(self, name: str): ... @@ -105,10 +111,15 @@ def __iter__(self) -> Iterator[tuple[KT, VT]]: res.append((k, v)) return iter(res) - - def __reversed__(self) -> 'UDict': ... + + def __reversed__(self) -> 'UDict[KT, VT]': + return self.reversed() # Booleans + def __nonzero__(self) -> bool: + return len(self) > 0 + + # TODO: make __contains__() def __contains__(self, item: tuple[KT, VT] | KT) -> bool: ... # Transform to other types @@ -116,16 +127,16 @@ def __repr__(self) -> str: return f'''u{self.__dict}''' # Comparing - def __cmp__(self, other: "dict[KT, VT] | UDict") -> int: + def __cmp__(self, other: 'dict[KT, VT] | UDict') -> int: return len(self) - len(other) - def __eq__(self, other: "dict[KT, VT] | UDict") -> bool: + def __eq__(self, other: 'dict[KT, VT] | UDict') -> bool: if isinstance(other, UDict): other = other.dictionary return self.__dict == other # Math operations - def __add__(self, other: "dict[KT, VT] | UDict") -> "UDict": + def __add__(self, other: 'dict[KT, VT] | UDict') -> 'UDict': new_dict = self.__dict if isinstance(other, UDict): @@ -135,7 +146,7 @@ def __add__(self, other: "dict[KT, VT] | UDict") -> "UDict": new_dict[k] = v return UDict(new_dict) - def __sub__(self, other: "dict[KT, VT] | UDict") -> "UDict": + def __sub__(self, other: 'dict[KT, VT] | UDict') -> 'UDict': new_dict = self.__dict if isinstance(other, UDict): @@ -146,7 +157,7 @@ def __sub__(self, other: "dict[KT, VT] | UDict") -> "UDict": del new_dict[k] return UDict(new_dict) - def __mul__(self, other: "dict[KT, float | int] | UDict[KT, float | int] | float | int") -> "UDict": + def __mul__(self, other: 'dict[KT, float | int] | UDict[KT, float | int] | float | int') -> 'UDict': new_dict = self.__dict if isinstance(other, UDict): @@ -159,7 +170,7 @@ def __mul__(self, other: "dict[KT, float | int] | UDict[KT, float | int] | float return UDict(new_dict) - def __truediv__(self, other: "dict[KT, float | int] | UDict[KT, float | int] | float | int") -> "UDict": + def __truediv__(self, other: 'dict[KT, float | int] | UDict[KT, float | int] | float | int') -> 'UDict': new_dict = self.__dict if isinstance(other, UDict): diff --git a/ufpy/utils.py b/ufpy/utils.py index 9487923..e8afaf6 100644 --- a/ufpy/utils.py +++ b/ufpy/utils.py @@ -1,11 +1,11 @@ __all__ = ( - 'SupportsGetItem', - 'SupportsGet', 'get_items_for_several_keys', 'set_items_for_several_keys', ) -from typing import Protocol, TypeVar, Generic, Type +from typing import TypeVar + +from .protocols import SupportsGet, SupportsSetItem, SupportsDelItem type AnyCollection[T] = tuple[T, ...] | list[T] @@ -14,26 +14,21 @@ 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: ... - - 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] -) -> Type[SupportsSetItem[KT, 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 From c252161feab085c7baf1a88393a07a8a809fe146 Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 25 May 2024 00:00:06 +0300 Subject: [PATCH 09/28] `__getattr__()` --- ufpy/udict.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ufpy/udict.py b/ufpy/udict.py index 436b2b5..b97a45f 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -91,8 +91,8 @@ def __delitem__(self, key: KT | int | slice): self.__dict = del_items_for_several_keys(self.__dict, keys) # get/set/del attrs - # TODO: make __getattr__() - # def __getattr__(self, name: str): ... + def __getattr__(self, name: str): + return self.__dict.get(name, self.__default) # TODO: make __getattr__() # def __setattr__(self, name: str, value: VT): ... @@ -120,7 +120,11 @@ def __nonzero__(self) -> bool: return len(self) > 0 # TODO: make __contains__() - def __contains__(self, item: tuple[KT, VT] | KT) -> bool: ... + 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 __repr__(self) -> str: From 24c4ae727176655bc627d9008e771bfb7ef5a423 Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 25 May 2024 00:53:35 +0300 Subject: [PATCH 10/28] Revert get/set/del attr methods. --- ufpy/udict.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ufpy/udict.py b/ufpy/udict.py index b97a45f..a4e0d97 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -90,16 +90,6 @@ def __delitem__(self, key: KT | int | slice): self.__dict = del_items_for_several_keys(self.__dict, keys) - # get/set/del attrs - def __getattr__(self, name: str): - return self.__dict.get(name, self.__default) - - # TODO: make __getattr__() - # def __setattr__(self, name: str, value: VT): ... - - # TODO: make __delattr__() - # def __delattr__(self, name: str): ... - # Len, iterator and reversed version def __len__(self) -> int: return len(self.__dict.keys()) From 2c6d3ff1c0654025334ba3db6e6d9c2ef306625c Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 25 May 2024 01:18:33 +0300 Subject: [PATCH 11/28] Many changes --- ufpy/protocols.py | 2 ++ ufpy/udict.py | 57 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/ufpy/protocols.py b/ufpy/protocols.py index 3e06d7c..9e92718 100644 --- a/ufpy/protocols.py +++ b/ufpy/protocols.py @@ -11,6 +11,8 @@ VT = TypeVar('VT') DV = TypeVar('DV') +T = TypeVar('T') + class SupportsGetItem(Protocol, Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... diff --git a/ufpy/udict.py b/ufpy/udict.py index a4e0d97..9c4a637 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -1,4 +1,4 @@ -from typing import Generic, Iterator, overload, TypeVar +from typing import Generic, Iterator, overload, TypeVar, Iterable from .cmp import cmp_generator from .i import i_generator @@ -36,6 +36,31 @@ def dictionary(self, value: 'dict[KT, VT] | UDict'): 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: Iterable[KT]): + values = list(self.__dict.values()) + self.__dict = dict(list(zip(value, values))) + + # values + @property + def values(self) -> list[VT]: + return list(self.__dict.values()) + + @values.setter + def values(self, value: Iterable[VT]): + keys = list(self.__dict.keys()) + self.__dict = dict(list(zip(keys, value))) + + # items + @property + def items(self) -> list[tuple[KT, VT]]: + return list(zip(self.keys, self.values)) # default @property @@ -58,6 +83,9 @@ def reversed(self) -> 'UDict[KT, VT]': def __neg__(self) -> 'UDict[KT, VT]': return self.reversed() + def __reversed__(self) -> 'UDict[KT, VT]': + return self.reversed() + # 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: @@ -92,24 +120,15 @@ def __delitem__(self, key: KT | int | slice): # Len, iterator and reversed version def __len__(self) -> int: - return len(self.__dict.keys()) + return len(self.items) def __iter__(self) -> Iterator[tuple[KT, VT]]: - res = [] - - for k, v in self.__dict.items(): - res.append((k, v)) - - return iter(res) - - def __reversed__(self) -> 'UDict[KT, VT]': - return self.reversed() + return iter(self.items) # Booleans def __nonzero__(self) -> bool: return len(self) > 0 - # TODO: make __contains__() def __contains__(self, item: tuple[KT, VT] | list[KT | VT] | KT) -> bool: if isinstance(item, (list, tuple)): k, v = item @@ -117,8 +136,14 @@ def __contains__(self, item: tuple[KT, VT] | list[KT | VT] | KT) -> bool: 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') -> int: @@ -134,7 +159,7 @@ def __add__(self, other: 'dict[KT, VT] | UDict') -> 'UDict': new_dict = self.__dict if isinstance(other, UDict): - other = other.dictionary + other: dict[KT, VT] = other.dictionary for k, v in other.items(): new_dict[k] = v @@ -144,7 +169,7 @@ def __sub__(self, other: 'dict[KT, VT] | UDict') -> 'UDict': new_dict = self.__dict if isinstance(other, UDict): - other = other.dictionary + other: dict[KT, VT] = other.dictionary for k, v in other.items(): if new_dict.get(k) == v: @@ -155,7 +180,7 @@ def __mul__(self, other: 'dict[KT, float | int] | UDict[KT, float | int] | float new_dict = self.__dict if isinstance(other, UDict): - other = other.dictionary + other: dict[KT, VT] = other.dictionary if isinstance(other, (int, float)): other = dict([(k, other) for k in new_dict.keys()]) @@ -168,7 +193,7 @@ def __truediv__(self, other: 'dict[KT, float | int] | UDict[KT, float | int] | f new_dict = self.__dict if isinstance(other, UDict): - other = other.dictionary + other: dict[KT, VT] = other.dictionary if isinstance(other, (int, float)): other = dict([(k, other) for k in new_dict.keys()]) From e3017174774df55f13535cce09625a064c4617c1 Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 25 May 2024 12:20:41 +0300 Subject: [PATCH 12/28] Final UDict --- ufpy/__init__.py | 2 +- ufpy/{i.py => math_op.py} | 33 ++++++++++++++++++++- ufpy/udict.py | 61 ++++++++++++++++++++++++++------------- 3 files changed, 74 insertions(+), 22 deletions(-) rename ufpy/{i.py => math_op.py} (50%) diff --git a/ufpy/__init__.py b/ufpy/__init__.py index 290010f..ba61b7c 100644 --- a/ufpy/__init__.py +++ b/ufpy/__init__.py @@ -1,6 +1,6 @@ __version__ = '1.0-alpha' from .cmp import * -from .i import * +from .math_op import * from .udict import * from .utils import * diff --git a/ufpy/i.py b/ufpy/math_op.py similarity index 50% rename from ufpy/i.py rename to ufpy/math_op.py index 2ee37e0..32c9a81 100644 --- a/ufpy/i.py +++ b/ufpy/math_op.py @@ -1,11 +1,12 @@ __all__ = ( 'i_generator', + 'r_generator', ) from typing import Type, TypeVar -T = TypeVar('T', bound=type) +T = TypeVar('T') def i_generator(t: Type[T]) -> Type[T]: if '__add__' in t.__dict__: @@ -36,3 +37,33 @@ def i_generator(t: Type[T]) -> Type[T]: 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/udict.py b/ufpy/udict.py index 9c4a637..1762a02 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -1,7 +1,7 @@ -from typing import Generic, Iterator, overload, TypeVar, Iterable +from typing import Generic, Iterator, overload, TypeVar, Iterable, Callable from .cmp import cmp_generator -from .i import i_generator +from .math_op import i_generator, r_generator from .utils import set_items_for_several_keys, get_items_for_several_keys, del_items_for_several_keys __all__ = ( @@ -10,18 +10,20 @@ KT = TypeVar('KT') VT = TypeVar('VT') +DV = TypeVar('DV') @cmp_generator @i_generator -class UDict(Generic[KT, VT]): +@r_generator +class UDict(Generic[KT, VT, DV]): @overload def __init__(self, dictionary: dict[KT, VT]): ... @overload - def __init__(self, dictionary: dict[KT, VT], *, default: VT): ... + def __init__(self, dictionary: dict[KT, VT], *, default: DV): ... @overload def __init__(self, **kwargs: VT): ... @overload - def __init__(self, *, default: VT, **kwargs: VT): ... + def __init__(self, *, default: DV, **kwargs: VT): ... def __init__(self, dictionary = None, *, default = None, **kwargs): self.__dict = dictionary if dictionary is not None else kwargs self.__default = default @@ -32,7 +34,7 @@ def dictionary(self) -> dict[KT, VT]: return self.__dict @dictionary.setter - def dictionary(self, value: 'dict[KT, VT] | UDict'): + def dictionary(self, value: 'dict[KT, VT] | UDict[KT, VT]'): if isinstance(value, UDict): value = value.dictionary self.__dict = value @@ -64,28 +66,45 @@ def items(self) -> list[tuple[KT, VT]]: # default @property - def default(self) -> VT: + def default(self) -> DV: return self.__default @default.setter - def default(self, value: VT): + def default(self, value: DV): self.__default = value + + # call + def __call__(self, func: Callable[[KT, VT], VT]) -> 'UDict[KT, VT, DV]': + new_dict = self.__dict + for k, v in self: + new_dict[k] = func(k, v) + return UDict(new_dict, default=self.__default) # reverse - def reverse(self) -> 'UDict[KT, VT]': + def reverse(self) -> 'UDict[KT, VT, DV]': self.__dict = self.reversed().__dict return self - def reversed(self) -> 'UDict[KT, VT]': + def reversed(self) -> 'UDict[KT, VT, DV]': keys, values = list(self.__dict.keys())[::-1], list(self.__dict.values())[::-1] return UDict(dict(list(zip(keys, values)))) - def __neg__(self) -> 'UDict[KT, VT]': + def __neg__(self) -> 'UDict[KT, VT, DV]': return self.reversed() - def __reversed__(self) -> 'UDict[KT, VT]': + def __reversed__(self) -> 'UDict[KT, VT, DV]': return self.reversed() + # sort + def sort(self) -> 'UDict[KT, VT, DV]': + self.__dict = self.sorted().__dict + return self + + def sorted(self) -> 'UDict[KT, VT, DV]': + 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: @@ -98,7 +117,7 @@ def __get_keys_from_slice_or_int(self, key: KT | int | slice) -> list[KT]: return [list(self.__dict.keys())[i - 1] for i in indexes] return [key] - def __getitem__(self, key: KT | int | slice) -> 'UDict[KT, VT] | VT': + 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) @@ -113,7 +132,7 @@ def __setitem__(self, key: KT | int | slice, value: VT | list[VT]) -> None: self.__dict = set_items_for_several_keys(self.__dict, keys, values) - def __delitem__(self, key: KT | int | slice): + 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) @@ -146,16 +165,16 @@ def __hash__(self) -> int: return hash(self.__repr__()) # Comparing - def __cmp__(self, other: 'dict[KT, VT] | UDict') -> int: + def __cmp__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> int: return len(self) - len(other) - def __eq__(self, other: 'dict[KT, VT] | UDict') -> bool: + def __eq__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> bool: if isinstance(other, UDict): other = other.dictionary return self.__dict == other # Math operations - def __add__(self, other: 'dict[KT, VT] | UDict') -> 'UDict': + def __add__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> 'UDict[KT, VT, DV]': new_dict = self.__dict if isinstance(other, UDict): @@ -165,7 +184,7 @@ def __add__(self, other: 'dict[KT, VT] | UDict') -> 'UDict': new_dict[k] = v return UDict(new_dict) - def __sub__(self, other: 'dict[KT, VT] | UDict') -> 'UDict': + def __sub__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> 'UDict[KT, VT, DV]': new_dict = self.__dict if isinstance(other, UDict): @@ -176,7 +195,7 @@ def __sub__(self, other: 'dict[KT, VT] | UDict') -> 'UDict': del new_dict[k] return UDict(new_dict) - def __mul__(self, other: 'dict[KT, float | int] | UDict[KT, float | int] | float | int') -> 'UDict': + def __mul__(self, other: 'dict[KT, float | int] | UDict[KT, float | int, DV] | float | int') -> 'UDict[KT, VT, DV]': new_dict = self.__dict if isinstance(other, UDict): @@ -189,7 +208,9 @@ def __mul__(self, other: 'dict[KT, float | int] | UDict[KT, float | int] | float return UDict(new_dict) - def __truediv__(self, other: 'dict[KT, float | int] | UDict[KT, float | int] | float | int') -> 'UDict': + def __truediv__( + self, other: 'dict[KT, float | int] | UDict[KT, float | int, DV] | float | int' + ) -> 'UDict[KT, VT, DV]': new_dict = self.__dict if isinstance(other, UDict): From b6fc7291e922a47f061c2d7fdbe6fbbfe0c1cae9 Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 25 May 2024 13:57:07 +0300 Subject: [PATCH 13/28] Add tests --- tests/__init__.py | 0 tests/test_udict.py | 79 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/test_udict.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_udict.py b/tests/test_udict.py new file mode 100644 index 0000000..f656c1e --- /dev/null +++ b/tests/test_udict.py @@ -0,0 +1,79 @@ +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}) + + +if __name__ == '__main__': + unittest.main() From e3760d8c6cf2caa343fd80132d567a83acf4d12b Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 25 May 2024 20:32:35 +0300 Subject: [PATCH 14/28] Adding examples part 1 --- examples/.ipynb | 24 ------------------------ examples/udict.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 24 deletions(-) delete mode 100644 examples/.ipynb create mode 100644 examples/udict.md diff --git a/examples/.ipynb b/examples/.ipynb deleted file mode 100644 index bafd298..0000000 --- a/examples/.ipynb +++ /dev/null @@ -1,24 +0,0 @@ -{ - "cells": [], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/udict.md b/examples/udict.md new file mode 100644 index 0000000..e2c1624 --- /dev/null +++ b/examples/udict.md @@ -0,0 +1,48 @@ +# 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 +(warning: in this class first index is 1): +```python +d[1] # 2 +d[:] # u{'id': 2, 'content': 'hello, world'} (UDict object) +``` + +## 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 +``` \ No newline at end of file From ceead0bd9eae14c0e991c778ac1b078547ec5515 Mon Sep 17 00:00:00 2001 From: bleudev Date: Tue, 28 May 2024 19:22:38 +0300 Subject: [PATCH 15/28] get() and tests for get() --- tests/test_udict.py | 14 ++++++++++++++ ufpy/udict.py | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tests/test_udict.py b/tests/test_udict.py index f656c1e..d4c3b9f 100644 --- a/tests/test_udict.py +++ b/tests/test_udict.py @@ -74,6 +74,20 @@ def test_set_item(self): d[:] = [1, 2] self.assertDictEqual(d.dictionary, {'hello': 1, 'hi': 2}) + def test_get(self): + d = UDict({2: 1, 4: 91, 1: 12}) + 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)) + + # TODO: test_del_item + # TODO: test_len_and_iter + # TODO: test_bool + # TODO: test_contains + # TODO: test_str_and_repr + # TODO: test_cmp_and_eq + # TODO: test_math_operations + if __name__ == '__main__': unittest.main() diff --git a/ufpy/udict.py b/ufpy/udict.py index 1762a02..a2f6e94 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -137,6 +137,23 @@ def __delitem__(self, key: KT | int | slice) -> None: self.__dict = del_items_for_several_keys(self.__dict, keys) + # get + def get(self, *, key: KT | None = None, index: int | None = None) -> VT | DV: + """ + Get a value with key or it's index. + + :param key: Key of value in dict (optional) + :param index: Index of value in dict (optional) + :return: Value or default value + + :exception ValueError: You defined both key and index 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 index > len(self): + raise IndexError('Index is bigger that length of UDict.') + return self[self.keys[index-1]] if index else self[key] + # Len, iterator and reversed version def __len__(self) -> int: return len(self.items) From 5b2db39f4d948e97eeaa2c55fb202c35fbb33be8 Mon Sep 17 00:00:00 2001 From: bleudev Date: Tue, 28 May 2024 19:34:15 +0300 Subject: [PATCH 16/28] add __invert__() (__invert__ = __reversed__) and change __neg__ --- tests/test_udict.py | 3 ++- ufpy/udict.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_udict.py b/tests/test_udict.py index d4c3b9f..71c030c 100644 --- a/tests/test_udict.py +++ b/tests/test_udict.py @@ -40,7 +40,7 @@ def test_reverse(self): self.assertDictEqual(d2, ud.reversed().dictionary) self.assertEqual(reversed(ud), ud.reversed()) - self.assertEqual(-ud, ud.reverse()) + self.assertEqual(~ud, ud.reverse()) def test_sort(self): d = {'hi': 2, 'hello': 1} @@ -87,6 +87,7 @@ def test_get(self): # TODO: test_str_and_repr # TODO: test_cmp_and_eq # TODO: test_math_operations + # TODO: test_neg if __name__ == '__main__': diff --git a/ufpy/udict.py b/ufpy/udict.py index a2f6e94..a5a305d 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -79,6 +79,10 @@ def __call__(self, func: Callable[[KT, VT], VT]) -> 'UDict[KT, VT, DV]': 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, DV]': + return self(lambda k, v: -v) # reverse def reverse(self) -> 'UDict[KT, VT, DV]': @@ -88,8 +92,8 @@ def reverse(self) -> 'UDict[KT, VT, DV]': def reversed(self) -> 'UDict[KT, VT, DV]': keys, values = list(self.__dict.keys())[::-1], list(self.__dict.values())[::-1] return UDict(dict(list(zip(keys, values)))) - - def __neg__(self) -> 'UDict[KT, VT, DV]': + + def __invert__(self) -> 'UDict[KT, VT, DV]': return self.reversed() def __reversed__(self) -> 'UDict[KT, VT, DV]': From 78c3ee4a67bb540d90a9d441a25cb51191c07553 Mon Sep 17 00:00:00 2001 From: bleudev Date: Tue, 28 May 2024 19:50:43 +0300 Subject: [PATCH 17/28] Example for `get()` --- examples/udict.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/udict.md b/examples/udict.md index e2c1624..fc4ef48 100644 --- a/examples/udict.md +++ b/examples/udict.md @@ -39,6 +39,17 @@ 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, +and you can get item by its index using `index` kwarg. +Example: +```python +d = UDict({2: 3, 1: 4}) +d[2] # 3 +d.get(index=2) # 4 +d.get(key=1) # also 4 +``` + ## Set items For setting items you should use the way you use in lists and dicts: From d4006230785a1e042d49247423abe45350ecd1e2 Mon Sep 17 00:00:00 2001 From: bleudev Date: Tue, 28 May 2024 19:59:58 +0300 Subject: [PATCH 18/28] Delete .md --- examples/.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/.md diff --git a/examples/.md b/examples/.md deleted file mode 100644 index e69de29..0000000 From 085effe1d1620f525da4ef6057db7ec662fce9df Mon Sep 17 00:00:00 2001 From: bleudev Date: Tue, 28 May 2024 20:55:22 +0300 Subject: [PATCH 19/28] final tests + fix math operations --- tests/test_udict.py | 69 +++++++++++++++++++++++++++++++++++++++------ ufpy/udict.py | 8 +++--- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/tests/test_udict.py b/tests/test_udict.py index 71c030c..cc0ba55 100644 --- a/tests/test_udict.py +++ b/tests/test_udict.py @@ -74,20 +74,73 @@ def test_set_item(self): 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}) + def test_get(self): d = UDict({2: 1, 4: 91, 1: 12}) 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)) - # TODO: test_del_item - # TODO: test_len_and_iter - # TODO: test_bool - # TODO: test_contains - # TODO: test_str_and_repr - # TODO: test_cmp_and_eq - # TODO: test_math_operations - # TODO: test_neg + 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_bool(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.assertTrue(('hi', 2) 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__': diff --git a/ufpy/udict.py b/ufpy/udict.py index a5a305d..64777bf 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -196,7 +196,7 @@ def __eq__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> bool: # Math operations def __add__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> 'UDict[KT, VT, DV]': - new_dict = self.__dict + new_dict = self.__dict.copy() if isinstance(other, UDict): other: dict[KT, VT] = other.dictionary @@ -206,7 +206,7 @@ def __add__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> 'UDict[KT, VT, D return UDict(new_dict) def __sub__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> 'UDict[KT, VT, DV]': - new_dict = self.__dict + new_dict = self.__dict.copy() if isinstance(other, UDict): other: dict[KT, VT] = other.dictionary @@ -217,7 +217,7 @@ def __sub__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> 'UDict[KT, VT, D return UDict(new_dict) def __mul__(self, other: 'dict[KT, float | int] | UDict[KT, float | int, DV] | float | int') -> 'UDict[KT, VT, DV]': - new_dict = self.__dict + new_dict = self.__dict.copy() if isinstance(other, UDict): other: dict[KT, VT] = other.dictionary @@ -232,7 +232,7 @@ def __mul__(self, other: 'dict[KT, float | int] | UDict[KT, float | int, DV] | f def __truediv__( self, other: 'dict[KT, float | int] | UDict[KT, float | int, DV] | float | int' ) -> 'UDict[KT, VT, DV]': - new_dict = self.__dict + new_dict = self.__dict.copy() if isinstance(other, UDict): other: dict[KT, VT] = other.dictionary From e91f7ffd276dec8b9ef413e654d242806db11cce Mon Sep 17 00:00:00 2001 From: bleudev Date: Tue, 28 May 2024 20:59:37 +0300 Subject: [PATCH 20/28] fix __repr__ and fix get items (IndexError is raising when key == 0) --- ufpy/udict.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ufpy/udict.py b/ufpy/udict.py index 64777bf..ea9db2b 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -112,6 +112,8 @@ def sorted(self) -> 'UDict[KT, VT, DV]': # 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") return [list(self.__dict.keys())[key - 1]] if isinstance(key, slice): start, stop, step = key.indices(len(self) + 1) @@ -180,7 +182,7 @@ def __str__(self) -> str: return str(self.__dict) def __repr__(self) -> str: - return f'''u{self.__dict}''' + return f'u{self.__dict}' def __hash__(self) -> int: return hash(self.__repr__()) From 6185757e292c4ace06d4c84b1b3a9a7b3a455c2e Mon Sep 17 00:00:00 2001 From: bleudev Date: Wed, 29 May 2024 11:47:17 +0300 Subject: [PATCH 21/28] add get.value param --- tests/test_udict.py | 4 ++++ ufpy/udict.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tests/test_udict.py b/tests/test_udict.py index cc0ba55..bbdd55b 100644 --- a/tests/test_udict.py +++ b/tests/test_udict.py @@ -85,6 +85,10 @@ def test_get(self): 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) + def test_len_and_iter(self): d = UDict(hello=1, hi=2) self.assertEqual(len(d), 2) diff --git a/ufpy/udict.py b/ufpy/udict.py index ea9db2b..45876a9 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -144,20 +144,50 @@ def __delitem__(self, key: KT | int | slice) -> None: self.__dict = del_items_for_several_keys(self.__dict, keys) # get - def get(self, *, key: KT | None = None, index: int | None = None) -> VT | DV: + @overload + def get(self, *, key: KT) -> VT | DV: ... + @overload + def get(self, *, index: int) -> VT | DV: ... + @overload + def get(self, *, value: VT) -> KT: ... + def get(self, *, key: KT | None = None, index: int | None = None, value: VT | None = None) -> KT | VT | 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) :return: Value or default value - :exception ValueError: You defined both key and index params + :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 value: + i = self.values.index(value) + return self.keys[i] return self[self.keys[index-1]] if index else self[key] # Len, iterator and reversed version From 86f36c55fb2e4a133ee9cf6ea7f581214a180da3 Mon Sep 17 00:00:00 2001 From: bleudev Date: Fri, 31 May 2024 23:42:26 +0300 Subject: [PATCH 22/28] Big update --- examples/udict.md | 16 +++++++++++++--- ufpy/typ/__init__.py | 2 ++ ufpy/{ => typ}/protocols.py | 12 +++++++++++- ufpy/typ/type_alias.py | 9 +++++++++ ufpy/udict.py | 30 ++++++++++++++++++------------ ufpy/utils.py | 5 ++--- 6 files changed, 55 insertions(+), 19 deletions(-) create mode 100644 ufpy/typ/__init__.py rename ufpy/{ => typ}/protocols.py (77%) create mode 100644 ufpy/typ/type_alias.py diff --git a/examples/udict.md b/examples/udict.md index fc4ef48..6e4c5a9 100644 --- a/examples/udict.md +++ b/examples/udict.md @@ -32,8 +32,11 @@ use `UDict[key]` syntax: d['id'] # 2 ``` -You can also use index of key or slice of indexes of keys -(warning: in this class first index is 1): +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) @@ -56,4 +59,11 @@ For setting items you should use the way you use in lists and dicts: use `UDict[key] = value` syntax: ```python d['id'] = 3 -``` \ No newline at end of file +``` + +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 +``` 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/protocols.py b/ufpy/typ/protocols.py similarity index 77% rename from ufpy/protocols.py rename to ufpy/typ/protocols.py index 9e92718..16d99ef 100644 --- a/ufpy/protocols.py +++ b/ufpy/typ/protocols.py @@ -3,6 +3,7 @@ 'SupportsGet', 'SupportsSetItem', 'SupportsDelItem', + 'LikeDict', ) from typing import Protocol, Generic, TypeVar @@ -11,7 +12,6 @@ VT = TypeVar('VT') DV = TypeVar('DV') -T = TypeVar('T') class SupportsGetItem(Protocol, Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... @@ -27,3 +27,13 @@ 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 index 45876a9..6befe5f 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -1,7 +1,8 @@ -from typing import Generic, Iterator, overload, TypeVar, Iterable, Callable +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__ = ( @@ -17,15 +18,18 @@ @r_generator class UDict(Generic[KT, VT, DV]): @overload - def __init__(self, dictionary: dict[KT, VT]): ... + def __init__(self, dictionary: AnyDict[KT, VT]): ... @overload - def __init__(self, dictionary: dict[KT, VT], *, default: DV): ... + def __init__(self, dictionary: AnyDict[KT, VT], *, default: DV): ... @overload def __init__(self, **kwargs: VT): ... @overload def __init__(self, *, default: DV, **kwargs: VT): ... - def __init__(self, dictionary = None, *, default = None, **kwargs): - self.__dict = dictionary if dictionary is not None else kwargs + + def __init__(self, dictionary: AnyDict[KT, VT] = None, *, default: DV = None, **kwargs: VT): + if isinstance(dictionary, UDict): + dictionary = dictionary.dictionary + self.__dict = dictionary if dictionary else kwargs self.__default = default # dictionary @@ -34,7 +38,7 @@ def dictionary(self) -> dict[KT, VT]: return self.__dict @dictionary.setter - def dictionary(self, value: 'dict[KT, VT] | UDict[KT, VT]'): + def dictionary(self, value: AnyDict[KT, VT]): if isinstance(value, UDict): value = value.dictionary self.__dict = value @@ -45,9 +49,8 @@ def keys(self) -> list[KT]: return list(self.__dict.keys()) @keys.setter - def keys(self, value: Iterable[KT]): - values = list(self.__dict.values()) - self.__dict = dict(list(zip(value, values))) + def keys(self, value: AnyCollection[KT]): + self.__dict = dict(list(zip(value, self.values))) # values @property @@ -55,14 +58,17 @@ def values(self) -> list[VT]: return list(self.__dict.values()) @values.setter - def values(self, value: Iterable[VT]): - keys = list(self.__dict.keys()) - self.__dict = dict(list(zip(keys, value))) + 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 diff --git a/ufpy/utils.py b/ufpy/utils.py index e8afaf6..a975752 100644 --- a/ufpy/utils.py +++ b/ufpy/utils.py @@ -1,13 +1,12 @@ __all__ = ( 'get_items_for_several_keys', 'set_items_for_several_keys', + 'del_items_for_several_keys', ) from typing import TypeVar -from .protocols import SupportsGet, SupportsSetItem, SupportsDelItem - -type AnyCollection[T] = tuple[T, ...] | list[T] +from .typ import SupportsGet, SupportsSetItem, SupportsDelItem, AnyCollection KT = TypeVar('KT') VT = TypeVar('VT') From dd73b4015544cc6f94f4572fc02c09646d80d7dc Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 1 Jun 2024 20:12:11 +0300 Subject: [PATCH 23/28] Examples + fix tests --- examples/udict.md | 61 +++++++++++++++++++++++++++++++++++++++++++-- tests/test_udict.py | 2 ++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/examples/udict.md b/examples/udict.md index 6e4c5a9..990490b 100644 --- a/examples/udict.md +++ b/examples/udict.md @@ -44,13 +44,15 @@ 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, -and you can get item by its index using `index` kwarg. +you can get item by its index using `index` kwarg, +and you get item key by its value using `value` kwarg. Example: ```python d = UDict({2: 3, 1: 4}) d[2] # 3 d.get(index=2) # 4 d.get(key=1) # also 4 +d.get(value=3) # 2 ``` ## Set items @@ -61,9 +63,64 @@ use `UDict[key] = value` syntax: d['id'] = 3 ``` -Also you can use indexes and slices when you are setting items: +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: + ... +``` diff --git a/tests/test_udict.py b/tests/test_udict.py index bbdd55b..af9cead 100644 --- a/tests/test_udict.py +++ b/tests/test_udict.py @@ -78,6 +78,8 @@ 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}) From a7026f4b5667bbb084047421684a86a146dc77f4 Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 1 Jun 2024 20:24:52 +0300 Subject: [PATCH 24/28] Rename `test_bool` to `test_nonzero` --- tests/test_udict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_udict.py b/tests/test_udict.py index af9cead..99977ab 100644 --- a/tests/test_udict.py +++ b/tests/test_udict.py @@ -103,7 +103,7 @@ def test_len_and_iter(self): ('hello', 1), ('hi', 2) ]) - def test_bool(self): + def test_nonzero(self): d = UDict(hello=1, hi=2) self.assertTrue(bool(d)) From 69af1b139d6eb535b2eeb6374be291dc7d22931a Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 1 Jun 2024 20:28:33 +0300 Subject: [PATCH 25/28] Create `is_empty()` method --- ufpy/udict.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ufpy/udict.py b/ufpy/udict.py index 6befe5f..24dbc09 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -204,8 +204,11 @@ 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 len(self) > 0 + return not self.is_empty() def __contains__(self, item: tuple[KT, VT] | list[KT | VT] | KT) -> bool: if isinstance(item, (list, tuple)): From 6bf2ae278a924a926a5ec4ee239c842b00e6e4a1 Mon Sep 17 00:00:00 2001 From: bleudev Date: Sat, 1 Jun 2024 21:40:02 +0300 Subject: [PATCH 26/28] Final examples + update test_contains! --- examples/udict.md | 171 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_udict.py | 4 ++ 2 files changed, 175 insertions(+) diff --git a/examples/udict.md b/examples/udict.md index 990490b..dfe6518 100644 --- a/examples/udict.md +++ b/examples/udict.md @@ -124,3 +124,174 @@ 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 index 99977ab..315c0fa 100644 --- a/tests/test_udict.py +++ b/tests/test_udict.py @@ -111,8 +111,12 @@ 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} From ce7dcb3611e0d448d6bdbd7f38bb5dc77f62c8b7 Mon Sep 17 00:00:00 2001 From: bleudev Date: Sun, 2 Jun 2024 13:09:13 +0300 Subject: [PATCH 27/28] default kwarg --- examples/udict.md | 8 ++++- tests/test_udict.py | 7 ++++- ufpy/udict.py | 77 ++++++++++++++++++++++++++++----------------- 3 files changed, 62 insertions(+), 30 deletions(-) diff --git a/examples/udict.md b/examples/udict.md index dfe6518..3033026 100644 --- a/examples/udict.md +++ b/examples/udict.md @@ -46,13 +46,19 @@ 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}) +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 diff --git a/tests/test_udict.py b/tests/test_udict.py index 315c0fa..f473428 100644 --- a/tests/test_udict.py +++ b/tests/test_udict.py @@ -82,7 +82,7 @@ def test_del_item(self): self.assertDictEqual(d.dictionary, {}) def test_get(self): - d = UDict({2: 1, 4: 91, 1: 12}) + 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)) @@ -91,6 +91,11 @@ def test_get(self): 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) diff --git a/ufpy/udict.py b/ufpy/udict.py index 24dbc09..a30e67e 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -11,22 +11,23 @@ KT = TypeVar('KT') VT = TypeVar('VT') +CDV = TypeVar('CDV') DV = TypeVar('DV') @cmp_generator @i_generator @r_generator -class UDict(Generic[KT, VT, DV]): +class UDict(Generic[KT, VT, CDV]): @overload def __init__(self, dictionary: AnyDict[KT, VT]): ... @overload - def __init__(self, dictionary: AnyDict[KT, VT], *, default: DV): ... + def __init__(self, dictionary: AnyDict[KT, VT], *, default: CDV): ... @overload def __init__(self, **kwargs: VT): ... @overload - def __init__(self, *, default: DV, **kwargs: VT): ... + def __init__(self, *, default: CDV, **kwargs: VT): ... - def __init__(self, dictionary: AnyDict[KT, VT] = None, *, default: DV = None, **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 @@ -72,45 +73,45 @@ def items(self, value: AnyCollection[tuple[KT, VT] | list[KT | VT]]): # default @property - def default(self) -> DV: + def default(self) -> CDV: return self.__default @default.setter - def default(self, value: DV): + def default(self, value: CDV): self.__default = value # call - def __call__(self, func: Callable[[KT, VT], VT]) -> 'UDict[KT, VT, DV]': + 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, DV]': + def __neg__(self) -> 'UDict[KT, VT, CDV]': return self(lambda k, v: -v) # reverse - def reverse(self) -> 'UDict[KT, VT, DV]': + def reverse(self) -> 'UDict[KT, VT, CDV]': self.__dict = self.reversed().__dict return self - def reversed(self) -> 'UDict[KT, VT, DV]': + 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, DV]': + def __invert__(self) -> 'UDict[KT, VT, CDV]': return self.reversed() - def __reversed__(self) -> 'UDict[KT, VT, DV]': + def __reversed__(self) -> 'UDict[KT, VT, CDV]': return self.reversed() # sort - def sort(self) -> 'UDict[KT, VT, DV]': + def sort(self) -> 'UDict[KT, VT, CDV]': self.__dict = self.sorted().__dict return self - def sorted(self) -> 'UDict[KT, VT, DV]': + 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)))) @@ -119,12 +120,14 @@ def sorted(self) -> 'UDict[KT, VT, DV]': 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") + 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 + 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] @@ -151,12 +154,22 @@ def __delitem__(self, key: KT | int | slice) -> None: # get @overload - def get(self, *, key: KT) -> VT | DV: ... + def get(self, *, key: KT) -> VT | CDV: ... @overload - def get(self, *, index: int) -> VT | DV: ... + def get(self, *, key: KT, default: DV) -> VT | DV: ... @overload - def get(self, *, value: VT) -> KT: ... - def get(self, *, key: KT | None = None, index: int | None = None, value: VT | None = None) -> KT | VT | DV: + 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 = None, index: int | None = None, value: VT | None = None, + default: DV | CDV = 'class default' + ) -> KT | VT | CDV | DV: """ Get a value with key or it's index. @@ -165,6 +178,7 @@ def get(self, *, key: KT | None = None, index: int | None = None, value: VT | No :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 @@ -191,10 +205,15 @@ def get(self, *, key: KT | None = None, index: int | None = None, value: VT | No if index and index > len(self): raise IndexError('Index is bigger that length of UDict.') + if default == 'class default': + default = self.__default + if value: + if value not in self.values: + return default i = self.values.index(value) return self.keys[i] - return self[self.keys[index-1]] if index else self[key] + 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: @@ -227,16 +246,16 @@ def __hash__(self) -> int: return hash(self.__repr__()) # Comparing - def __cmp__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> int: + 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, DV]') -> bool: + 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, DV]') -> 'UDict[KT, VT, DV]': + def __add__(self, other: 'dict[KT, VT] | UDict[KT, VT, CDV]') -> 'UDict[KT, VT, CDV]': new_dict = self.__dict.copy() if isinstance(other, UDict): @@ -246,7 +265,7 @@ def __add__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> 'UDict[KT, VT, D new_dict[k] = v return UDict(new_dict) - def __sub__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> 'UDict[KT, VT, DV]': + def __sub__(self, other: 'dict[KT, VT] | UDict[KT, VT, CDV]') -> 'UDict[KT, VT, CDV]': new_dict = self.__dict.copy() if isinstance(other, UDict): @@ -257,7 +276,9 @@ def __sub__(self, other: 'dict[KT, VT] | UDict[KT, VT, DV]') -> 'UDict[KT, VT, D 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, DV]': + 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): @@ -272,7 +293,7 @@ def __mul__(self, other: 'dict[KT, float | int] | UDict[KT, float | int, DV] | f def __truediv__( self, other: 'dict[KT, float | int] | UDict[KT, float | int, DV] | float | int' - ) -> 'UDict[KT, VT, DV]': + ) -> 'UDict[KT, VT, CDV]': new_dict = self.__dict.copy() if isinstance(other, UDict): From ab1ad1e0a1d700497655fdbdd4fdeee132de40fb Mon Sep 17 00:00:00 2001 From: bleudev Date: Sun, 2 Jun 2024 15:20:58 +0300 Subject: [PATCH 28/28] Fix default value of `default` kwarg --- ufpy/udict.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ufpy/udict.py b/ufpy/udict.py index a30e67e..4a6c094 100644 --- a/ufpy/udict.py +++ b/ufpy/udict.py @@ -14,6 +14,8 @@ CDV = TypeVar('CDV') DV = TypeVar('DV') +class _ClassDefault: ... + @cmp_generator @i_generator @r_generator @@ -167,8 +169,8 @@ def get(self, *, value: VT) -> KT | CDV: ... def get(self, *, value: VT, default: DV) -> KT | DV: ... def get( - self, *, key: KT | None = None, index: int | None = None, value: VT | None = None, - default: DV | CDV = 'class default' + 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. @@ -205,7 +207,7 @@ def get( if index and index > len(self): raise IndexError('Index is bigger that length of UDict.') - if default == 'class default': + if default == _ClassDefault: default = self.__default if value: