from typing import Dict, TypeVar, Optional, Type, Generic, Any, Iterable from collections.abc import Set # frozenset will not work correctly if we extend typing.Set K=TypeVar('K') V=TypeVar('V') class Keys(Generic[K,V], Set): ''' Keys is a Set, containing the keys of the given dict. Actions on Keys are reflected directly into the dict. An alternative to the python dict.keys() function. The dict.keys() function returns a dict_keys object that really is a second class citizen because it offers a very small subset of the functionality of set. Eg no "pop" method. See also collections ABC Set ''' def __init__(self, d:Dict[K,V]): self._d:Dict[K,V]=d def __contains__(self, key:Any): return key in self._d def __len__(self): return len(self._d) def __delitem__(self, key:K): ''' remove key from dict. May thrkw key error. See also pop ''' del self._d[key] def __iter__(self): return iter(self._d.keys()) def __getitem__(self, key:K)->V: ''' raises keyerror if item not there. See also get ''' return self._d[key] def __hash__(self): return hash(frozenset(self._d.keys())) def __repr__(self): return repr(set(self._d)) def __eq__(self, other:Any): return len(self) == len(other) and self.__le__(other) def __le__(self, other:Any): if not isinstance(other, Set): return NotImplemented if len(self) > len(other): return False for elem in self: if elem not in other: return False return True def get(self, key:K, default:Optional[Type]=None)->Any: ''' @param key the key to look up @param default the default value for return if key not present, defaults to None @return value for given key, or default if key is not in dict ''' return self._d.get(key, default) def pop(self, key:K, default:Any=None)->Any: ''' Remove given key from the dict @param key the key to pop @param default the value to return if key is not present. @return the value for the given key. If the key is not in dict, returns the default value. override is bit poor, because original is not properly typed... ''' return self._d.pop(key, default) def add(self, key:K): raise NotImplemented # NotSupported would be better def difference_update(self, *items: Iterable[Any])->None: for lst in items: for key in lst: self._d.pop(key) def issuperset(self, otherset: Set): return set(self._d.keys()).issuperset(otherset) def remove(self, key:K): del self._d[key] def copy(self): ''' shallow copy of the keys at this moment ''' return set(self._d.keys())