I work on a class with and embedded list.
class a:
def __init__(self, n):
self.l = [1] * n
def __getitem__(self, i):
return self.l[i]
def __delitem__(self, i):
print type(i)
print i
I want to use the del operator with the full syntax of slices:
p = a(10)
del p[1:5:2]
The __delitem__ receives a slice object if the parameter is not a single index. How can I use the slice object to iterate through the specified elements?
The indices method of the slice object will, given the length of the sequence, provide the canonical interpretation of the slice that you can feed to xrange:
def __delitem__(self, item):
if isinstance(item, slice):
for i in xrange(*item.indices(len(self.l))):
print i
else:
print operator.index(item)
The use of slice.indices makes sure that you get correct behavior in cases pointed out by Dunes. Also note that you can pass the slice object to list.__delitem__, so if you only need to do some preprocessing and delegate actual deletion to the underlying list, a "naive" del self.l[i] will in fact work correctly.
operator.index will make sure that you get an early exception if your __delitem__ receives a non-slice object that cannot be converted to an index.
slice objects have start, stop, and step attributes that you can use to get each of those components. For example:
def __delitem__(self, i):
if isinstance(i, slice):
for j in xrange(i.start, i.stop, i.step):
print j
else:
print i
Related
I am trying to implement slice functionality for a class I am making that creates a vector representation.
I have this code so far, which I believe will properly implement the slice but whenever I do something like v[4] where v is a vector, python raises an error about not having enough arguments. So I am trying to figure out how to define the __getitem__ special method in my class to handle both plain indexes and slicing.
def __getitem__(self, start, stop, step):
index = start
if stop == None:
end = start + 1
else:
end = stop
if step == None:
stride = 1
else:
stride = step
return self.__data[index:end:stride]
The __getitem__() method will receive a slice object when the object is sliced. Simply look at the start, stop, and step members of the slice object in order to get the components for the slice.
>>> class C(object):
... def __getitem__(self, val):
... print val
...
>>> c = C()
>>> c[3]
3
>>> c[3:4]
slice(3, 4, None)
>>> c[3:4:-2]
slice(3, 4, -2)
>>> c[():1j:'a']
slice((), 1j, 'a')
I have a "synthetic" list (one where the data is larger than you would want to create in memory) and my __getitem__ looks like this:
def __getitem__(self, key):
if isinstance(key, slice):
# Get the start, stop, and step from the slice
return [self[ii] for ii in xrange(*key.indices(len(self)))]
elif isinstance(key, int):
if key < 0: # Handle negative indices
key += len(self)
if key < 0 or key >= len(self):
raise IndexError, "The index (%d) is out of range." % key
return self.getData(key) # Get the data from elsewhere
else:
raise TypeError, "Invalid argument type."
The slice doesn't return the same type, which is a no-no, but it works for me.
How to define the getitem class to handle both plain indexes and slicing?
Slice objects gets automatically created when you use a colon in the subscript notation - and that is what is passed to __getitem__. Use isinstance to check if you have a slice object:
from __future__ import print_function
class Sliceable(object):
def __getitem__(self, subscript):
if isinstance(subscript, slice):
# do your handling for a slice object:
print(subscript.start, subscript.stop, subscript.step)
else:
# Do your handling for a plain index
print(subscript)
Say we were using a range object, but we want slices to return lists instead of new range objects (as it does):
>>> range(1,100, 4)[::-1]
range(97, -3, -4)
We can't subclass range because of internal limitations, but we can delegate to it:
class Range:
"""like builtin range, but when sliced gives a list"""
__slots__ = "_range"
def __init__(self, *args):
self._range = range(*args) # takes no keyword arguments.
def __getattr__(self, name):
return getattr(self._range, name)
def __getitem__(self, subscript):
result = self._range.__getitem__(subscript)
if isinstance(subscript, slice):
return list(result)
else:
return result
r = Range(100)
We don't have a perfectly replaceable Range object, but it's fairly close:
>>> r[1:3]
[1, 2]
>>> r[1]
1
>>> 2 in r
True
>>> r.count(3)
1
To better understand the slice notation, here's example usage of Sliceable:
>>> sliceme = Sliceable()
>>> sliceme[1]
1
>>> sliceme[2]
2
>>> sliceme[:]
None None None
>>> sliceme[1:]
1 None None
>>> sliceme[1:2]
1 2 None
>>> sliceme[1:2:3]
1 2 3
>>> sliceme[:2:3]
None 2 3
>>> sliceme[::3]
None None 3
>>> sliceme[::]
None None None
>>> sliceme[:]
None None None
Python 2, be aware:
In Python 2, there's a deprecated method that you may need to override when subclassing some builtin types.
From the datamodel documentation:
object.__getslice__(self, i, j)
Deprecated since version 2.0: Support slice objects as parameters to the __getitem__() method. (However, built-in types in CPython currently still implement __getslice__(). Therefore, you have to override it in derived classes when implementing slicing.)
This is gone in Python 3.
To extend Aaron's answer, for things like numpy, you can do multi-dimensional slicing by checking to see if given is a tuple:
class Sliceable(object):
def __getitem__(self, given):
if isinstance(given, slice):
# do your handling for a slice object:
print("slice", given.start, given.stop, given.step)
elif isinstance(given, tuple):
print("multidim", given)
else:
# Do your handling for a plain index
print("plain", given)
sliceme = Sliceable()
sliceme[1]
sliceme[::]
sliceme[1:, ::2]
```
Output:
('plain', 1)
('slice', None, None, None)
('multidim', (slice(1, None, None), slice(None, None, 2)))
The correct way to do this is to have __getitem__ take one parameter, which can either be a number or a slice object.
so I can't figure out how to overload the __getitem__ and __setitem__ operators for double indexing in my class. I'm trying to iterate over a user-defined instance attribute, which is a collection of 2-tuples, for example self.coeffs = ((6,2),(5,5),(1,8)), where the first index is a 2-tuple and the second index is either the first or second item in the 2-tuple. For the previous object, self.coeffs[2][0] would be 1.
I'm pretty sure I understand how to set up the methods for a single index, but I don't know how to get Python to consider the second index call.
Here's a section of my class (self.coeffs is the instance attribute made from user-defined tuples):
Where the class is first calling the index operators:
`def __str__(self):
if self.__coeffs[0][1] == 0: #TypeError: 'int' object isn't subscriptable
return str((self.__coeffs)[0][0])`
My current index operators:
`def __getitem__(self, idx):
for t in self.__coeffs:
if t[1] == idx:
return t[0]
if idx not in [t[1] for t in self.__coeffs]:
return 0
def __setitem__(self, idx, value):
for t in self.__coeffs:
if t[1] == idx:
t[0] = value
if idx not in [t[1] for t in self.__coeffs]:
self.addterm(value, exp)`
Also, I have to have the single parameter idx for __getitem__, I can't pass in more than that single argument. If anyone knows how to work around this or has any suggestions, it will be greatly appreciated, thanks.
The object that is returned, by the first __getitem__ needs to implement the __getitem__ too. The error occurs because at one point you return 0, which is an integer and as the error says: 'int' object is not subscriptable.
To answer your question more precise. The implementation depends on the data that is stored.
The simplest case: 2d array
If you store a 2d in your class, you can return it like this:
# self.data[[0,1],[0,1],[34,234,234,234]]
def __getitem__(self, idx):
return self.data[idx]
You have to make sure, that you always return another list.
Other objects
If you want to return another object of a class, that object needs to implement the __getitem__ method.
class A:
# self.data = [1,2,3,67]
def __getitem__(self, idx):
return self.data[idx]
class B:
# self.data = [A(), A(), A()]
def __getitem__(self, idx):
return self.data[idx]
The same things apply to the __setitem__ function
I work on a class with and embedded list.
class a:
def __init__(self, n):
self.l = [1] * n
def __getitem__(self, i):
return self.l[i]
def __delitem__(self, i):
print type(i)
print i
I want to use the del operator with the full syntax of slices:
p = a(10)
del p[1:5:2]
The __delitem__ receives a slice object if the parameter is not a single index. How can I use the slice object to iterate through the specified elements?
The indices method of the slice object will, given the length of the sequence, provide the canonical interpretation of the slice that you can feed to xrange:
def __delitem__(self, item):
if isinstance(item, slice):
for i in xrange(*item.indices(len(self.l))):
print i
else:
print operator.index(item)
The use of slice.indices makes sure that you get correct behavior in cases pointed out by Dunes. Also note that you can pass the slice object to list.__delitem__, so if you only need to do some preprocessing and delegate actual deletion to the underlying list, a "naive" del self.l[i] will in fact work correctly.
operator.index will make sure that you get an early exception if your __delitem__ receives a non-slice object that cannot be converted to an index.
slice objects have start, stop, and step attributes that you can use to get each of those components. For example:
def __delitem__(self, i):
if isinstance(i, slice):
for j in xrange(i.start, i.stop, i.step):
print j
else:
print i
I have a class that looks like this
Class myClass:
def __init__(self, key, value):
self.key = key
self.value = value
where key is a string and value is always a list of elements of myClass, possibly empty.
I want to define my own iter method that returns value.key for each value in values. I tried
def __iter__(self):
return self
def __next__(self):
try:
self.value.next().key
except:
raise StopIteration
but it's looping forever. What am I doing wrong?
Also if I want to have Python 2 / 3 compatibility, should I add the method
def next(self):
return self.__next__()
There's no reason for you to implement __next__. You can use __iter__ to return a generator which will do what you want.
class Pair(object):
def __init__(self, key, value):
self.key = key
self.value = value
def __iter__(self):
return (v.key for v in self.value)
# alternative iter function, that allows more complicated logic
def __iter__(self):
for v in self.value:
yield v.key
p = Pair("parent", [Pair("child0", "value0"), Pair("child1", "value1")])
assert list(p) == ["child0", "child1"]
This way of doing things is compatible with both python2 and python3 as the returned generator will have the required next function in python2, and __next__ in python3.
You need to extract and preserve an iterator on list self.value -- you can't just call next on a list, you need an iterator on such a list.
So, you need an auxiliary iterator class:
class myClassIter(object):
def __init__(self, theiter):
self.theiter = theiter
def __next__(self):
return next(self.theiter).key
next = __next__
which I've also made Py 2/3 compatible with the object base and appropriate aliasing.
Here, I'm assuming every item in the list has a key attribute (so the only expected exception is StopIteration, which you can just propagate). If that is not the case, and you want to just stop the iteration when an item is met without the attribite, the try/except is needed, but keep it tight! -- a crucial design aspect of good exception handling. I.e, if these are indeed your specs:
def __next__(self):
try: return next(self.theiter).key
except AttributeError: raise StopIteration
don't catch all exceptions -- only the ones you specifically expect!
Now, in myClass, you'll want:
def __iter__(self):
return myClassIter(iter(self.value))
This means that myClass is an iterable, not an iterator, so you can e.g properly have more than one loop on a myClass instance:
mc = myClass(somekey, funkylist)
for ka in mc:
for kb in mc:
whatever(ka, kb)
If mc was itself an iterator, the inner loop would exhaust it and the semantics of the nested loops would therefore be completely different.
If you do indeed want such completely different semantics (i.e you want mc to be itself an iterator, not just an iterable) then you must dispense with the auxiliary class (but still need to store the iterator on self.value as an instance attribute for myClass) -- that would be a strange, uncomfortable arrangement, but it is (just barely) possible that it is indeed the arrangement your application needs...
Is it possible to have a list be evaluated lazily in Python?
For example
a = 1
list = [a]
print list
#[1]
a = 2
print list
#[1]
If the list was set to evaluate lazily then the final line would be [2]
The concept of "lazy" evaluation normally comes with functional languages -- but in those you could not reassign two different values to the same identifier, so, not even there could your example be reproduced.
The point is not about laziness at all -- it is that using an identifier is guaranteed to be identical to getting a reference to the same value that identifier is referencing, and re-assigning an identifier, a bare name, to a different value, is guaranteed to make the identifier refer to a different value from them on. The reference to the first value (object) is not lost.
Consider a similar example where re-assignment to a bare name is not in play, but rather any other kind of mutation (for a mutable object, of course -- numbers and strings are immutable), including an assignment to something else than a bare name:
>>> a = [1]
>>> list = [a]
>>> print list
[[1]]
>>> a[:] = [2]
>>> print list
[[2]]
Since there is no a - ... that reassigns the bare name a, but rather an a[:] = ... that reassigns a's contents, it's trivially easy to make Python as "lazy" as you wish (and indeed it would take some effort to make it "eager"!-)... if laziness vs eagerness had anything to do with either of these cases (which it doesn't;-).
Just be aware of the perfectly simple semantics of "assigning to a bare name" (vs assigning to anything else, which can be variously tweaked and controlled by using your own types appropriately), and the optical illusion of "lazy vs eager" might hopefully vanish;-)
Came across this post when looking for a genuine lazy list implementation, but it sounded like a fun thing to try and work out.
The following implementation does basically what was originally asked for:
from collections import Sequence
class LazyClosureSequence(Sequence):
def __init__(self, get_items):
self._get_items = get_items
def __getitem__(self, i):
return self._get_items()[i]
def __len__(self):
return len(self._get_items())
def __repr__(self):
return repr(self._get_items())
You use it like this:
>>> a = 1
>>> l = LazyClosureSequence(lambda: [a])
>>> print l
[1]
>>> a = 2
>>> print l
[2]
This is obviously horrible.
Python is not really very lazy in general.
You can use generators to emulate lazy data structures (like infinite lists, et cetera), but as far as things like using normal list syntax, et cetera, you're not going to have laziness.
That is a read-only lazy list where it only needs a pre-defined length and a cache-update function:
import copy
import operations
from collections.abc import Sequence
from functools import partialmethod
from typing import Dict, Union
def _cmp_list(a: list, b: list, op, if_eq: bool, if_long_a: bool) -> bool:
"""utility to implement gt|ge|lt|le class operators"""
if a is b:
return if_eq
for ia, ib in zip(a, b):
if ia == ib:
continue
return op(ia, ib)
la, lb = len(a), len(b)
if la == lb:
return if_eq
if la > lb:
return if_long_a
return not if_long_a
class LazyListView(Sequence):
def __init__(self, length):
self._range = range(length)
self._cache: Dict[int, Value] = {}
def __len__(self) -> int:
return len(self._range)
def __getitem__(self, ix: Union[int, slice]) -> Value:
length = len(self)
if isinstance(ix, slice):
clone = copy.copy(self)
clone._range = self._range[slice(*ix.indices(length))] # slicing
return clone
else:
if ix < 0:
ix += len(self) # negative indices count from the end
if not (0 <= ix < length):
raise IndexError(f"list index {ix} out of range [0, {length})")
if ix not in self._cache:
... # update cache
return self._cache[ix]
def __iter__(self) -> dict:
for i, _row_ix in enumerate(self._range):
yield self[i]
__eq__ = _eq_list
__gt__ = partialmethod(_cmp_list, op=operator.gt, if_eq=False, if_long_a=True)
__ge__ = partialmethod(_cmp_list, op=operator.ge, if_eq=True, if_long_a=True)
__le__ = partialmethod(_cmp_list, op=operator.le, if_eq=True, if_long_a=False)
__lt__ = partialmethod(_cmp_list, op=operator.lt, if_eq=False, if_long_a=False)
def __add__(self, other):
"""BREAKS laziness and returns a plain-list"""
return list(self) + other
def __mul__(self, factor):
"""BREAKS laziness and returns a plain-list"""
return list(self) * factor
__radd__ = __add__
__rmul__ = __mul__
Note that this class is discussed also in this SO.