import re
from typing import Generic, Union, List, Iterable, Iterator, Optional, Any, TypeVar
T = TypeVar("T")
[docs]
class GenericListMixin(Generic[T]):
"""
Mixin class containing common functionality for list operations.
Notes on typing:
- Pure list-like operations (append/extend/...) are fully generic over `T`.
- Convenience helpers like `find()` / `filter_by_size()` rely on optional attributes
(e.g. `.name`, `.size`). For arbitrary `T`, we use `getattr()` to keep runtime
behavior flexible while remaining type-safe-ish.
"""
def __len__(self):
return len(self.elements)
[docs]
def append(self, elements: T):
self.elements.append(elements)
[docs]
def extend(self, elements: Union[List[T], Iterable[T], "GenericListMixin[T]"]):
if isinstance(elements, GenericListMixin):
# It's another GenericListMixin object
self.elements.extend(elements.elements)
else:
# It's a list or iterable
self.elements.extend(list(elements))
[docs]
def insert(self, index: int, elements: T):
self.elements.insert(index, elements)
[docs]
def remove(self, elements: T):
self.elements.remove(elements)
[docs]
def pop(self, index: int = -1) -> T:
return self.elements.pop(index)
[docs]
def clear(self):
self.elements.clear()
[docs]
def index(self, elements: T, start: int = 0, stop: int = None) -> int:
if stop is None:
return self.elements.index(elements, start)
return self.elements.index(elements, start, stop)
[docs]
def count(self, elements: T) -> int:
return self.elements.count(elements)
[docs]
def reverse(self):
self.elements.reverse()
[docs]
def sort(self, key=None, reverse: bool = False):
self.elements.sort(key=key, reverse=reverse)
[docs]
def copy(self) -> List[T]:
return self.elements.copy()
def __getitem__(self, index: Union[int, slice]) -> Union[T, List[T]]:
return self.elements[index]
def __setitem__(self, index: Union[int, slice], value: Union[T, List[T], Iterable[T]]):
if isinstance(index, slice):
self.elements[index] = list(value)
else:
self.elements[index] = value
def __delitem__(self, index: Union[int, slice]):
del self.elements[index]
def __iter__(self) -> Iterator[T]:
return iter(self.elements)
def __contains__(self, element: T) -> bool:
return element in self.elements
def __bool__(self) -> bool:
return bool(self.elements)
def __repr__(self) -> str:
return f"{self.__class__.__name__}(elements={self.elements!r})"
[docs]
def find(self, pattern: str) -> Optional[T]:
regex = re.compile(pattern)
for element in self.elements:
name = getattr(element, "name", None)
if name and regex.search(name):
return element
return None
[docs]
def find_all(self, pattern: str) -> Iterator[T]:
regex = re.compile(pattern)
for element in self.elements:
name = getattr(element, "name", None)
if name and regex.search(name):
yield element
[docs]
def filter_by_size(self, min_size: int = None, max_size: int = None) -> Iterator[T]:
for element in self.elements:
size = getattr(element, "size", None)
if size is None:
continue
if min_size is not None and size < min_size:
continue
if max_size is not None and size > max_size:
continue
yield element
[docs]
def filter_by_property(self, property_name: str, value: Any) -> Iterator[T]:
for element in self.elements:
if hasattr(element, property_name) and getattr(element, property_name) == value:
yield element
[docs]
def sort_by_name(self, reverse: bool = False):
self.elements.sort(key=lambda x: getattr(x, "name", "") or "", reverse=reverse)
[docs]
def sort_by_size(self, reverse: bool = False):
self.elements.sort(key=lambda x: getattr(x, "size", 0) or 0, reverse=reverse)