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.
Related
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
My question comes from this page, while I would like to create a pointer(-like thing) for an list element. The element is a primitive value (string) so I have to create a FooWrapper class as that page says.
I know that by setting __repr__ one can directly access this value.
class FooWrapper(object):
def __init__(self, value):
self.value = value
def __repr__(self):
return repr(self.value)
>>> bar=FooWrapper('ABC')
>>> bar
'ABC'
>>> bar=FooWrapper(3)
>>> bar
3
Now I can use it as an reference of string:
>>> L=[3,5,6,9]
>>> L[1]=FooWrapper('ABC')
>>> L
[3,'ABC',6,9]
>>> this=L[1]
>>> this.value='BCD'
>>> print(L)
[3,'BCD',6,9]
So now I have a pointer-like this for the list element L[1].
However it is still inconvenient since I must use this.value='BCD' to change its value. While there exists a __repr__ method to make this directly return this.value, is there any similar method to make this='BCD' to do this.value='BCD' ? I know this changes the rule of binding.. but anyway, is it possible?
I would also appreciate if there is a better solution for a list element pointer.
Thank you in advance:)
I'm not sure exactly what you are trying to do, but you could do something like:
class FooWrapper(object):
def __init__(self, value):
self.value = value
def __repr__(self):
return 'FooWrapper(' + repr(self.value) + ')'
def __str__(self):
return str(self.value)
def __call__(self,value):
self.value = value
Here I got rid of your idea of using __repr__ to hide FooWrapper since I think it a bad idea to hide from the programmer what is happening at the REPL. Instead -- I used __str__ so that when you print the object you will print the wrapped value. The __call__ functions as a default method, which doesn't change the meaning of = but is sort of what you want:
>>> vals = [1,2,3]
>>> vals[1] = FooWrapper("Bob")
>>> vals
[1, FooWrapper('Bob'), 3]
>>> for x in vals: print(x)
1
Bob
3
>>> this = vals[1]
>>> this(10)
>>> vals
[1, FooWrapper(10), 3]
However, I think it misleading to refer to this as a pointer. It is just a wrapper object, and is almost certain to make dealing with the wrapped object inconvenient.
On Edit: The following is more of a pointer to a list. It allows you to create something like a pointer object with __call__ used to dereference the pointer (when no argument is passed to it) or to mutate the list (when a value is passed to __call__). It also implements a form of p++ called (pp) with wrap-around (though the wrap-around part could of course be dropped):
class ListPointer(object):
def __init__(self, myList,i=0):
self.myList = myList
self.i = i % len(self.myList)
def __repr__(self):
return 'ListPointer(' + repr(self.myList) + ',' + str(self.i) + ')'
def __str__(self):
return str(self.myList[self.i])
def __call__(self,*value):
if len(value) == 0:
return self.myList[self.i]
else:
self.myList[self.i] = value[0]
def pp(self):
self.i = (self.i + 1) % len(self.myList)
Used like this:
>>> vals = ['a','b','c']
>>> this = ListPointer(vals)
>>> this()
'a'
>>> this('d')
>>> vals
['d', 'b', 'c']
>>> this.pp()
>>> this()
'b'
>>> print(this)
b
I think that this is a more transparent way of getting something which acts like a list pointer. It doesn't require the thing pointed to to be wrapped in anything.
The __repr__ method can get a string however it wants to. Let's say it says return repr(self.value) + 'here'. If you say this = '4here', what should be affected? Should self.value be assigned to 4 or 4here? What if this had another attribute called key and __repr__ did return repr(self.key) + repr(self.value)? When you did this = '4here', would it assign self.key to the whole string, assign self.value to the whole string, or assign self.key to 4 and self.value to here? What if the string is completely made up by the method? If it says return 'here', what should this = '4here' do?
In short, you can't.
Self-Answer based on John Coleman's idea:
class ListPointer(object):
def __init__(self,list,index):
self.value=list[index]
list[index]=self
def __repr__(self):
return self.value
def __call__(self,value):
self.value=value
>>> foo=[2,3,4,5]
>>> this=ListPointer(foo,2)
>>> this
4
>>> foo
[2,3,4,5]
>>> this('ABC')
>>> foo
[2,3,'ABC',5]
>>> type(foo(2))
<class '__main__.ListPointer'>
ListPointer object accepts a list and an index, stores the list[index] in self.value and then substitutes the list element with self. Similarly a pp method can also be achieved, while the former element should be restored and the next element be substituted with self object. By directly referring foo[2] one also gets this object, which is what I want. (Perhaps this should be called as a reference rather than a pointer..)
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 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
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.