iteritems
on a dict
can useful.
Occasionally iteritems
is useful for a slice of a list and this effect can be (crudely) implemented as follows:
class List(list):
def iteritems(self, slice=None):
if slice is None: return enumerate(self)
else: return itertools.izip(range(*slice.indices(len(self))), self[slice])
if __name__ == "__main__":
l=List("hAnGtEn")
print l
print list(l.iteritems())
print list(l.iteritems(slice(1,None,2)))
Output:
['h', 'A', 'n', 'G', 't', 'E', 'n']
[(0, 'h'), (1, 'A'), (2, 'n'), (3, 'G'), (4, 't'), (5, 'E'), (6, 'n')]
[(1, 'A'), (3, 'G'), (5, 'E')]
Is there a more "pythonic" list slicing syntax that should be used?
This:
range(slice.start,slice.stop,slice.step)
does not handle certain special cases very well: e.g. where stop=-1
, start=None
or step=None
. How can the example range/slice implementation be also improved?
edit:
range(slice.start,slice.stop,slice.step)
is better handled with:
range(*slice.indices(len(self)))
3 Answers 3
Instead of
range(slice.start,slice.stop,slice.step)
you could use this expression that handles the special cases too
range(len(self))[slice]
(This works with range
on both Python 2 and 3, but not with Python 2 xrange
even though it is mostly equivalent to Python 3 range
)
-
2\$\begingroup\$ I just figured out the "indicies" does a better job of range slicing: e.g.
range(*slice.indices(len(self))
\$\endgroup\$NevilleDNZ– NevilleDNZ2014年11月27日 21:25:11 +00:00Commented Nov 27, 2014 at 21:25 -
1\$\begingroup\$ @NevilleDNZ Good find! That works even with
xrange
\$\endgroup\$Janne Karila– Janne Karila2014年11月28日 06:41:25 +00:00Commented Nov 28, 2014 at 6:41
How about:
class IterItems(object):
def __init__(self, master_l):
self.master_l=master_l
return super(IterItems,self).__init__()
def __call__(self, *sss_l):
if not sss_l: return enumerate(self.master_l)
else: return self[slice(*sss_l)]
def __getitem__(self,sss):
if not isinstance(sss, slice):
yield sss,self.master_l[sss]
else:
sss_l=sss.indices(len(self.master_l))
for key in range(*sss_l): yield key,self.master_l[key]
class ListItems(list):
def __init__(self, *arg_l, **arg_d):
self.iteritems=IterItems(self)
return super(ListItems,self).__init__(*arg_l, **arg_d)
if __name__ == "__main__":
l=ListItems("hAnGtEn")
print list(l.iteritems())
print list(l.iteritems[1::2])
for item in l.iteritems[1::2]: print item,
print
Output:
[(0, 'h'), (1, 'A'), (2, 'n'), (3, 'G'), (4, 't'), (5, 'E'), (6, 'n')]
[(1, 'A'), (3, 'G'), (5, 'E')]
(1, 'A') (3, 'G') (5, 'E')
This new class ListItems
works for an arbitrarily large list, providing a useful iterator that avoids loading the entire "list" into memory.
Or (if given it is feasible to short sub-slice the original long list) try...
>>> l="hAnGtEn"
>>> sss=slice(1,None,2)
>>> zip(l[sss],xrange(*sss.indices(len(l))))
[('A', 1), ('G', 3), ('E', 5)]
Or (using strict iteration):
>>> import itertools
>>> l="hAnGtEn"
>>> sss=slice(1,None,2)
>>> sss=sss.indices(len(l))
>>> list(itertools.izip(itertools.islice(l,*sss),xrange(*sss)))
[('A', 1), ('G', 3), ('E', 5)]
The more pythonic way is to use enumerate()
:
>>> l = 'hAnGtEn'
>>> l
'hAnGtEn'
>>> list(enumerate(l))
[(0, 'h'), (1, 'A'), (2, 'n'), (3, 'G'), (4, 't'), (5, 'E'), (6, 'n')]
>>> list(enumerate(l))[1::2]
[(1, 'A'), (3, 'G'), (5, 'E')]
>>>
-
\$\begingroup\$ The original already uses
enumerate
. \$\endgroup\$Janne Karila– Janne Karila2014年11月27日 09:56:44 +00:00Commented Nov 27, 2014 at 9:56 -
\$\begingroup\$ You are right. I didn't study it fully. I was making the point that enumerate alone is sufficient rather than defining a new class. \$\endgroup\$Arvind Padmanabhan– Arvind Padmanabhan2014年11月27日 11:15:37 +00:00Commented Nov 27, 2014 at 11:15
-
\$\begingroup\$ The output is perfect, but I was hoping to avoid enumerating the entire (million item) list. (Hence the use of a selective iteritems) \$\endgroup\$NevilleDNZ– NevilleDNZ2014年11月27日 12:41:09 +00:00Commented Nov 27, 2014 at 12:41