diff --git a/src/cpl_query/base/ordered_queryable.py b/src/cpl_query/base/ordered_queryable.py new file mode 100644 index 00000000..c7b3dbf7 --- /dev/null +++ b/src/cpl_query/base/ordered_queryable.py @@ -0,0 +1,34 @@ +from collections.abc import Callable + +from cpl_query.base.ordered_queryable_abc import OrderedQueryableABC +from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument +from cpl_query.iterable.iterable import Iterable + + +class OrderedQueryable(OrderedQueryableABC): + r"""Implementation of :class: `cpl_query.extension.Iterable` `cpl_query.extension.OrderedIterableABC` + """ + + def __init__(self, _t: type, _values: Iterable = None, _func: Callable = None): + OrderedQueryableABC.__init__(self, _t, _values, _func) + + def then_by(self: OrderedQueryableABC, _func: Callable) -> OrderedQueryableABC: + if self is None: + raise ArgumentNoneException(ExceptionArgument.list) + + if _func is None: + raise ArgumentNoneException(ExceptionArgument.func) + + self._funcs.append(_func) + + return OrderedQueryable(self.type, sorted(self, key=lambda *args: [f(*args) for f in self._funcs]), _func) + + def then_by_descending(self: OrderedQueryableABC, _func: Callable) -> OrderedQueryableABC: + if self is None: + raise ArgumentNoneException(ExceptionArgument.list) + + if _func is None: + raise ArgumentNoneException(ExceptionArgument.func) + + self._funcs.append(_func) + return OrderedQueryable(self.type, sorted(self, key=lambda *args: [f(*args) for f in self._funcs], reverse=True), _func) diff --git a/src/cpl_query/iterable/ordered_iterable_abc.py b/src/cpl_query/base/ordered_queryable_abc.py similarity index 69% rename from src/cpl_query/iterable/ordered_iterable_abc.py rename to src/cpl_query/base/ordered_queryable_abc.py index eba32559..f3ec56fc 100644 --- a/src/cpl_query/iterable/ordered_iterable_abc.py +++ b/src/cpl_query/base/ordered_queryable_abc.py @@ -2,20 +2,20 @@ from abc import abstractmethod from collections.abc import Callable from typing import Iterable -from cpl_query.iterable.iterable_abc import IterableABC +from cpl_query.base.queryable_abc import QueryableABC -class OrderedIterableABC(IterableABC): +class OrderedQueryableABC(QueryableABC): @abstractmethod - def __init__(self, _t: type, _func: Callable = None, _values: Iterable = None): - IterableABC.__init__(self, _t, _values) + def __init__(self, _t: type, _values: Iterable = None, _func: Callable = None): + QueryableABC.__init__(self, _t, _values) self._funcs: list[Callable] = [] if _func is not None: self._funcs.append(_func) @abstractmethod - def then_by(self, func: Callable) -> 'OrderedIterableABC': + def then_by(self, func: Callable) -> 'OrderedQueryableABC': r"""Sorts OrderedList in ascending order by function Parameter @@ -29,7 +29,7 @@ class OrderedIterableABC(IterableABC): pass @abstractmethod - def then_by_descending(self, func: Callable) -> 'OrderedIterableABC': + def then_by_descending(self, func: Callable) -> 'OrderedQueryableABC': r"""Sorts OrderedList in descending order by function Parameter diff --git a/src/cpl_query/base/queryable_abc.py b/src/cpl_query/base/queryable_abc.py index fa22ab83..117397c6 100644 --- a/src/cpl_query/base/queryable_abc.py +++ b/src/cpl_query/base/queryable_abc.py @@ -1,10 +1,19 @@ -from abc import abstractmethod, ABC from typing import Optional, Callable, Union +from cpl_query._helper import is_number +from cpl_query.base.sequence_abc import SequenceABC +from cpl_query.exceptions import InvalidTypeException, ArgumentNoneException, ExceptionArgument, IndexOutOfRangeException -class QueryableABC(ABC): - @abstractmethod +def _default_lambda(x: object): + return x + + +class QueryableABC(SequenceABC): + + def __init__(self, t: type = None, values: list = None): + SequenceABC.__init__(self, t, values) + def all(self, _func: Callable = None) -> bool: r"""Checks if every element of list equals result found by function @@ -17,9 +26,11 @@ class QueryableABC(ABC): ------- bool """ - pass + if _func is None: + _func = _default_lambda + + return self.count(_func) == self.count() - @abstractmethod def any(self, _func: Callable = None) -> bool: r"""Checks if list contains result found by function @@ -32,9 +43,11 @@ class QueryableABC(ABC): ------- bool """ - pass + if _func is None: + _func = _default_lambda + + return self.where(_func).count() > 0 - @abstractmethod def average(self, _func: Callable = None) -> Union[int, float, complex]: r"""Returns average value of list @@ -47,10 +60,12 @@ class QueryableABC(ABC): ------- Union[int, float, complex] """ - pass + if _func is None and not is_number(self.type): + raise InvalidTypeException() - @abstractmethod - def contains(self, value: object) -> bool: + return self.sum(_func) / self.count() + + def contains(self, _value: object) -> bool: r"""Checks if list contains value given by function Parameter @@ -62,9 +77,11 @@ class QueryableABC(ABC): ------- bool """ - pass + if _value is None: + raise ArgumentNoneException(ExceptionArgument.value) + + return self.where(lambda x: x == _value).count() > 0 - @abstractmethod def count(self, _func: Callable = None) -> int: r"""Returns length of list or count of found elements @@ -77,9 +94,11 @@ class QueryableABC(ABC): ------- int """ - pass + if _func is None: + return self.__len__() + + return self.where(_func).__len__() - @abstractmethod def distinct(self, _func: Callable = None) -> 'QueryableABC': r"""Returns list without redundancies @@ -92,39 +111,62 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + _func = _default_lambda - @abstractmethod - def element_at(self, index: int) -> any: + result = [] + known_values = [] + for element in self: + value = _func(element) + if value in known_values: + continue + + known_values.append(value) + result.append(element) + + return type(self)(self._type, result) + + def element_at(self, _index: int) -> any: r"""Returns element at given index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- - Value at index: any + Value at _index: any """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) - @abstractmethod - def element_at_or_default(self, index: int) -> Optional[any]: + result = self[_index] + if result is None: + raise IndexOutOfRangeException + + return result + + def element_at_or_default(self, _index: int) -> Optional[any]: r"""Returns element at given index or None Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- - Value at index: Optional[any] + Value at _index: Optional[any] """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) + + try: + return self[_index] + except IndexError: + return None - @abstractmethod def first(self) -> any: r"""Returns first element @@ -132,9 +174,11 @@ class QueryableABC(ABC): ------- First element of list: any """ - pass + if len(self) == 0: + raise IndexOutOfRangeException() + + return self[0] - @abstractmethod def first_or_default(self) -> any: r"""Returns first element or None @@ -142,9 +186,11 @@ class QueryableABC(ABC): ------- First element of list: Optional[any] """ - pass + if len(self) == 0: + return None + + return self[0] - @abstractmethod def for_each(self, _func: Callable = None): r"""Runs given function for each element of list @@ -153,7 +199,11 @@ class QueryableABC(ABC): func: :class: `Callable` function to call """ - pass + if _func is not None: + for element in self: + _func(element) + + return self def group_by(self, _func: Callable = None) -> 'QueryableABC': r"""Groups by func @@ -162,9 +212,23 @@ class QueryableABC(ABC): ------- Grouped list[list[any]]: any """ - pass + if _func is None: + _func = _default_lambda + groups = {} + + for v in self: + value = _func(v) + if v not in groups: + groups[value] = [] + + groups[value].append(v) + + v = [] + for g in groups.values(): + v.append(type(self)(None, g)) + x = type(self)(type(self), v) + return x - @abstractmethod def last(self) -> any: r"""Returns last element @@ -172,9 +236,11 @@ class QueryableABC(ABC): ------- Last element of list: any """ - pass + if len(self) == 0: + raise IndexOutOfRangeException() + + return self[len(self) - 1] - @abstractmethod def last_or_default(self) -> any: r"""Returns last element or None @@ -182,9 +248,11 @@ class QueryableABC(ABC): ------- Last element of list: Optional[any] """ - pass + if len(self) == 0: + return None + + return self[len(self) - 1] - @abstractmethod def max(self, _func: Callable = None) -> Union[int, float, complex]: r"""Returns the highest value @@ -197,19 +265,33 @@ class QueryableABC(ABC): ------- Union[int, float, complex] """ - pass + if _func is None and not is_number(self.type): + raise InvalidTypeException() - @abstractmethod - def median(self) -> Union[int, float]: + if _func is None: + _func = _default_lambda + + return _func(max(self, key=_func)) + + def median(self, _func=None) -> Union[int, float]: r"""Return the median value of data elements Returns ------- Union[int, float] """ - pass + if _func is None: + _func = _default_lambda + + result = self.order_by(_func).select(_func).to_list() + length = len(result) + i = int(length / 2) + return ( + result[i] + if length % 2 == 1 + else (float(result[i - 1]) + float(result[i])) / float(2) + ) - @abstractmethod def min(self, _func: Callable = None) -> Union[int, float, complex]: r"""Returns the lowest value @@ -222,9 +304,14 @@ class QueryableABC(ABC): ------- Union[int, float, complex] """ - pass + if _func is None and not is_number(self.type): + raise InvalidTypeException() + + if _func is None: + _func = _default_lambda + + return _func(min(self, key=_func)) - @abstractmethod def order_by(self, _func: Callable = None) -> 'QueryableABC': r"""Sorts elements by function in ascending order @@ -237,9 +324,12 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + _func = _default_lambda + + from cpl_query.base.ordered_queryable import OrderedQueryable + return OrderedQueryable(self.type, sorted(self, key=_func), _func) - @abstractmethod def order_by_descending(self, _func: Callable = None) -> 'QueryableABC': r"""Sorts elements by function in descending order @@ -252,9 +342,12 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + _func = _default_lambda + + from cpl_query.base.ordered_queryable import OrderedQueryable + return OrderedQueryable(self.type, sorted(self, key=_func, reverse=True), _func) - @abstractmethod def reverse(self) -> 'QueryableABC': r"""Reverses list @@ -262,29 +355,31 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + return type(self)(self._type, list(reversed(self))) - @abstractmethod - def select(self, _f: Callable) -> 'QueryableABC': + def select(self, _func: Callable) -> 'QueryableABC': r"""Formats each element of list to a given format Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + _func = _default_lambda - @abstractmethod - def select_many(self, _f: Callable) -> 'QueryableABC': + return type(self)(any, [_func(_o) for _o in self]) + + def select_many(self, _func: Callable) -> 'QueryableABC': r"""Flattens resulting lists to one Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + # The line below is pain. I don't understand anything of it... + # written on 09.11.2022 by Sven Heidemann + return type(self)(any, [_a for _o in self for _a in _func(_o)]) - @abstractmethod def single(self) -> any: r"""Returns one single element of list @@ -297,9 +392,13 @@ class QueryableABC(ABC): ArgumentNoneException: when argument is None Exception: when argument is None or found more than one element """ - pass + if len(self) > 1: + raise Exception('Found more than one element') + elif len(self) == 0: + raise Exception('Found no element') + + return self[0] - @abstractmethod def single_or_default(self) -> Optional[any]: r"""Returns one single element of list @@ -307,39 +406,48 @@ class QueryableABC(ABC): ------- Found value: Optional[any] """ - pass + if len(self) > 1: + raise Exception('Index out of range') + elif len(self) == 0: + return None - @abstractmethod - def skip(self, index: int) -> 'QueryableABC': + return self[0] + + def skip(self, _index: int) -> 'QueryableABC': r"""Skips all elements from index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) - @abstractmethod - def skip_last(self, index: int) -> 'QueryableABC': + return type(self)(self.type, values=self[_index:]) + + def skip_last(self, _index: int) -> 'QueryableABC': r"""Skips all elements after index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) + + index = len(self) - _index + return type(self)(self._type, self[:index]) - @abstractmethod def sum(self, _func: Callable = None) -> Union[int, float, complex]: r"""Sum of all values @@ -352,39 +460,54 @@ class QueryableABC(ABC): ------- Union[int, float, complex] """ - pass + if _func is None and not is_number(self.type): + raise InvalidTypeException() - @abstractmethod - def take(self, index: int) -> 'QueryableABC': + if _func is None: + _func = _default_lambda + + result = 0 + for x in self: + result += _func(x) + + return result + + def take(self, _index: int) -> 'QueryableABC': r"""Takes all elements from index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) - @abstractmethod - def take_last(self, index: int) -> 'QueryableABC': + return type(self)(self._type, self[:_index]) + + def take_last(self, _index: int) -> 'QueryableABC': r"""Takes all elements after index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + index = len(self) - _index + + if index >= len(self) or index < 0: + raise IndexOutOfRangeException() + + return type(self)(self._type, self[index:]) - @abstractmethod def where(self, _func: Callable = None) -> 'QueryableABC': r"""Select element by function @@ -397,4 +520,15 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + raise ArgumentNoneException(ExceptionArgument.func) + + if _func is None: + _func = _default_lambda + + result = [] + for element in self: + if _func(element): + result.append(element) + + return type(self)(self.type, result) diff --git a/src/cpl_query/base/sequence_abc.py b/src/cpl_query/base/sequence_abc.py new file mode 100644 index 00000000..8e3a01aa --- /dev/null +++ b/src/cpl_query/base/sequence_abc.py @@ -0,0 +1,86 @@ +from abc import ABC, abstractmethod +from itertools import islice + +from cpl_query.base.sequence_values import SequenceValues + + +class SequenceABC(ABC): + + @abstractmethod + def __init__(self, t: type = None, values: list = None): + ABC.__init__(self) + if values is None: + values = [] + + if t is None and len(values) > 0: + t = type(values[0]) + + if t is None: + t = any + + self._type = t + self._set_values(values) + + def __len__(self): + return len(self._values) + + def __iter__(self): + return iter(self._values) + + def next(self): + return next(self._values) + + def __next__(self): + return self.next() + + def __getitem__(self, n): + values = [x for x in self] + if isinstance(n, slice): + try: + return values[n] + except Exception as e: + raise e + + for i in range(len(values)): + if i == n: + return values[i] + + def __repr__(self): + return f'<{type(self).__name__} {list(self).__repr__()}>' + + @property + def type(self) -> type: + return self._type + + def _check_type(self, __object: any): + if self._type == any: + return + + if self._type is not None and type(__object) != self._type and not isinstance(type(__object), self._type) and not issubclass(type(__object), self._type): + raise Exception(f'Unexpected type: {type(__object)}\nExpected type: {self._type}') + + def _set_values(self, values: list): + self._values = SequenceValues(values, self._type) + + def to_list(self) -> list: + r"""Converts :class: `cpl_query.base.sequence_abc.SequenceABC` to :class: `list` + + Returns + ------- + :class: `list` + """ + return [x for x in self] + + @classmethod + def empty(cls) -> 'SequenceABC': + r"""Returns an empty sequence + + Returns + ------- + Sequence object that contains no elements + """ + return cls() + + @classmethod + def range(cls, start: int, length: int) -> 'SequenceABC': + return cls(int, list(range(start, length))) diff --git a/src/cpl_query/base/sequence_values.py b/src/cpl_query/base/sequence_values.py index 9e2a485e..6298a685 100644 --- a/src/cpl_query/base/sequence_values.py +++ b/src/cpl_query/base/sequence_values.py @@ -5,15 +5,13 @@ from cpl_query.exceptions import IndexOutOfRangeException class SequenceValues: - def __init__(self, data, _t: type): - if data is None: - data = [] - + def __init__(self, data: list, _t: type): if len(data) > 0: def type_check(_t: type, _l: list): - return all(isinstance(x, _t) for x in _l) + return all([_t == any or isinstance(x, _t) for x in _l]) if not type_check(_t, data): + print([type(x) for x in data]) raise Exception(f'Unexpected type\nExpected type: {_t}') if not hasattr(data, '__iter__'): @@ -30,7 +28,7 @@ class SequenceValues: def __iter__(self): i = 0 - while i < len(self): + while i < self._len(): yield next(self._cycle) i += 1 diff --git a/src/cpl_query/enumerable/__init__.py b/src/cpl_query/enumerable/__init__.py index 9bd1147e..f7ff2517 100644 --- a/src/cpl_query/enumerable/__init__.py +++ b/src/cpl_query/enumerable/__init__.py @@ -19,12 +19,9 @@ __version__ = '2022.10.0.post2' from collections import namedtuple - # imports: from .enumerable import Enumerable from .enumerable_abc import EnumerableABC -from .ordered_enumerable import OrderedEnumerable -from .ordered_enumerable_abc import OrderedEnumerableABC VersionInfo = namedtuple('VersionInfo', 'major minor micro') version_info = VersionInfo(major='2022', minor='10', micro='0.post2') diff --git a/src/cpl_query/enumerable/enumerable.py b/src/cpl_query/enumerable/enumerable.py index 81b3647b..7b99518a 100644 --- a/src/cpl_query/enumerable/enumerable.py +++ b/src/cpl_query/enumerable/enumerable.py @@ -1,9 +1,4 @@ -from typing import Union, Callable, Optional - -from cpl_query._helper import is_number from cpl_query.enumerable.enumerable_abc import EnumerableABC -from cpl_query.enumerable.ordered_enumerable_abc import OrderedEnumerableABC -from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument, InvalidTypeException, IndexOutOfRangeException def _default_lambda(x: object): @@ -14,294 +9,5 @@ class Enumerable(EnumerableABC): r"""Implementation of :class: `cpl_query.enumerable.enumerable_abc.EnumerableABC` """ - def __init__(self, t: type = None, values: Union[list, iter] = None): + def __init__(self, t: type = None, values: list = None): EnumerableABC.__init__(self, t, values) - - def all(self, _func: Callable = None) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - result = self.where(_func) - return len(result) == len(self) - - def any(self, _func: Callable = None) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - result = self.where(_func) - return len(result) > 0 - - def average(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return float(self.sum(_func)) / float(self.count()) - - def contains(self, _value: object) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _value is None: - raise ArgumentNoneException(ExceptionArgument.value) - - return self.where(lambda x: x == _value).count() > 0 - - def count(self, _func: Callable = None) -> int: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - return len(self) - - return len(self.where(_func)) - - def distinct(self, _func: Callable = None) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - result = Enumerable() - known_values = [] - for element in self: - value = _func(element) - if value in known_values: - continue - - known_values.append(value) - result.add(element) - - return result - - def element_at(self, _index: int) -> any: - self._values.reset() - while _index >= 0: - current = self.next() - if _index == 0: - return current - _index -= 1 - - def element_at_or_default(self, _index: int) -> any: - try: - return self.element_at(_index) - except IndexOutOfRangeException: - return None - - @staticmethod - def empty() -> 'EnumerableABC': - r"""Returns an empty enumerable - - Returns - ------- - Enumerable object that contains no elements - """ - return Enumerable() - - def first(self: EnumerableABC, _func=None) -> any: - if _func is not None: - return self.where(_func).element_at(0) - return self.element_at(0) - - def first_or_default(self: EnumerableABC, _func=None) -> Optional[any]: - if _func is not None: - return self.where(_func).element_at_or_default(0) - return self.element_at_or_default(0) - - def for_each(self, _func: Callable = None): - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - for element in self: - _func(element) - - def last(self: EnumerableABC) -> any: - return self.element_at(self.count() - 1) - - def last_or_default(self: EnumerableABC) -> Optional[any]: - return self.element_at_or_default(self.count() - 1) - - def max(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return _func(max(self, key=_func)) - - def median(self, _func=None) -> Union[int, float]: - if _func is None: - _func = _default_lambda - result = self.order_by(_func).select(_func).to_list() - length = len(result) - i = int(length / 2) - return ( - result[i] - if length % 2 == 1 - else (float(result[i - 1]) + float(result[i])) / float(2) - ) - - def min(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return _func(min(self, key=_func)) - - def order_by(self, _func: Callable = None) -> OrderedEnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - from cpl_query.enumerable.ordered_enumerable import OrderedEnumerable - return OrderedEnumerable(self.type, _func, sorted(self, key=_func)) - - def order_by_descending(self, _func: Callable = None) -> OrderedEnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - from cpl_query.enumerable.ordered_enumerable import OrderedEnumerable - return OrderedEnumerable(self.type, _func, sorted(self, key=_func, reverse=True)) - - @staticmethod - def range(start: int, length: int) -> 'EnumerableABC': - return Enumerable(int, range(start, length)) - - def reverse(self: EnumerableABC) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - return Enumerable(self.type, list(reversed(self.to_list()))) - - def select(self, _func: Callable = None) -> EnumerableABC: - if _func is None: - _func = _default_lambda - - _l = [_func(_o) for _o in self] - return Enumerable(self._type if len(_l) < 1 else type(_l[0]), _l) - - def select_many(self, _func: Callable = None) -> EnumerableABC: - if _func is None: - _func = _default_lambda - - # The line below is pain. I don't understand anything of the list comprehension... - # written on 09.11.2022 by Sven Heidemann - _l = [_a for _o in self for _a in _func(_o)] - return Enumerable(self._type if len(_l) < 1 else type(_l[0]), _l) - - def single(self: EnumerableABC) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if len(self) > 1: - raise IndexError('Found more than one element') - elif len(self) == 0: - raise IndexOutOfRangeException(f'{type(self).__name__} is empty') - - return self.element_at(0) - - def single_or_default(self: EnumerableABC) -> Optional[any]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if len(self) > 1: - raise IndexError('Found more than one element') - elif len(self) == 0: - return None - - return self.element_at(0) - - def skip(self, _index: int) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - _list = self.to_list() - - return Enumerable(self.type, _list[_index:]) - - def skip_last(self, _index: int) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - index = len(self) - _index - - return self.take(len(self) - _index) - - def take(self, _index: int) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - _list = self.to_list() - - return Enumerable(self.type, _list[:_index]) - - def take_last(self, _index: int) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - _list = self.to_list() - index = len(_list) - _index - - return self.skip(index) - - def sum(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return sum([_func(x) for x in self]) - - def where(self, _func: Callable = None) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - return Enumerable(self.type, list(filter(_func, self._values))) diff --git a/src/cpl_query/enumerable/enumerable_abc.py b/src/cpl_query/enumerable/enumerable_abc.py index ff587f1a..b59054d9 100644 --- a/src/cpl_query/enumerable/enumerable_abc.py +++ b/src/cpl_query/enumerable/enumerable_abc.py @@ -1,8 +1,6 @@ from abc import abstractmethod -from typing import Iterable from cpl_query.base.queryable_abc import QueryableABC -from cpl_query.base.sequence_values import SequenceValues class EnumerableABC(QueryableABC): @@ -11,81 +9,15 @@ class EnumerableABC(QueryableABC): @abstractmethod def __init__(self, t: type = None, values: list = None): - if t == any or t is None and values is not None: - t = type(values[0]) + QueryableABC.__init__(self, t, values) - self._type, self._values, self._remove_error_check = t, SequenceValues(values, t), True - - def __len__(self): - return len(self._values) - - def __iter__(self): - return iter(self._values) - - def next(self): - return next(self._values) - - def __next__(self): - return self.next() - - def __repr__(self): - return f'<{type(self).__name__} {list(self).__repr__()}>' - - @property - def type(self) -> type: - return self._type + self._remove_error_check = True def set_remove_error_check(self, _value: bool): r"""Set flag to check if element exists before removing """ self._remove_error_check = _value - def add(self, __object: object) -> None: - r"""Adds an element to the enumerable. - """ - if self._type is not None and type(__object) != self._type and not isinstance(type(__object), self._type) and not issubclass(type(__object), self._type): - raise Exception(f'Unexpected type: {type(__object)}\nExpected type: {self._type}') - - if len(self) == 0 and self._type is None: - self._type = type(__object) - - self._values = SequenceValues([*self._values, __object], self._type) - - def clear(self): - r"""Removes all elements - """ - del self._values - self._values = [] - - def extend(self, __list: Iterable) -> 'EnumerableABC': - r"""Adds elements of given list to enumerable - - Parameter - --------- - __enumerable: :class: `cpl_query.enumerable.enumerable_abc.EnumerableABC` - index - """ - self._values = SequenceValues([*self._values, *__list], self._type) - return self - - def remove(self, __object: object) -> None: - r"""Removes element from list - - Parameter - --------- - __object: :class:`object` - value - - Raises - --------- - `Element not found` when element does not exist. Check can be deactivated by calling .set_remove_error_check(False) - """ - if self._remove_error_check and __object not in self._values: - raise Exception('Element not found') - - # self._values.remove(__object) - self._values = SequenceValues([x for x in self.to_list() if x != __object], self._type) - def to_iterable(self) -> 'IterableABC': r"""Converts :class: `cpl_query.enumerable.enumerable_abc.EnumerableABC` to :class: `cpl_query.iterable.iterable_abc.IterableABC` @@ -95,12 +27,3 @@ class EnumerableABC(QueryableABC): """ from cpl_query.iterable.iterable import Iterable return Iterable(self._type, self.to_list()) - - def to_list(self) -> list: - r"""Converts :class: `cpl_query.base.sequence_abc.SequenceABC` to :class: `list` - - Returns - ------- - :class: `list` - """ - return [x for x in self] diff --git a/src/cpl_query/enumerable/ordered_enumerable.py b/src/cpl_query/enumerable/ordered_enumerable.py deleted file mode 100644 index 28628da0..00000000 --- a/src/cpl_query/enumerable/ordered_enumerable.py +++ /dev/null @@ -1,36 +0,0 @@ -from collections.abc import Callable -from typing import Iterable - -from cpl_query.enumerable.enumerable import Enumerable -from cpl_query.enumerable.ordered_enumerable_abc import OrderedEnumerableABC -from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument - - -class OrderedEnumerable(Enumerable, OrderedEnumerableABC): - r"""Implementation of :class: `cpl_query.extension.Enumerable` `cpl_query.extension.OrderedEnumerableABC` - """ - - def __init__(self, _t: type, _func: Callable = None, _values: Iterable = None): - Enumerable.__init__(self, _t) - OrderedEnumerableABC.__init__(self, _t, _func, _values) - - def then_by(self: OrderedEnumerableABC, _func: Callable) -> OrderedEnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - self._funcs.append(_func) - - return OrderedEnumerable(self.type, _func, sorted(self, key=lambda *args: [f(*args) for f in self._funcs])) - - def then_by_descending(self: OrderedEnumerableABC, _func: Callable) -> OrderedEnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - self._funcs.append(_func) - return OrderedEnumerable(self.type, _func, sorted(self, key=lambda *args: [f(*args) for f in self._funcs], reverse=True)) diff --git a/src/cpl_query/enumerable/ordered_enumerable_abc.py b/src/cpl_query/enumerable/ordered_enumerable_abc.py deleted file mode 100644 index ef83f07b..00000000 --- a/src/cpl_query/enumerable/ordered_enumerable_abc.py +++ /dev/null @@ -1,43 +0,0 @@ -from abc import abstractmethod -from collections.abc import Callable -from typing import Iterable - -from cpl_query.enumerable.enumerable_abc import EnumerableABC - - -class OrderedEnumerableABC(EnumerableABC): - - @abstractmethod - def __init__(self, _t: type, _func: Callable = None, _values: Iterable = None): - EnumerableABC.__init__(self, _t, _values) - self._funcs: list[Callable] = [] - if _func is not None: - self._funcs.append(_func) - - @abstractmethod - def then_by(self, func: Callable) -> 'OrderedEnumerableABC': - r"""Sorts OrderedList in ascending order by function - - Parameter - --------- - func: :class:`Callable` - - Returns - ------- - list of :class:`cpl_query.extension.OrderedEnumerableABC` - """ - pass - - @abstractmethod - def then_by_descending(self, func: Callable) -> 'OrderedEnumerableABC': - r"""Sorts OrderedList in descending order by function - - Parameter - --------- - func: :class:`Callable` - - Returns - ------- - list of :class:`cpl_query.extension.OrderedEnumerableABC` - """ - pass diff --git a/src/cpl_query/iterable/__init__.py b/src/cpl_query/iterable/__init__.py index d129a1c1..2f6ae095 100644 --- a/src/cpl_query/iterable/__init__.py +++ b/src/cpl_query/iterable/__init__.py @@ -23,8 +23,6 @@ from collections import namedtuple # imports: from .iterable_abc import IterableABC from .iterable import Iterable -from .ordered_iterable_abc import OrderedIterableABC -from .ordered_iterable import OrderedIterable VersionInfo = namedtuple('VersionInfo', 'major minor micro') version_info = VersionInfo(major='2022', minor='10', micro='0.post2') diff --git a/src/cpl_query/iterable/iterable.py b/src/cpl_query/iterable/iterable.py index 6f7ce73f..38367269 100644 --- a/src/cpl_query/iterable/iterable.py +++ b/src/cpl_query/iterable/iterable.py @@ -1,9 +1,6 @@ -from typing import Callable, Optional, Union, Iterable as IterableType +from typing import Iterable as IterableType -from cpl_query._helper import is_number -from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument, InvalidTypeException, IndexOutOfRangeException from cpl_query.iterable.iterable_abc import IterableABC -from cpl_query.iterable.ordered_iterable_abc import OrderedIterableABC def _default_lambda(x: object): @@ -14,251 +11,3 @@ class Iterable(IterableABC): def __init__(self, t: type = None, values: IterableType = None): IterableABC.__init__(self, t, values) - - def all(self, _func: Callable = None) -> bool: - if _func is None: - _func = _default_lambda - - return self.where(_func).count() == self.count() - - def any(self, _func: Callable = None) -> bool: - if _func is None: - _func = _default_lambda - - return self.where(_func).count() > 0 - - def average(self, _func: Callable = None) -> Union[int, float, complex]: - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - return self.sum(_func) / self.count() - - def contains(self, _value: object) -> bool: - if _value is None: - raise ArgumentNoneException(ExceptionArgument.value) - - return self.where(lambda x: x == _value).count() > 0 - - def count(self, _func: Callable = None) -> int: - if _func is None: - return self.__len__() - - return self.where(_func).__len__() - - def distinct(self, _func: Callable = None) -> 'Iterable': - if _func is None: - _func = _default_lambda - - result = Iterable() - known_values = [] - for element in self: - value = _func(element) - if value in known_values: - continue - - known_values.append(value) - result.append(element) - - return result - - def element_at(self, _index: int) -> any: - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - return self[_index] - - def element_at_or_default(self, _index: int) -> any: - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - try: - return self[_index] - except IndexError: - return None - - def first(self) -> any: - if len(self) == 0: - raise IndexOutOfRangeException() - - return self[0] - - def first_or_default(self) -> Optional[any]: - if len(self) == 0: - return None - - return self[0] - - def group_by(self, _func: Callable = None) -> 'Iterable': - if _func is None: - _func = _default_lambda - groups = {} - - for v in self: - value = _func(v) - if v not in groups: - groups[value] = Iterable(type(v)) - - groups[value].append(v) - - return Iterable(Iterable).extend(groups.values()) - - def last(self) -> any: - if len(self) == 0: - raise IndexOutOfRangeException() - - return self[len(self) - 1] - - def last_or_default(self) -> Optional[any]: - if len(self) == 0: - return None - - return self[len(self) - 1] - - def for_each(self, _func: Callable = None) -> 'Iterable': - if _func is not None: - for element in self: - _func(element) - - return self - - def max(self, _func: Callable = None) -> Union[int, float, complex]: - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return _func(max(self, key=_func)) - - def median(self, _func=None) -> Union[int, float]: - if _func is None: - _func = _default_lambda - - result = self.order_by(_func).select(_func).to_list() - length = len(result) - i = int(length / 2) - return ( - result[i] - if length % 2 == 1 - else (float(result[i - 1]) + float(result[i])) / float(2) - ) - - def min(self, _func: Callable = None) -> Union[int, float, complex]: - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return _func(min(self, key=_func)) - - def order_by(self, _func: Callable = None) -> OrderedIterableABC: - if _func is None: - _func = _default_lambda - - from cpl_query.iterable.ordered_iterable import OrderedIterable - return OrderedIterable(self.type, _func, sorted(self, key=_func)) - - def order_by_descending(self, _func: Callable = None) -> OrderedIterableABC: - if _func is None: - _func = _default_lambda - - from cpl_query.iterable.ordered_iterable import OrderedIterable - return OrderedIterable(self.type, _func, sorted(self, key=_func, reverse=True)) - - def reverse(self) -> 'Iterable': - return Iterable().extend(reversed(self)) - - def select(self, _func: Callable = None) -> 'Iterable': - if _func is None: - _func = _default_lambda - - result = Iterable() - result.extend(_func(_o) for _o in self) - return result - - def select_many(self, _func: Callable = None) -> 'Iterable': - result = Iterable() - # The line below is pain. I don't understand anything of it... - # written on 09.11.2022 by Sven Heidemann - elements = [_a for _o in self for _a in _func(_o)] - - result.extend(elements) - return result - - def single(self) -> any: - if len(self) > 1: - raise Exception('Found more than one element') - elif len(self) == 0: - raise Exception('Found no element') - - return self[0] - - def single_or_default(self) -> Optional[any]: - if len(self) > 1: - raise Exception('Index out of range') - elif len(self) == 0: - return None - - return self[0] - - def skip(self, _index: int) -> 'Iterable': - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - return Iterable(self.type, values=self[_index:]) - - def skip_last(self, _index: int) -> 'Iterable': - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - index = len(self) - _index - - result = Iterable() - result.extend(self[:index]) - return result - - def sum(self, _func: Callable = None) -> Union[int, float, complex]: - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - result = 0 - for x in self: - result += _func(x) - - return result - - def take(self, _index: int) -> 'Iterable': - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - result = Iterable() - result.extend(self[:_index]) - return result - - def take_last(self, _index: int) -> 'Iterable': - index = len(self) - _index - - if index >= len(self) or index < 0: - raise IndexOutOfRangeException() - - result = Iterable() - result.extend(self[index:]) - return result - - def where(self, _func: Callable = None) -> 'Iterable': - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - if _func is None: - _func = _default_lambda - - result = Iterable(self.type) - for element in self: - if _func(element): - result.append(element) - - return result diff --git a/src/cpl_query/iterable/iterable_abc.py b/src/cpl_query/iterable/iterable_abc.py index 5c6c47db..dcae50b3 100644 --- a/src/cpl_query/iterable/iterable_abc.py +++ b/src/cpl_query/iterable/iterable_abc.py @@ -4,54 +4,45 @@ from typing import Iterable from cpl_query.base.queryable_abc import QueryableABC -class IterableABC(list, QueryableABC): +class IterableABC(QueryableABC): r"""ABC to define functions on list """ @abstractmethod def __init__(self, t: type = None, values: Iterable = None): - values = [] if values is None else values - list.__init__(self, values) + QueryableABC.__init__(self, t, values) - if t is None and len(values) > 0: - t = type(values[0]) + def __setitem__(self, i, val): + self._check_type(val) + values = [*self._values] + values[i] = val + self._set_values(values) - self._type = t - - def __repr__(self): - return f'<{type(self).__name__} {list(self).__repr__()}>' + def __delitem__(self, i): + values = [*self._values] + del values[i] + self._set_values(values) @property def type(self) -> type: return self._type - def to_list(self) -> list: - r"""Converts :class: `cpl_query.base.sequence_abc.SequenceABC` to :class: `list` - - Returns - ------- - :class: `list` - """ - return [x for x in self] - def __str__(self): return str(self.to_list()) - def append(self, __object: object) -> None: + def append(self, _object: object): + self.add(_object) + + def add(self, _object: object): r"""Adds element to list Parameter --------- - __object: :class:`object` + _object: :class:`object` value """ - if self._type is not None and type(__object) != self._type and not isinstance(type(__object), self._type) and not issubclass(type(__object), self._type): - raise Exception(f'Unexpected type: {type(__object)}\nExpected type: {self._type}') - - if len(self) == 0 and self._type is None: - self._type = type(__object) - - # self._values = SequenceValues([*self._values, __object], self._type) - super().append(__object) + self._check_type(_object) + values = [*self._values, _object] + self._set_values(values) def extend(self, __iterable: Iterable) -> 'IterableABC': r"""Adds elements of given list to list diff --git a/src/cpl_query/iterable/ordered_iterable.py b/src/cpl_query/iterable/ordered_iterable.py deleted file mode 100644 index 9fe50079..00000000 --- a/src/cpl_query/iterable/ordered_iterable.py +++ /dev/null @@ -1,35 +0,0 @@ -from collections.abc import Callable - -from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument -from cpl_query.iterable.iterable import Iterable -from cpl_query.iterable.ordered_iterable_abc import OrderedIterableABC - - -class OrderedIterable(Iterable, OrderedIterableABC): - r"""Implementation of :class: `cpl_query.extension.Iterable` `cpl_query.extension.OrderedIterableABC` - """ - - def __init__(self, _t: type, _func: Callable = None, _values: Iterable = None): - Iterable.__init__(self, _t) - OrderedIterableABC.__init__(self, _t, _func, _values) - - def then_by(self: OrderedIterableABC, _func: Callable) -> OrderedIterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - self._funcs.append(_func) - - return OrderedIterable(self.type, _func, sorted(self, key=lambda *args: [f(*args) for f in self._funcs])) - - def then_by_descending(self: OrderedIterableABC, _func: Callable) -> OrderedIterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - self._funcs.append(_func) - return OrderedIterable(self.type, _func, sorted(self, key=lambda *args: [f(*args) for f in self._funcs], reverse=True)) diff --git a/unittests/unittests_cli/constants.py b/unittests/unittests_cli/constants.py index c8318dab..13986be9 100644 --- a/unittests/unittests_cli/constants.py +++ b/unittests/unittests_cli/constants.py @@ -6,4 +6,4 @@ if not os.getcwd().endswith('unittests'): PLAYGROUND_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}test_cli_playground')) TRANSLATION_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}unittests_translation')) -CLI_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}../src/cpl_cli/main.py')) +CLI_PATH = os.path.abspath(os.path.join(os.getcwd(), f'../../src/cpl_cli/main.py')) diff --git a/unittests/unittests_query/enumerable_query_test_case.py b/unittests/unittests_query/enumerable_query_test_case.py index d95275e0..ffe71576 100644 --- a/unittests/unittests_query/enumerable_query_test_case.py +++ b/unittests/unittests_query/enumerable_query_test_case.py @@ -11,7 +11,16 @@ from unittests_query.models import User, Address class EnumerableQueryTestCase(unittest.TestCase): def setUp(self) -> None: - self._tests = Enumerable(User) + users = [] + for i in range(0, 100): + users.append(User( + String.random_string(string.ascii_letters, 8).lower(), + Address( + String.random_string(string.ascii_letters, 10).lower(), + randint(1, 10) + ) + )) + self._t_user = User( 'Test user', Address( @@ -27,22 +36,10 @@ class EnumerableQueryTestCase(unittest.TestCase): ) ) - self._generate_test_data() + users.append(self._t_user) + users.append(self._t_user2) - def _generate_test_data(self): - for i in range(0, 100): - user = User( - String.random_string(string.ascii_letters, 8).lower(), - Address( - String.random_string(string.ascii_letters, 10).lower(), - randint(1, 10) - ) - ) - - self._tests.add(user) - - self._tests.add(self._t_user) - self._tests.add(self._t_user2) + self._tests = Enumerable(User, users) def test_any(self): results = [] diff --git a/unittests/unittests_query/enumerable_test_case.py b/unittests/unittests_query/enumerable_test_case.py index 60fe056e..b8605493 100644 --- a/unittests/unittests_query/enumerable_test_case.py +++ b/unittests/unittests_query/enumerable_test_case.py @@ -6,17 +6,9 @@ from cpl_query.enumerable.enumerable import Enumerable class EnumerableTestCase(unittest.TestCase): def setUp(self) -> None: - self._list = Enumerable(int) - - def _clear(self): - self._list.clear() - self.assertEqual(self._list, []) + self._list = Enumerable(int, list(range(1, 4))) def test_append(self): - self._list.add(1) - self._list.add(2) - self._list.add(3) - self.assertEqual(self._list.to_list(), [1, 2, 3]) self.assertRaises(Exception, lambda v: self._list.add(v), '3') @@ -38,30 +30,7 @@ class EnumerableTestCase(unittest.TestCase): n += 1 def test_get(self): - self._list.add(1) - self._list.add(2) - self._list.add(3) - self.assertEqual(self._list.element_at(2), [1, 2, 3][2]) def test_count(self): - self._list.add(1) - self._list.add(2) - self._list.add(3) - self.assertEqual(self._list.count(), 3) - - def test_remove(self): - old_values = self._list._values - self._list.add(1) - self.assertNotEqual(old_values, self._list._values) - self._list.add(2) - self._list.add(3) - - self.assertEqual(self._list.to_list(), [1, 2, 3]) - self.assertRaises(Exception, lambda v: self._list.add(v), '3') - old_values = self._list._values - self._list.remove(3) - self.assertNotEqual(old_values, self._list._values) - self.assertEqual(self._list.to_list(), [1, 2]) - self.assertRaises(Exception, lambda v: self._list.add(v), '3') diff --git a/unittests/unittests_query/iterable_query_test_case.py b/unittests/unittests_query/iterable_query_test_case.py index c48fc9ea..8ea3a0b8 100644 --- a/unittests/unittests_query/iterable_query_test_case.py +++ b/unittests/unittests_query/iterable_query_test_case.py @@ -113,7 +113,7 @@ class IterableQueryTestCase(unittest.TestCase): addresses.append(u.address.nr) - res2 = self._tests.distinct(lambda x: x.address.nr).select(lambda x: x.address.nr) + res2 = self._tests.distinct(lambda x: x.address.nr).select(lambda x: x.address.nr).to_list() self.assertEqual(addresses, res2) def test_element_at(self): @@ -199,7 +199,7 @@ class IterableQueryTestCase(unittest.TestCase): groups[v].append(v) elements.append(v) - r1, r2 = list(groups.values()), elements.group_by() + r1, r2 = list(groups.values()), elements.group_by().select(lambda l: l.to_list()).to_list() self.assertEqual(r1, r2) def test_for_each(self): diff --git a/unittests/unittests_query/models.py b/unittests/unittests_query/models.py index d3b147dd..54c9522c 100644 --- a/unittests/unittests_query/models.py +++ b/unittests/unittests_query/models.py @@ -4,9 +4,15 @@ class User: self.name = name self.address = address + def __repr__(self): + return f'<{type(self).__name__} {self.name} {self.address}>' + class Address: def __init__(self, street, nr): self.street = street - self.nr = nr \ No newline at end of file + self.nr = nr + + def __repr__(self): + return f'<{type(self).__name__} {self.street} {self.nr}>'