-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added more named list subclasses for the basic types.
This supports a 1:1 mapping of the R atomic vectors with names.
- Loading branch information
Showing
8 changed files
with
570 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
from typing import Any, Iterable, Optional, Sequence, Union | ||
|
||
from .NamedList import NamedList | ||
from .Names import Names | ||
from .normalize_subscript import SubscriptTypes | ||
|
||
|
||
def _coerce_to_bool(x: Any): | ||
return None if x is None else bool(x) | ||
|
||
|
||
class _SubscriptCoercer: | ||
def __init__(self, data): | ||
self._data = data | ||
|
||
def __getitem__(self, index): | ||
return _coerce_to_bool(self._data[index]) | ||
|
||
|
||
class BooleanList(NamedList): | ||
""" | ||
List of booleans. This mimics a regular Python list except that anything | ||
added to it will be coerced into a boolean. None values are also acceptable | ||
and are treated as missing booleans. The list may also be named (see | ||
:py:class:`~NamedList`), which provides some dictionary-like functionality. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
data: Optional[Iterable] = None, | ||
names: Optional[Names] = None, | ||
_validate: bool = True, | ||
): | ||
""" | ||
Args: | ||
data: | ||
Some iterable object where all values can be coerced to booleans | ||
or are None. | ||
Alternatively this may itself be None, which defaults to an empty list. | ||
names: | ||
Names for the list elements, defaults to an empty list. | ||
_validate: | ||
Internal use only. | ||
""" | ||
if _validate: | ||
if data is not None: | ||
if isinstance(data, BooleanList): | ||
data = data._data | ||
else: | ||
if isinstance(data, NamedList): | ||
data = data._data | ||
original = data | ||
data = list(_coerce_to_bool(item) for item in original) | ||
super().__init__(data, names, _validate=_validate) | ||
|
||
def set_value( | ||
self, index: Union[int, str], value: Any, in_place: bool = False | ||
) -> "BooleanList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.set_value` after coercing ``value`` to a boolean.""" | ||
return super().set_value(index, _coerce_to_bool(value), in_place=in_place) | ||
|
||
def set_slice( | ||
self, index: SubscriptTypes, value: Sequence, in_place: bool = False | ||
) -> "BooleanList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.set_slice` after coercing ``value`` to booleans.""" | ||
return super().set_slice(index, _SubscriptCoercer(value), in_place=in_place) | ||
|
||
def safe_insert( | ||
self, index: Union[int, str], value: Any, in_place: bool = False | ||
) -> "BooleanList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.safe_insert` after coercing ``value`` to a boolean.""" | ||
return super().safe_insert(index, _coerce_to_bool(value), in_place=in_place) | ||
|
||
def safe_append(self, value: Any, in_place: bool = False) -> "BooleanList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.safe_append` after coercing ``value`` to a boolean.""" | ||
return super().safe_append(_coerce_to_bool(value), in_place=in_place) | ||
|
||
def safe_extend(self, other: Iterable, in_place: bool = True) -> "BooleanList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.safe_extend` after coercing elements of ``other`` to booleans.""" | ||
return super().safe_extend( | ||
(_coerce_to_bool(y) for y in other), in_place=in_place | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
from typing import Any, Iterable, Optional, Sequence, Union | ||
|
||
from .NamedList import NamedList | ||
from .Names import Names | ||
from .normalize_subscript import SubscriptTypes | ||
|
||
|
||
def _coerce_to_float(x: Any): | ||
if x is None: | ||
return None | ||
try: | ||
return float(x) | ||
except: | ||
return None | ||
|
||
|
||
class _SubscriptCoercer: | ||
def __init__(self, data): | ||
self._data = data | ||
|
||
def __getitem__(self, index): | ||
return _coerce_to_float(self._data[index]) | ||
|
||
|
||
class FloatList(NamedList): | ||
""" | ||
List of floats. This mimics a regular Python list except that anything | ||
added to it will be coerced into a float. None values are also acceptable | ||
and are treated as missing floats. The list may also be named (see | ||
:py:class:`~NamedList`), which provides some dictionary-like functionality. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
data: Optional[Iterable] = None, | ||
names: Optional[Names] = None, | ||
_validate: bool = True, | ||
): | ||
""" | ||
Args: | ||
data: | ||
Some iterable object where all values can be coerced to floats | ||
or are None. | ||
Alternatively this may itself be None, which defaults to an empty list. | ||
names: | ||
Names for the list elements, defaults to an empty list. | ||
_validate: | ||
Internal use only. | ||
""" | ||
if _validate: | ||
if data is not None: | ||
if isinstance(data, FloatList): | ||
data = data._data | ||
else: | ||
if isinstance(data, NamedList): | ||
data = data._data | ||
original = data | ||
data = list(_coerce_to_float(item) for item in original) | ||
super().__init__(data, names, _validate=_validate) | ||
|
||
def set_value( | ||
self, index: Union[int, str], value: Any, in_place: bool = False | ||
) -> "FloatList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.set_value` after coercing ``value`` to a float.""" | ||
return super().set_value(index, _coerce_to_float(value), in_place=in_place) | ||
|
||
def set_slice( | ||
self, index: SubscriptTypes, value: Sequence, in_place: bool = False | ||
) -> "FloatList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.set_slice` after coercing ``value`` to floats.""" | ||
return super().set_slice(index, _SubscriptCoercer(value), in_place=in_place) | ||
|
||
def safe_insert( | ||
self, index: Union[int, str], value: Any, in_place: bool = False | ||
) -> "FloatList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.safe_insert` after coercing ``value`` to a float.""" | ||
return super().safe_insert(index, _coerce_to_float(value), in_place=in_place) | ||
|
||
def safe_append(self, value: Any, in_place: bool = False) -> "FloatList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.safe_append` after coercing ``value`` to a float.""" | ||
return super().safe_append(_coerce_to_float(value), in_place=in_place) | ||
|
||
def safe_extend(self, other: Iterable, in_place: bool = True) -> "FloatList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.safe_extend` after coercing elements of ``other`` to floats.""" | ||
return super().safe_extend( | ||
(_coerce_to_float(y) for y in other), in_place=in_place | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
from typing import Any, Iterable, Optional, Sequence, Union | ||
|
||
from .NamedList import NamedList | ||
from .Names import Names | ||
from .normalize_subscript import SubscriptTypes | ||
|
||
|
||
def _coerce_to_int(x: Any): | ||
if x is None: | ||
return None | ||
try: | ||
return int(x) | ||
except: | ||
return None | ||
|
||
|
||
class _SubscriptCoercer: | ||
def __init__(self, data): | ||
self._data = data | ||
|
||
def __getitem__(self, index): | ||
return _coerce_to_int(self._data[index]) | ||
|
||
|
||
class IntegerList(NamedList): | ||
""" | ||
List of integers. This mimics a regular Python list except that anything | ||
added to it will be coerced into a integer. None values are also acceptable | ||
and are treated as missing integers. The list may also be named (see | ||
:py:class:`~NamedList`), which provides some dictionary-like functionality. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
data: Optional[Iterable] = None, | ||
names: Optional[Names] = None, | ||
_validate: bool = True, | ||
): | ||
""" | ||
Args: | ||
data: | ||
Some iterable object where all values can be coerced to integers | ||
or are None. | ||
Alternatively this may itself be None, which defaults to an empty list. | ||
names: | ||
Names for the list elements, defaults to an empty list. | ||
_validate: | ||
Internal use only. | ||
""" | ||
if _validate: | ||
if data is not None: | ||
if isinstance(data, IntegerList): | ||
data = data._data | ||
else: | ||
if isinstance(data, NamedList): | ||
data = data._data | ||
original = data | ||
data = list(_coerce_to_int(item) for item in original) | ||
super().__init__(data, names, _validate=_validate) | ||
|
||
def set_value( | ||
self, index: Union[int, str], value: Any, in_place: bool = False | ||
) -> "IntegerList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.set_value` after coercing ``value`` to a integer.""" | ||
return super().set_value(index, _coerce_to_int(value), in_place=in_place) | ||
|
||
def set_slice( | ||
self, index: SubscriptTypes, value: Sequence, in_place: bool = False | ||
) -> "IntegerList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.set_slice` after coercing ``value`` to integers.""" | ||
return super().set_slice(index, _SubscriptCoercer(value), in_place=in_place) | ||
|
||
def safe_insert( | ||
self, index: Union[int, str], value: Any, in_place: bool = False | ||
) -> "IntegerList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.safe_insert` after coercing ``value`` to a integer.""" | ||
return super().safe_insert(index, _coerce_to_int(value), in_place=in_place) | ||
|
||
def safe_append(self, value: Any, in_place: bool = False) -> "IntegerList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.safe_append` after coercing ``value`` to a integer.""" | ||
return super().safe_append(_coerce_to_int(value), in_place=in_place) | ||
|
||
def safe_extend(self, other: Iterable, in_place: bool = True) -> "IntegerList": | ||
"""Calls :py:meth:`~biocutils.NamedList.NamedList.safe_extend` after coercing elements of ``other`` to integers.""" | ||
return super().safe_extend( | ||
(_coerce_to_int(y) for y in other), in_place=in_place | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import biocutils | ||
from biocutils import BooleanList, NamedList | ||
|
||
|
||
def test_BooleanList_init(): | ||
x = BooleanList([ True, False, False, True ]) | ||
assert isinstance(x, BooleanList) | ||
assert x.as_list() == [ True, False, False, True ] | ||
assert x.get_names() is None | ||
|
||
# Constructor works with other BooleanList objects. | ||
recon = BooleanList(x) | ||
assert recon.as_list() == x.as_list() | ||
|
||
empty = BooleanList() | ||
assert empty.as_list() == [] | ||
|
||
# Constructor works with Nones. | ||
x = BooleanList([True,None,None,False]) | ||
assert x.as_list() == [ True, None, None, False ] | ||
|
||
# Constructor works with other NamedList objects. | ||
x = NamedList(["", 2, None, 0.0]) | ||
recon = BooleanList(x) | ||
assert recon.as_list() == [False, True, None, False] | ||
|
||
|
||
def test_BooleanList_getitem(): | ||
x = BooleanList([True, False, True, False ]) | ||
|
||
assert x[0] | ||
sub = x[1:3] | ||
assert isinstance(sub, BooleanList) | ||
assert sub.as_list() == [False, True] | ||
|
||
x.set_names(["A", "B", "C", "D"], in_place=True) | ||
assert x["C"] | ||
sub = x[["D", "C", "A", "B"]] | ||
assert isinstance(sub, BooleanList) | ||
assert sub.as_list() == [False, True, True, False] | ||
|
||
|
||
def test_BooleanList_setitem(): | ||
x = BooleanList([False, True, True, False]) | ||
x[0] = None | ||
assert x.as_list() == [None, True, True, False] | ||
x[0] = 12345 | ||
assert x.as_list() == [True, True, True, False] | ||
|
||
x[1:3] = [False, False] | ||
assert x.as_list() == [True, False, False, False] | ||
|
||
x[0:4:2] = [None, None] | ||
assert x.as_list() == [None, False, None, False] | ||
|
||
x.set_names(["A", "B", "C", "D"], in_place=True) | ||
x["C"] = True | ||
assert x.as_list() == [None, False, True, False] | ||
x[["A", "B"]] = [False, True] | ||
assert x.as_list() == [False, True, True, False] | ||
x["E"] = "50" | ||
assert x.as_list() == [False, True, True, False, True] | ||
assert x.get_names().as_list() == [ "A", "B", "C", "D", "E" ] | ||
|
||
|
||
def test_BooleanList_mutations(): | ||
# Insertion: | ||
x = BooleanList([1,2,3,4]) | ||
x.insert(2, None) | ||
x.insert(1, "") | ||
assert x.as_list() == [ True, False, True, None, True, True ] | ||
|
||
# Extension: | ||
x.extend([None, 1, 5.0 ]) | ||
assert x.as_list() == [ True, False, True, None, True, True, None, True, True ] | ||
alt = BooleanList([ 0, "", 1 ]) | ||
x.extend(alt) | ||
assert x.as_list() == [ True, False, True, None, True, True, None, True, True, False, False, True ] | ||
|
||
# Appending: | ||
x.append(1) | ||
assert x[-1] | ||
x.append(None) | ||
assert x[-1] is None | ||
|
||
|
||
def test_BooleanList_generics(): | ||
x = BooleanList([False, False, True, True]) | ||
sub = biocutils.subset_sequence(x, [0,3,2,1]) | ||
assert isinstance(sub, BooleanList) | ||
assert sub.as_list() == [False, True, True, False] | ||
|
||
y = ["a", "b", "c", "d"] | ||
com = biocutils.combine_sequences(x, y) | ||
assert isinstance(com, BooleanList) | ||
assert com.as_list() == [False, False, True, True, True, True, True, True] | ||
|
||
ass = biocutils.assign_sequence(x, [1,3], ["a", 0]) | ||
assert isinstance(ass, BooleanList) | ||
assert ass.as_list() == [False, True, True, False] |
Oops, something went wrong.