Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support slicing properties as properties (aka. support obj[i:j].shape). #44

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 52 additions & 12 deletions slicerator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@ def __init__(self, ancestor, indices=None, length=None,
Also, the attributes of the parent object can be propagated, exposed
through the child Slicerators. By default, no attributes are
propagated. Attributes can be white_listed by using the optional
parameter `propagated_attrs`.
parameter `propagate_attrs`.

Methods taking an index will be remapped if they are decorated
with `index_attr`. They also have to be present in the
`propagate_attrs` list.

Properties declared using `sliceable_property` will be copied *as
properties* in subslices, i.e., on access, the property's getter will
be called on the subsliced object.

Parameters
----------
ancestor : object
Expand Down Expand Up @@ -186,7 +190,8 @@ def __getitem__(self, i):
if new_length is None:
return self._get(indices)
else:
return cls(self, indices, new_length, propagate_attrs)
return _instantiate_slicerator_subclass_with_attrs(
self, indices, new_length, propagate_attrs)

for name in ['__name__', '__module__', '__repr__']:
try:
Expand Down Expand Up @@ -238,20 +243,14 @@ def __getitem__(self, key):
if new_length is None:
return (self[k] for k in rel_indices)
indices = _index_generator(rel_indices, self.indices)
return Slicerator(self._ancestor, indices, new_length,
self._propagate_attrs)
return _instantiate_slicerator_subclass_with_attrs(
self._ancestor, indices, new_length, self._propagate_attrs)

def __getattr__(self, name):
# to avoid infinite recursion, always check if public field is there
if '_propagate_attrs' not in self.__dict__:
self._propagate_attrs = []
if name in self._propagate_attrs:
attr = getattr(self._ancestor, name)
if (isinstance(attr, SliceableAttribute) or
hasattr(attr, '_index_flag')):
return SliceableAttribute(self, attr)
else:
return attr
return _make_property_fget(name)(self)
raise AttributeError

def __getstate__(self):
Expand All @@ -264,6 +263,41 @@ def __setstate__(self, data_as_list):
return self.__init__(data_as_list)


def _make_property_fget(name):
def fget(self):
if name not in self._ancestor.__dict__:
cls_attr = getattr(type(self._ancestor), name, None)
if (isinstance(cls_attr, property)
and getattr(cls_attr.fget, '_slice_as_property', False)):
return cls_attr.fget(self)
attr = getattr(self._ancestor, name)
if (isinstance(attr, SliceableAttribute) or
hasattr(attr, '_index_flag')):
return SliceableAttribute(self, attr)
else:
return attr

return fget


_slicerator_subclass_cache = {}


def _instantiate_slicerator_subclass_with_attrs(
ancestor, indices, length, propagate_attrs):
if (ancestor, propagate_attrs) in _slicerator_subclass_cache:
cls = _slicerator_subclass_cache[ancestor, propagate_attrs]
return cls(ancestor, indices, length, propagate_attrs)
else:
class Sliced(Slicerator): pass
obj = Sliced(ancestor, indices, length, propagate_attrs)
# to avoid infinite recursion, always check if public field is there
for name in obj.__dict__.get('_propagate_attrs', []):
setattr(Sliced, name, property(_make_property_fget(name)))
_slicerator_subclass_cache[ancestor, propagate_attrs] = Sliced
return obj


def key_to_indices(key, length):
"""Converts a fancy key into a list of indices.

Expand Down Expand Up @@ -478,7 +512,8 @@ def __getitem__(self, i):
if new_length is None:
return self._get(indices)
else:
return Slicerator(self, indices, new_length, self._propagate_attrs)
return _instantiate_slicerator_subclass_with_attrs(
self, indices, new_length, self._propagate_attrs)

def __getattr__(self, name):
# to avoid infinite recursion, always check if public field is there
Expand Down Expand Up @@ -706,6 +741,11 @@ def propagate_attr(func):
return func


def sliceable_property(func):
func._slice_as_property = True
return property(func)


def index_attr(func):
@wraps(func)
def wrapper(obj, key, *args, **kwargs):
Expand Down