Source code for rpy2.rlike.container

"""Containers with R-like behaviors."""

import rpy2.rlike.indexing as rli
import itertools
from typing import Any
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
import warnings


[docs] class OrdDict(dict): """ Ordered dictionary. Warning: This class is being deprecated. Use NamedList instead. This is a dict since Python 3.8 `dict` conserve insertion order. This class differs a little from ordered dicts: not all elements have to be named. None as a key value means an absence of name for the element. """ __l: List[Tuple[Optional[str], Any]] def __init__( self, c: Iterable[Union[Tuple[Optional[str], Any], 'NamedItem']]=[] ): warnings.warn( 'rpy2.rinterface.rlike.container.OrdDict is being deprecated. ' 'Use NamedList instead.', DeprecationWarning ) if isinstance(c, TaggedList) or isinstance(c, OrdDict): c = c.items() elif isinstance(c, dict): # FIXME: allow instance from OrdDict ? raise TypeError('A regular dictionnary does not ' + 'conserve the order of its keys.') super(OrdDict, self).__init__() self.__l = [] for kv in c: if isinstance(kv, NamedItem): self.__l[kv.name] = kv.value else: self[kv[0]] = kv[1] def __copy__(self): cp = OrdDict(c=tuple(self.items())) return cp def __reduce__(self): # We need to override the special-cased dict unpickling process in order # to retain the attributes the `__l` attribute. return ( self.__class__, # callable (), # arguments to constructor {'_OrdDict__l': self.__l}, # state None, # list items iter(self.items()), # dict items ) def __cmp__(self, o): return NotImplemented def __eq__(self, o): return NotImplemented def __getitem__(self, key: str): if key is None: raise ValueError("Unnamed items cannot be retrieved by key.") i = super(OrdDict, self).__getitem__(key) return self.__l[i][1] def __iter__(self): seq = self.__l for e in seq: k = e[0] if k is None: continue else: yield k def __len__(self): return len(self.__l) def __ne__(self, o): return NotImplemented def __repr__(self) -> str: s = ['o{', ] for k, v in self.items(): s.append("'%s': %s, " % (str(k), str(v))) s.append('}') return ''.join(s) def __reversed__(self): raise NotImplementedError("Not yet implemented.") def __setitem__(self, key: Optional[str], value: Any): """ Replace the element if the key is known, and conserve its rank in the list, or append it if unknown. """ if key is None: self.__l.append((key, value)) return if key in self: i = self.index(key) self.__l[i] = (key, value) else: self.__l.append((key, value)) super(OrdDict, self).__setitem__(key, len(self.__l)-1)
[docs] def byindex(self, i: int) -> Any: """ Fetch a value by index (rank), rather than by key.""" return self.__l[i]
[docs] def index(self, k: str) -> int: """ Return the index (rank) for the key 'k' """ return super(OrdDict, self).__getitem__(k)
[docs] def get(self, k: str, d: Any = None): """ OD.get(k[,d]) -> OD[k] if k in OD, else d. d defaults to None """ try: res = self[k] except KeyError: res = d return res
[docs] def items(self): """ OD.items() -> an iterator over the (key, value) items of D """ return iter(self.__l)
def keys(self): """ """ return tuple([x[0] for x in self.__l])
[docs] def reverse(self): """ Reverse the order of the elements in-place (no copy).""" seq = self.__l n = len(self.__l) for i in range(n//2): tmp = seq[i] seq[i] = seq[n-i-1] kv = seq[i] if kv is not None: super(OrdDict, self).__setitem__(kv[0], i) seq[n-i-1] = tmp kv = tmp if kv is not None: super(OrdDict, self).__setitem__(kv[0], n-i-1)
def sort(self, cmp=None, key=None, reverse=False): raise NotImplementedError("Not yet implemented.") def values(self): """ """ return tuple([x[1] for x in self.__l])
class NamedItem: """A named item (in a NamedList.""" def __init__(self, name, value): self.__namevalue = (name, value) @property def name(self): return self.__namevalue[0] @property def value(self): return self.__namevalue[1] def __eq__(self, y) -> bool: if isinstance(y, NamedItem): return (self.name == y.name) and (self.value == y.value) else: return self == y class NamedList: """ A mutable sequence for which each item might have a 'name'. This is intended to mimic R's lists or pairlists. This structure is like a Python list to which optional names for each element in the list is added. This cannot be an ordered dictionnary because the keys (names) are not necessarily unique. :param seq: an iterable. :param names: optional sequence of names :param tags: [deprecated] optional sequence of names """ __list: List __names: List[Optional[str]] def __init__(self, seq: Iterable, names: Optional[Iterable] = None, tags: Optional[Iterable] = None): if tags: warnings.warn( 'The named argument "tags" when constructing ' 'a NamedList is deprecated. Use "names" instead.', DeprecationWarning ) if names: raise ValueError( '"tags" is deprecated. Only use the named argument ' '"names".' ) names = tags self.__list = list(seq) if names is None: names = [None, ] * len(self.__list) else: names = list(names) self.__names = names if len(self.__names) != len(self.__list): raise ValueError("There must be as many names as seq") def __add__(self, tl: 'NamedList'): res = NamedList(itertools.chain(self.values(), tl.values()), names=itertools.chain(self.names(), tl.names())) return res def __delitem__(self, i: int): self.__list.__delitem__(i) self.__names.__delitem__(i) def __iadd__(self, y: 'NamedList'): self.__list.__iadd__(tuple(y.values())) self.__names.__iadd__(tuple(y.names())) return self def __imul__(self, i: int): self.__names.__imul__(i) self.__list.__imul__(i) return self def __len__(self) -> int: return len(self.__list) def __mul__(self, i: int) -> 'NamedList': names = self.__names.__mul__(i) values = self.__list.__mul__(i) return type(self)(values, names=names) @classmethod def from_items( cls, namesvalues: Iterable[Union[NamedItem, Tuple[Any, Any]]] ) -> 'NamedList': """Create a NamedList from an iterable of NamedItem objects or (name, value) tuples.""" if isinstance(namesvalues, dict) or isinstance(namesvalues, NamedList): namesvalues = tuple(namesvalues.items()) else: namesvalues = tuple(namesvalues) iter_names = (obj.name if isinstance(obj, NamedItem) else obj[0] for obj in namesvalues) iter_values = (obj.value if isinstance(obj, NamedItem) else obj[1] for obj in namesvalues) return cls(iter_values, names=iter_names) def __getitem__(self, i: Union[int, slice]): if isinstance(i, slice): return type(self)(self.__list[i], names=self.__names[i]) else: return self.__list[i] def __setitem__(self, i: Union[int, slice], y: 'Union[NamedList, NamedItem]'): if isinstance(i, slice): step = i.step if i.step else 1 if isinstance(y, NamedList): if len(y) != ((i.stop-i.start) // step): raise ValueError('Length mistmatch between slice and values.') iter_i_name_value = zip( range(i.start, i.stop, step), y.names(), y.values() ) else: raise ValueError('y must be a NamedList when i is a slice.') for idx, name, value in iter_i_name_value: self.__list[idx] = value self.__names[idx] = name elif isinstance(i, int): if isinstance(y, NamedItem): self.__list[i] = y.value self.__names[i] = y.name else: raise ValueError('y must a NamedItem.') else: raise ValueError('i must be a slice or an int.') def append(self, y: Union[NamedItem, Any], tag=None): """ Append a NamedItems to the list :param y: NamedItem, or any object. :param tag: A tag/name (deprecated). """ if tag: warnings.warn( 'The named argument "tag" is deprecated. Use a NamedItem.', DeprecationWarning ) if isinstance(y, NamedItem): raise ValueError('Do not use tag if already appending a NamedItem.') self.__list.append(y) self.__names.append(tag) elif isinstance(y, NamedItem): self.__list.append(y.value) self.__names.append(y.name) else: warnings.warn( 'Appending non NamedItem object is deprecated.', DeprecationWarning ) self.__list.append(y) self.__names.append(tag) def extend(self, lst: 'NamedList'): """ Extend the named list. :param lst: A NamedList """ self.__names.extend(lst.names()) self.__list.extend(lst.values()) def insert(self, index: int, obj: 'Union[NamedItem, Any]', tag=None): """ Insert an object in the list :param index: integer :param obj: object :param tag: object """ if tag: warnings.warn( 'The named argument "tag" is deprecated. Use a NamedItem.', DeprecationWarning ) if isinstance(obj, NamedItem): raise ValueError('Do not use tag if already inserting a NamedItem.') self.__list.insert(index, obj) self.__names.insert(index, tag) elif isinstance(obj, NamedItem): self.__list.insert(index, obj.value) self.__names.insert(index, obj.name) else: warnings.warn( 'Inserting non NamedItem object is deprecated.', DeprecationWarning ) self.__list.insert(index, obj) self.__names.insert(index, tag) def iterontag(self, tag): """ iterate on items marked with one given tag. :param tag: object """ warnings.warn( 'The method iterontag is deprecated. ' 'Use items() and filter on the names.', DeprecationWarning ) i = 0 for onetag in self.__names: if tag == onetag: yield self[i] i += 1 def items(self) -> Iterator[NamedItem]: """ Return an iterator over (name, value) pairs. """ for name, value in zip(self.__names, self.__list): yield NamedItem(name, value) def names(self) -> Iterator[Any]: for n in self.__names: yield n def getbyname(self, name): """Get the first value with a matching name.""" idx = self.__names.index(name) return self.__list[idx] def values(self) -> Iterator[Any]: for v in self.__list: yield v def __iter__(self): return self.values() def itertags(self) -> Iterator[Any]: """ iterate on tags. :rtype: iterator """ warnings.warn( 'The method itertags() is deprecated. ' 'Use names() instead.', DeprecationWarning ) for tag in self.__names: yield tag def pop(self, index: Optional[int] = None): """ Pop the item at a given index out of the list :param index: integer """ if index is None: index = len(self) - 1 res = self.__list.pop(index) self.__names.pop(index) return res def remove(self, value): """ Remove the first item with a given value from the list. :param value: object """ found = False for i in range(len(self)): if self.__list[i] == value: found = True break if found: self.pop(i) def reverse(self): """ Reverse the order of the elements in the list. """ self.__list.reverse() self.__names.reverse() def sort(self, reverse=False, usenames=False): """ Sort in place. """ if usenames: o = rli.order(self.__names, reverse=reverse) else: o = rli.order(self.__list, reverse=reverse) self.__names = [self.__names[i] for i in o] self.__list = [self.__list[i] for i in o] def setnames(self, names): if len(names) == len(self.__names): self.__names = list(names) else: raise ValueError('The new sequence of names should have the ' 'same length as the old one.') def __get_tags(self): warnings.warn( 'The attribute .tags is deprecated. ' 'Use .names instead.', DeprecationWarning ) return self.names def __set_tags(self, names): warnings.warn( 'The attribute .tags is deprecated. ' 'Use .setname() instead.', DeprecationWarning ) self.names = names tags = property(__get_tags, __set_tags) def setname(self, i: Union[int, slice], n: Any): """ Set name 'n' for item at index 'i'. :param i: int or slice. :param n: object (name(s)) """ if isinstance(i, slice): if len(n) != ((i.stop-i.start) // i.step): raise ValueError('Length mistmatch between slice and values.') for idx, (name, value) in enumerate( zip( range(i.start, i.stop, i.step if i.step else 1), n ) ): self.__names[idx] = value else: self.__names[i] = n def settag(self, i, t): warnings.warn( 'The method settag() is deprecated. ' 'Use setname() instead.', DeprecationWarning ) self.setname(i, t)
[docs] class TaggedList(NamedList): def __init__(self, *args, **kwargs): warnings.warn('The class name "TaggedList" is deprecated. ' 'use "NamedList" instead.', DeprecationWarning) super().__init__(self, *args, **kwargs)