Source code for pyomo.core.base.indexed_component_slice

# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and Engineering
# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this
# software.  This software is distributed under the 3-clause BSD License.
# ____________________________________________________________________________________

import copy
import itertools

from pyomo.common import DeveloperError
from pyomo.common.collections import Sequence

from pyomo.core.base.enums import SortComponents
from pyomo.core.base.global_set import UnindexedComponent_index


[docs] class IndexedComponent_slice: """Special class for slicing through hierarchical component trees The basic concept is to interrupt the normal slice generation procedure to return a specialized iterable class (this object). This object supports simple getitem / getattr / call methods and caches them until it is time to actually iterate through the slice. We then walk down the cached names / indices and resolve the final objects during the iteration process. This works because all the calls to __getitem__ / __getattr__ / __call__ happen *before* the call to __iter__() """ ATTR_MASK = 8 ITEM_MASK = 16 CALL_MASK = 32 GET_MASK = 1 SET_MASK = 2 DEL_MASK = 4 slice_info = 0 get_attribute = ATTR_MASK | GET_MASK set_attribute = ATTR_MASK | SET_MASK del_attribute = ATTR_MASK | DEL_MASK get_item = ITEM_MASK | GET_MASK set_item = ITEM_MASK | SET_MASK del_item = ITEM_MASK | DEL_MASK call = CALL_MASK
[docs] def __init__(self, component, fixed=None, sliced=None, ellipsis=None): """A "slice" over an _IndexedComponent hierarchy This class has two forms for the constructor. The first form is the standard constructor that takes a base component and indexing information. This form takes IndexedComponent_slice(component, fixed, sliced, ellipsis) The second form is a "copy constructor" that is used internally when building up the "call stack" for the hierarchical slice. The copy constructor takes an IndexedComponent_slice and an optional "next term" in the slice construction (from get/set/del item/attr or call): IndexedComponent_slice(slice, next_term=None) Parameters ---------- component: IndexedComponent The base component for this slice fixed: dict A dictionary indicating the fixed indices of component, mapping index position to value sliced: dict A dictionary indicating the sliced indices of component mapping the index position to the (python) slice object ellipsis: int The position of the ellipsis in the initial component slice """ # Note that because we use a custom __setattr__, we need to # define actual instance attributes using the base class # __setattr__. set_attr = super(IndexedComponent_slice, self).__setattr__ if type(component) is IndexedComponent_slice: # Copy constructor _len = component._len # For efficiency, we will only duplicate the call stack # list if this instance is not point to the end of the list. if _len == len(component._call_stack): set_attr('_call_stack', component._call_stack) else: set_attr('_call_stack', component._call_stack[:_len]) set_attr('_len', _len) if fixed is not None: self._call_stack.append(fixed) self._len += 1 set_attr( 'call_errors_generate_exceptions', component.call_errors_generate_exceptions, ) set_attr( 'key_errors_generate_exceptions', component.key_errors_generate_exceptions, ) set_attr( 'attribute_errors_generate_exceptions', component.attribute_errors_generate_exceptions, ) else: # Normal constructor set_attr( '_call_stack', [ ( IndexedComponent_slice.slice_info, (component, fixed, sliced, ellipsis), ) ], ) set_attr('_len', 1) # Since this is an object, users may change these flags # between where they declare the slice and iterate over it. set_attr('call_errors_generate_exceptions', True) set_attr('key_errors_generate_exceptions', True) set_attr('attribute_errors_generate_exceptions', True)
def __getstate__(self): """Serialize this object. In general, we would not need to implement this (the object does not leverage ``__slots__``). However, because we have a "blanket" implementation of :py:meth:`__getattr__`, we need to explicitly implement these to avoid "accidentally" extending or evaluating this slice.""" return {k: getattr(self, k) for k in self.__dict__} def __setstate__(self, state): """Deserialize the state into this object.""" set_attr = super(IndexedComponent_slice, self).__setattr__ for k, v in state.items(): set_attr(k, v) def __deepcopy__(self, memo): """Deepcopy this object (leveraging :py:meth:`__getstate__`)""" ans = memo[id(self)] = self.__class__.__new__(self.__class__) ans.__setstate__(copy.deepcopy(self.__getstate__(), memo)) return ans def __iter__(self): """Return an iterator over this slice""" return _IndexedComponent_slice_iter(self) def __getattr__(self, name): """Override the "." operator to defer resolution until iteration. Creating a slice of a component returns a IndexedComponent_slice object. Subsequent attempts to resolve attributes hit this method. """ return IndexedComponent_slice( self, (IndexedComponent_slice.get_attribute, name) ) def __setattr__(self, name, value): """Override the "." operator implementing attribute assignment This supports notation similar to: del model.b[:].c.x = 5 and immediately evaluates the slice. """ # Don't overload any pre-existing attributes if name in self.__dict__: return super(IndexedComponent_slice, self).__setattr__(name, value) # Immediately evaluate the slice and set the attributes for i in IndexedComponent_slice( self, (IndexedComponent_slice.set_attribute, name, value) ): pass return None def __getitem__(self, idx): """Override the "[]" operator to defer resolution until iteration. Creating a slice of a component returns a IndexedComponent_slice object. Subsequent attempts to query items hit this method. """ return IndexedComponent_slice(self, (IndexedComponent_slice.get_item, idx)) def __setitem__(self, idx, val): """Override the "[]" operator for setting item values. This supports notation similar to: model.b[:].c.x[1,:] = 5 and immediately evaluates the slice. """ # Immediately evaluate the slice and set the attributes for i in IndexedComponent_slice( self, (IndexedComponent_slice.set_item, idx, val) ): pass return None def __delitem__(self, idx): """Override the "del []" operator for deleting item values. This supports notation similar to: del model.b[:].c.x[1,:] and immediately evaluates the slice. """ # Immediately evaluate the slice and set the attributes for i in IndexedComponent_slice(self, (IndexedComponent_slice.del_item, idx)): pass return None def __call__(self, *args, **kwds): """Special handling of the "()" operator for component slices. Creating a slice of a component returns a IndexedComponent_slice object. Subsequent attempts to call items hit this method. We handle the __call__ method separately based on the item (identifier immediately before the "()") being called: - if the item was 'component', then we defer resolution of this call until we are actually iterating over the slice. This allows users to do operations like `m.b[:].component('foo').bar[:]` - if the item is anything else, then we will immediately iterate over the slice and call the item. This allows "vector-like" operations like: `m.x[:,1].fix(0)`. """ # There is a weird case in pypy3.6-7.2.0 where __name__ gets # called after retrieving an attribute that will be called. I # don't know why that happens, but we will trap it here and # remove the getattr(__name__) from the call stack. _len = self._len if ( self._call_stack[_len - 1][0] == IndexedComponent_slice.get_attribute and self._call_stack[_len - 1][1] == '__name__' ): self._len -= 1 ans = IndexedComponent_slice(self, (IndexedComponent_slice.call, args, kwds)) # Because we just duplicated the slice and added a new entry, we # know that the _len == len(_call_stack) if ans._call_stack[-2][1] == 'component': return ans else: # Note: simply calling "list(self)" results in infinite # recursion in python2.6 return list(i for i in ans) @classmethod def _getitem_args_to_str(cls, args): for i, v in enumerate(args): if v is Ellipsis: args[i] = '...' elif type(v) is slice: args[i] = ( (repr(v.start) if v.start is not None else '') + ':' + (repr(v.stop) if v.stop is not None else '') + (':%r' % v.step if v.step is not None else '') ) else: args[i] = repr(v) return '[' + ', '.join(args) + ']' def __str__(self): ans = '' for level in self._call_stack: if level[0] == IndexedComponent_slice.slice_info: ans += level[1][0].name if level[1][1] is not None: tmp = dict(level[1][1]) tmp.update(level[1][2]) if level[1][3] is not None: tmp[level[1][3]] = Ellipsis ans += self._getitem_args_to_str([tmp[i] for i in sorted(tmp)]) elif level[0] & IndexedComponent_slice.ITEM_MASK: if isinstance(level[1], Sequence): tmp = list(level[1]) else: tmp = [level[1]] ans += self._getitem_args_to_str(tmp) elif level[0] & IndexedComponent_slice.ATTR_MASK: ans += '.' + level[1] elif level[0] & IndexedComponent_slice.CALL_MASK: ans += ( '(' + ', '.join( itertools.chain( (repr(_) for _ in level[1]), ('%s=%r' % kv for kv in level[2].items()), ) ) + ')' ) if level[0] & IndexedComponent_slice.SET_MASK: ans += ' = %r' % (level[2],) elif level[0] & IndexedComponent_slice.DEL_MASK: ans = 'del ' + ans return ans def __hash__(self): return hash(tuple(_freeze(x) for x in self._call_stack[: self._len])) def __eq__(self, other): if other is self: return True if type(other) is not IndexedComponent_slice: return False return tuple(_freeze(x) for x in self._call_stack[: self._len]) == tuple( _freeze(x) for x in other._call_stack[: other._len] ) def __ne__(self, other): return not self.__eq__(other) def duplicate(self): ans = IndexedComponent_slice(self) ans._call_stack = ans._call_stack[: ans._len] return ans def index_wildcard_keys(self, sort): _iter = _IndexedComponent_slice_iter(self, iter_over_index=True, sort=sort) return (_iter.get_last_index_wildcards() for _ in _iter) def wildcard_keys(self, sort=SortComponents.UNSORTED): _iter = _IndexedComponent_slice_iter(self, sort=sort) return (_iter.get_last_index_wildcards() for _ in _iter)
[docs] def wildcard_values(self, sort=SortComponents.UNSORTED): """Return an iterator over this slice""" return _IndexedComponent_slice_iter(self, sort=sort)
def wildcard_items(self, sort=SortComponents.UNSORTED): _iter = _IndexedComponent_slice_iter(self, sort=sort) return ((_iter.get_last_index_wildcards(), _) for _ in _iter) def expanded_keys(self): _iter = self.__iter__() return (_iter.get_last_index() for _ in _iter) def expanded_items(self): _iter = self.__iter__() return ((_iter.get_last_index(), _) for _ in _iter)
def _freeze(info): if info[0] == IndexedComponent_slice.slice_info: return ( info[0], id(info[1][0]), # id of the Component tuple(info[1][1].items()), # {idx: value} for fixed tuple(info[1][2].keys()), # {idx: slice} for slices info[1][3], # ellipsis index ) elif info[0] & IndexedComponent_slice.ITEM_MASK: if type(info[1]) is not tuple: index = (info[1],) else: index = info[1] return ( info[0], tuple((x.start, x.stop, x.step) if type(x) is slice else x for x in index), info[2:], ) else: return info class _slice_generator: """Utility (iterator) for generating the elements of one slice Iterate through the component index and yield the component data values that match the slice template. """ def __init__(self, component, fixed, sliced, ellipsis, iter_over_index, sort): self.component = component self.fixed = fixed self.sliced = sliced self.ellipsis = ellipsis self.iter_over_index = iter_over_index # Cache for the most recent index returned. This is used to # iterate over keys of the slice (for instance, in a # _ReferenceDict). self.last_index = () self.tuplize_unflattened_index = ( len(list(self.component.index_set().subsets())) <= 1 ) if fixed is None and sliced is None and ellipsis is None: # This is a slice rooted at a concrete component. This is # unusual (i.e., it is generated programmatically and not # through the normal slice/ellipsis notation). We will mock # up a "generator" so that the slice can be cleanly iterated # over self.explicit_index_count = 0 self.component_iter = _NotIterable return self.explicit_index_count = len(fixed) + len(sliced) if iter_over_index and component.index_set().isfinite(): # This should be used to iterate over all the potential # indices of a sparse IndexedComponent. if SortComponents.SORTED_INDICES in sort: self.component_iter = component.index_set().sorted_iter() elif SortComponents.ORDERED_INDICES in sort: self.component_iter = component.index_set().ordered_iter() else: self.component_iter = iter(component.index_set()) else: # The default behavior is to iterate over the component. self.component_iter = component.keys(sort) def next(self): """__next__() iterator for Py2 compatibility""" return self.__next__() def __next__(self): # We have to defer this import to here to resolve circular # imports. Ideally, we would move normalize_index to another # module to resolve this. from .indexed_component import normalize_index if self.component_iter is _NotIterable: # Special case handling for "slices" rooted at concrete # components. We will replace the iterator with an "empty" # iterator so that the next call will raise StopIteration self.component_iter = iter(()) return self.component while 1: # Note: running off the end of the underlying iterator will # generate a StopIteration exception that will propagate up # and end this iterator. index = next(self.component_iter) # We want a tuple of indices, so convert scalars to tuples if normalize_index.flatten: _idx = index if type(index) is tuple else (index,) elif self.tuplize_unflattened_index: _idx = (index,) else: _idx = index # Verify the number of indices: if there is a wildcard # slice, then there must be enough indices to at least match # the fixed indices. Without the wildcard slice (ellipsis), # the number of indices must match exactly. if self.ellipsis is not None: if self.explicit_index_count > len(_idx): continue elif len(_idx) != self.explicit_index_count: continue valid = True for key, val in self.fixed.items(): # If this index of the component does not match all # the specified fixed indices, don't return anything. if not val == _idx[key]: valid = False break if valid: # Remember the index tuple corresponding to the last # component data returned by this iterator. In this way # we can use the cached indices to iterate over "indices" # of a slice. # # last_index is the most recent index encountered, not # the last index that will ever be encountered. self.last_index = _idx # Note: it is important to use __getitem__, as the # derived class may implement a non-standard storage # mechanism (e.g., Param) if (not self.iter_over_index) or index in self.component: # If iter_over_index is False, we are iterating over # the component ("filled-in" indices only). Since # `advance_iter` was called on the component iter, # we already know index is in self.component. return self.component[index] else: # If iter_over_index is True, we need to return # something even when index is not actually in # self.component. We will (arbitrarily) return # None. return None # Backwards compatibility _IndexedComponent_slice = IndexedComponent_slice # Mock up a callable object with a "check_complete" method def _advance_iter(_iter): return next(_iter) def _advance_iter_check_complete(): pass _advance_iter.check_complete = _advance_iter_check_complete # A dummy class that we can use as a named entity below class _NotIterable: pass class _IndexedComponent_slice_iter: def __init__( self, component_slice, advance_iter=_advance_iter, iter_over_index=False, sort=False, ): # _iter_stack holds a list of elements X where X is either a # _slice_generator iterator (if this level in the hierarchy is a # slice) or None (if this level is either a SimpleComponent, # attribute, method, or is explicitly indexed). self._slice = component_slice self.advance_iter = advance_iter self._iter_over_index = iter_over_index self._sort = SortComponents(sort) call_stack = self._slice._call_stack call_stack_len = self._slice._len self._iter_stack = [None] * call_stack_len # Initialize the top of the `_iter_stack` (deepest part of the # model hierarchy): if call_stack[0][0] == IndexedComponent_slice.slice_info: # The root of the _iter_stack is a generator for the # "highest-level slice" (slice closest to the model() block) self._iter_stack[0] = _slice_generator( *call_stack[0][1], iter_over_index=self._iter_over_index, sort=self._sort, ) # call_stack[0][1] is a (fixed, sliced, ellipsis) tuple, where # fixed and sliced are dicts. elif call_stack[0][0] == IndexedComponent_slice.set_item: # This is a special case that happens when calling # `_ReferenceDict.__setitem__` when the call stack consists # of only a `set_item` entry. We need to initialize the # root of _iter_stack to something other than None (so it # doesn't immediately get "popped" off. However, set_item # is not an iterable thing, so we will will use a type flag # to signal this case to __next__below. assert call_stack_len == 1 self._iter_stack[0] = _NotIterable # Something not None else: raise DeveloperError( "Unexpected call_stack flag encountered: %s" % call_stack[0][0] ) def __iter__(self): """This class implements the iterator API""" return self def next(self): """__next__() iterator for Py2 compatibility""" return self.__next__() def __next__(self): """Return the next element in the slice.""" # In each call to this function, idx will initially point # to the bottom of the stack. # # NOTE: We refer to this stack as growing "downward", just like # the model hierarchy to which it refers. idx = len(self._iter_stack) - 1 while True: # Flush out any non-slice levels. Since we initialize # _iter_stack with None, in the first call this will # immediately walk up to the beginning of the _iter_stack # # On subsequent calls, we will walk up only as far as the # "deepest" active (non-exhausted) iterator. Higher-level # iterators could still be active as well, but those index # values will remain constant until we have exhausted this # "deepest" iterator. while self._iter_stack[idx] is None: idx -= 1 # Get the next element in the deepest iterator (active slice) try: if self._iter_stack[idx] is _NotIterable: # This happens when attempting a `set_item` call on # a `_ReferenceDict` whose slice consists of only a # `slice_info` entry. # E.g. # ref = Reference(m.x[:]) # ref._data[1] = 2 # but not # ref = Reference(m.b[:].x[:]) # ref._data['a',1] = 2 # _comp = self._slice._call_stack[0][1][0] # _comp is the component in the slice_info entry # of the call stack else: # Advance the "deepest active iterator" _comp = self.advance_iter(self._iter_stack[idx]) # Note that if we are looking for a specific # wildcard index, that data is stored in # advance_iter() and will be automatically inserted. # # _comp is "local" part of the component we're # looking for. The rest of the component will be # located using the remainder of the iter stack. # # Note that _comp is actually a component data, because # the _slice_generator (_iter_stack[idx]) returns # component datas rather than indices. # The _slice_generator is able to know about its # component because it was created from a "higher- # level" component/slice in the call/iter stack. # A higher-level iterator may still be active, and # this _slice_generator will need to be regenerated # when/if that iterator is advanced. idx += 1 except StopIteration: # We have exhausted the iterator at this level of the # stack if not idx: # Top-level iterator is done. We are done. # (This is how the infinite loop terminates!) raise # Reset the _slice_generator to None so that the next # iteration will walk up to - and advance - the # "next-highest level" iterator. self._iter_stack[idx] = None # Trivial optimization: we now know that the # _iter_stack[idx] is None, so we can preemptively # decrement idx in preparatioon for the next iteration # of this loop. idx -= 1 continue # Walk to the end of the iter/call stacks, constructing a # component to return along the way. The _iter_stack for # all of these levels from idx to the end of the list are # known to be None at this point. while idx < self._slice._len: _call = self._slice._call_stack[idx] if _call[0] == IndexedComponent_slice.get_attribute: try: # Attach attribute to our current component: _comp = getattr(_comp, _call[1]) except AttributeError: # Since we are slicing, we may only be interested in # things that match. We will allow users to # (silently) ignore any attribute errors generated # by concrete indices in the slice hierarchy... if ( self._slice.attribute_errors_generate_exceptions and not self._iter_over_index ): raise # Break from the inner loop; next action will be to # advance the "highest-level iterator" break elif _call[0] == IndexedComponent_slice.get_item: try: # Get the specified index for the current component: _comp = _comp.__getitem__(_call[1]) except LookupError: # Since we are slicing, we may only be # interested in things that match. We will # allow users to (silently) ignore any key # errors generated by concrete indices in the # slice hierarchy... if ( self._slice.key_errors_generate_exceptions and not self._iter_over_index ): raise break # If the index defines a slice, add a slice generator # to the iter_stack: if _comp.__class__ is IndexedComponent_slice: # Extract the _slice_generator (for # efficiency... these are always 1-level slices, # so we don't need the overhead of the # IndexedComponent_slice object) assert _comp._len == 1 self._iter_stack[idx] = _slice_generator( *_comp._call_stack[0][1], iter_over_index=self._iter_over_index, sort=self._sort, ) try: # Advance to get the first component defined # by this slice (so that we have a concrete # context that we can use to descend further # down the model hierarchy): _comp = self.advance_iter(self._iter_stack[idx]) # Note that the iterator will remained # cached for subsequent calls to __next__() # (when it will eventually be exhausted). except StopIteration: # We got a slicer, but the slicer doesn't # match anything. We should break here, # which (due to 'while True' above) will # walk back up to the next iterator and move # on self._iter_stack[idx] = None break else: # `_comp` is a fully qualified component data (i.e., # not a slice). Record None in the _iter_stack # so we note that this level in the stack is not # defined by an iterator (i.e., subsequent calls # should immediately "pop" this level off the # stack and proceed to the next higher level. self._iter_stack[idx] = None elif _call[0] == IndexedComponent_slice.call: try: # Assume the callable "comp" in our hierarchy # returns a component: _comp = _comp(*(_call[1]), **(_call[2])) except: # Since we are slicing, we may only be # interested in things that match. We will # allow users to (silently) ignore any key # errors generated by concrete indices in the # slice hierarchy... if ( self._slice.call_errors_generate_exceptions and not self._iter_over_index ): raise break elif _call[0] == IndexedComponent_slice.set_attribute: # set_attribute should only appear at the deepest # point (end) of the call stack assert idx == self._slice._len - 1 try: # set attribute of this component: _comp = setattr(_comp, _call[1], _call[2]) # If we want to support __setattr__ with "vector" # arguments, e.g. # `m.b[:].v.value = [1,2,3]` or # `m.b[:].v[1].value = m.b[:].v[0].value`, # this will need to be modified to # cache an iterator over _call[2]. except AttributeError: # Since we are slicing, we may only be interested in # things that match. We will allow users to # (silently) ignore any attribute errors generated # by concrete indices in the slice hierarchy... if self._slice.attribute_errors_generate_exceptions: raise break elif _call[0] == IndexedComponent_slice.set_item: # `set_item` must always appear at the deepest # point (end) of the call stack assert idx == self._slice._len - 1 # We have a somewhat unusual situation when someone # makes a _ReferenceDict to m.x[:] and then wants to # set one of the items. In that situation, # there is only one level in the _call_stack, and we # need to iterate over it here (so we do not allow # the outer portion of this loop to handle the # iteration). This is indicated by setting the # _iter_stack value to _NotIterable. if self._iter_stack[idx] is _NotIterable: _iter = _slice_generator( *_call[1], iter_over_index=self._iter_over_index, sort=self._sort, ) while True: # This ends when the _slice_generator raises # a StopIteration exception self.advance_iter(_iter) # Check to make sure the custom iterator # (i.e._fill_in_known_wildcards) is complete self.advance_iter.check_complete() _comp[_iter.last_index] = _call[2] # The problem here is that _call[1] may be a slice. # If it is, but we are in something like a # _ReferenceDict, where the caller actually wants a # specific index from the slice, we cannot simply # set every element of the slice. Instead, we will # look for the component (generating a slice if # appropriate). If it returns a slice, we can use # our current advance_iter to walk it and set only # the appropriate keys try: _tmp = _comp.__getitem__(_call[1]) except KeyError: # Since we are slicing, we may only be # interested in things that match. We will # allow users to (silently) ignore any key # errors generated by concrete indices in the # slice hierarchy... if ( self._slice.key_errors_generate_exceptions and not self._iter_over_index ): raise break if _tmp.__class__ is IndexedComponent_slice: # Extract the _slice_generator and evaluate it. assert _tmp._len == 1 _iter = _IndexedComponent_slice_iter( _tmp, self.advance_iter, sort=self._sort ) for _ in _iter: # Check to make sure the custom iterator # (i.e._fill_in_known_wildcards) is complete self.advance_iter.check_complete() _comp[_iter.get_last_index()] = _call[2] break else: # Check to make sure the custom iterator # (i.e._fill_in_known_wildcards) is complete self.advance_iter.check_complete() # No try-catch, since we know this key is valid _comp[_call[1]] = _call[2] # If we want to support vectorized set_item, e.g. # `m.b[:].v[1] = m.b[:].v[0]`, # we need to cache an iterator over _call[2]. elif _call[0] == IndexedComponent_slice.del_item: assert idx == self._slice._len - 1 # The problem here is that _call[1] may be a slice. # If it is, but we are in something like a # _ReferenceDict, where the caller actually wants a # specific index from the slice, we cannot simply # delete the slice from the component. Instead, we # will look for the component (generating a slice if # appropriate). If it returns a slice, we can use # our current advance_iter to walk it and delete the # appropriate keys try: _tmp = _comp.__getitem__(_call[1]) except KeyError: # Since we are slicing, we may only be # interested in things that match. We will # allow users to (silently) ignore any key # errors generated by concrete indices in the # slice hierarchy... if self._slice.key_errors_generate_exceptions: raise break if _tmp.__class__ is IndexedComponent_slice: # Extract the _slice_generator and evaluate it. assert _tmp._len == 1 _iter = _IndexedComponent_slice_iter( _tmp, self.advance_iter, sort=self._sort ) _idx_to_del = [] # Two passes, so that we don't edit the _data # dicts while we are iterating over them for _ in _iter: _idx_to_del.append(_iter.get_last_index()) # Check to make sure the custom iterator # (i.e._fill_in_known_wildcards) is complete self.advance_iter.check_complete() for _idx in _idx_to_del: del _comp[_idx] break else: # No try-catch, since we know this key is valid del _comp[_call[1]] elif _call[0] == IndexedComponent_slice.del_attribute: assert idx == self._slice._len - 1 try: _comp = delattr(_comp, _call[1]) except AttributeError: # Since we are slicing, we may only be interested in # things that match. We will allow users to # (silently) ignore any attribute errors generated # by concrete indices in the slice hierarchy... if self._slice.attribute_errors_generate_exceptions: raise break else: raise DeveloperError( "Unexpected entry in IndexedComponent_slice " "_call_stack: %s" % (_call[0],) ) idx += 1 if idx == self._slice._len: # Check to make sure the custom iterator # (i.e._fill_in_known_wildcards) is complete self.advance_iter.check_complete() # We have a concrete object at the end of the chain. Return it return _comp def get_last_index(self): ans = sum((x.last_index for x in self._iter_stack if x is not None), ()) if len(ans) == 1: return ans[0] else: return ans def get_last_index_wildcards(self): """Get a tuple of the values in the wildcard positions for the most recent indices corresponding to the last component returned by each _slice_generator in the iter stack. """ # This method is how we iterate over keys. # # last_index is the index corresponding to the most recent # component data returned by the corresponding _slice_generator. # Extract the indices corresponding to the wildcard positions # for that slice. ans = sum( ( tuple( x.last_index[i] for i in range(len(x.last_index)) if i not in x.fixed ) for x in self._iter_stack if x is not None ), (), ) if not ans: return UnindexedComponent_index if len(ans) == 1: return ans[0] else: return ans