Does reverse actually reverse a Python iterator? - python

So I can create a reverse iterator on a list:
list(reversed([0,1,2,3]))
[3, 2, 1, 0]
I assume this simply calls getitem from index len(...)-1 to 0. But then I cannot also do this:
list(reversed(xrange(4)))
[3, 2, 1, 0]
Now I am a bit confused. Does this create the list from xrange(4) and then reverse it? If not, how does it know what the last element is and how to go backwards? I read the documentation but it didn't help.

reversed() looks for a __reversed__ special method on the object. List objects provide this, and so does xrange():
>>> xrange(4).__reversed__()
<rangeiterator object at 0x106e2fa50>
The iterator object simply produces the values in reverse, no list object is produced.
For objects that do not implement __reversed__, the reversed() function uses the length and the __getitem__ method; e.g. reversed() is essentially equivalent to:
def reversed(seq):
try:
return seq.__reversed__()
except AttributeError:
return (seq[i] for i in xrange(len(seq) - 1, -1 , -1))
where the second part is a generator expression, evaluating lazily. The generator then accesses each item in turn starting at index (length - 1) all the way down to index 0.

reversed() can only take a sequence -- if it took generic iterators, it couldn't know what the final value was without exhausting the iterator and storing all the values.
Luckily, xrange returns an xrange object that works as a sequence:
>>> x = xrange(10)
>>> len(x)
10
>>> x[9]
9
It also happens to have an actual __reversed__ method, but that's a special case of having all the sequence methods.

Just compare the two:
In [2]: reversed(xrange(4))
Out[2]: <rangeiterator at 0x7fa83291bde0>
In [3]: list(reversed(xrange(4)))
Out[3]: [3, 2, 1, 0]
In [4]: reversed([0,1,2,3])
Out[4]: <listreverseiterator at 0x7fa8328be2d0>
In [5]: list(reversed([0,1,2,3]))
Out[5]: [3, 2, 1, 0]

Related

How to iterate over a slice?

A slice in python is not iterable. This code:
s = slice(1, 10, 2)
iter(s)
results in this error:
TypeError: 'slice' object is not iterable
This is the code I've come up with to show the slice by creating a list iterable:
list(range(s.start, s.stop, s.step))
This uses the start, stop and step attributes of the slice object. I plug those into a range (an immutable sequence type) and create a list:
[1, 3, 5, 7, 9]
Is there something missing? Can I iterate over a slice any better?
A slice isn't an iterable. It doesn't contain elements, but instead specifies which elements in some other iterable are to be returned if the slice is applied to that iterable.
Since it's not an iterable, you can't iterate over it. As you have discovered, however, you can obtain the indices for which it will return elements from an iterable to which it is applied, using range() - and you can iterate over that:
s = slice(1, 10, 2)
indices = range(s.start, s.stop, s.step)
it = iter(indices)
>>> list(it)
[1, 3, 5, 7, 9]
Convert the slice to a range, then you can iterate the range.
range(10**10)[slice]
The question is more general than the answer. Slice can take floating point numbers as a step where range can only take integers. So more generally:
test=slice(0,1,0.01)
testlist=[x*test.step for x in range(0,int((test.stop-test.start)/test.step+1))]
You may get rounding errors, where alternative's are discussed here
You need to get the indeces from slice object to iterate.
Here is the example:
for x in range(*slice.indices(max_index)):
print(x)

How does sorted() work for lists inside of lists

I am confused on how lists inside lists get sorted.
L = [[1,1,1],[0,9,0],[2,1,1]]
sorted(L) returns [[0, 9, 0], [1, 1, 1], [2, 1, 1]]
This means that it is not based off sum as 0+9+0 is larger than both of the other ones.
No it is based on all the elements of the iterator starting from the first element of the iterator
sorted(L,key=lambda x:(x[0],x[1],x[2]) #==sorted(L)
In case you need by sum
sorted(L,key=sum)
A more simplified version of above code to understand the key argument further
print(sorted(L,key=lambda x: x[0]+x[1]+x[2]))
Built-in sorted considers each element of an iterable in turn. So, for example, [0, 9, 0] appears first because 0 < 1 and 0 < 2.
To help gain intuition, you can test a few examples:
[0,9,0] < [1,1,1] # True
[0,9,0] < [0,8,0] # False
[1,1,1] < [2,1,1] # True
So sorted works consistently with how comparison operators are defined. Sequence objects in Python usually support lexicographic comparison. To sort by the sum of each list, you need to feed a function to the key argument, in this case built-in sum:
res = sorted(L, key=sum)

What is the reason for removing default values of start and step in range() function in Python3?

I am learning Python3 and as I can see in the past, with Python2, it was possible to create a list of numbers with range() by just passing a single argument, which would be the last number of list + 1 in the list:
range(4) # == [0, 1, 2, 3]
and the start and step values were defaulted to 0 and 1.
But in Python3 for some reason, it is no longer possible to omit those 2 arguments and for the same result we would need to wrap the range() function with the list() function:
range(0, 4, 1) # == range(0, 4)
range(4) # == range(0, 4)
list(range(4)) # == [0, 1, 2, 3]
Question(s):
What is the reason behind that? Why was the functionality changed this way? Is there a good reason for that?
P.S. or maybe I am doing something incorrectly. Here, I am talking about range() function in general, whether it is being used in for-loops, just to create a list or for any other purpose
You've unfortunately been misled by the repr of range:
>>> range(4)
range(0, 4)
It is actually the same interface, but this is returning a lazily-generated sequence now, as opposed to a list. You may iterate a range instance to consume the values:
>>> list(range(4))
[0, 1, 2, 3]
See Python range() and zip() object type for more details about this change.

Why does Python have `reversed`?

Why does Python have the built-in function reversed?
Why not just use x[::-1] instead of reversed(x)?
Edit: #TanveerAlam pointed out that reversed is not actually a function, but rather a class, despite being listed on the page Built-in Functions.
>>> a= [1,2,3,4,5,6,7,8,9,10]
>>> a[::-1]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> reversed(a)
<listreverseiterator object at 0x10dbf5390>
The first notation is generating the reverse eagerly; the second is giving you a reverse iterator, which is possibly cheaper to acquire, as it has potential to only generate elements as needed
reversed returns a reverse iterator.
[::-1] asks the object for a slice
Python objects try to return what you probably expect
>>> [1, 2, 3][::-1]
[3, 2, 1]
>>> "123"[::-1]
'321'
This is convenient - particularly for strings and tuples.
But remember the majority of code doesn't need to reverse strings.
The most important role of reversed() is making code easier to read and understand.
The fact that it returns an iterator without creating a new sequence is of secondary importance
From the docs
PEP 322: Reverse Iteration A new built-in function, reversed(seq)(),
takes a sequence and returns an iterator that loops over the elements
of the sequence in reverse order.
>>>
>>> for i in reversed(xrange(1,4)):
... print i
...
3
2
1
Compared to extended slicing, such as range(1,4)[::-1], reversed() is
easier to read, runs faster, and uses substantially less memory.
Note that reversed() only accepts sequences, not arbitrary iterators.
If you want to reverse an iterator, first convert it to a list with
list().
>>>
>>> input = open('/etc/passwd', 'r')
>>> for line in reversed(list(input)):
... print line
...
reversed return a reverse iterator.
x[::-1] return a list.
In [1]: aaa = [1,2,3,4,5]
In [4]: aaa[::-1]
Out[4]: [5, 4, 3, 2, 1]
In [5]: timeit(aaa[::-1])
1000000 loops, best of 3: 206 ns per loop
In [6]: reversed(aaa)
Out[6]: <listreverseiterator at 0x104310d50>
In [7]: timeit(reversed(aaa))
10000000 loops, best of 3: 182 ns per loop
First of all, reversed is not a built-in function.
>>> type(reversed)
<type 'type'>
It's a class which itrates over a sequence and gives a reverse order of sequence.
Try:
>>> help(reversed)
Help on class reversed in module __builtin__:
class reversed(object)
| reversed(sequence) -> reverse iterator over values of the sequence
And when we pass a parameter to it, it acts as a iterator,
>>> l = [1, 2, 3, 4]
>>> obj = reversed(l)
>>> obj
<listreverseiterator object at 0x0220F950>
>>> obj.next()
4
>>> obj.next()
3
>>> obj.next()
2
>>> obj.next()
1
>>> obj.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
whereas a slice operation returns the whole list which is not memory efficient for larger lists.
That is why, in Python 2, we have range (which returns whole list) as well as xrange (which generates each element on every iteration).
>>> l[::-1]
[4, 3, 2, 1]
aList = [123, 'xyz', 'zara', 'abc', 'xyz'];
print type(reversed(aList))
bList = [123, 'xyz', 'zara', 'abc', 'xyz'];
print type(bList[::-1])
Output:
<type 'listreverseiterator'>
<type 'list'>
The reversed function returns a reverse iterator. The [::-1] returns a list.
Because reversed returns an iterator.

Python a = a.reverse makes the list empty?

At the interpreter,
a = [1,2,3,4]
a = a.reverse()
Next when I type a at the interpreter, I get nothing. So it seems a = a.reverse() generates an empty list. Is this by design?
I am using python 2.5 on windows xp.
list.reverse() modifies the list in-place, returns None. But if you want to protect old list, you can use reversed() function for that, it returns an iterator.
In [1]: a=[1,2,3,4]
In [2]: print(a.reverse())
None
In [3]: a
Out[3]: [4, 3, 2, 1]
In [4]: a=[1,2,3,4]
In [5]: print(reversed(a))
<listreverseiterator object at 0x24e7e50>
In [6]: list(reversed(a))
Out[6]: [4, 3, 2, 1]
In [7]: a
Out[7]: [1, 2, 3, 4]
reverse changes list in-place, and doesn't return anything. Thus, this is the expected usage:
a = [1, 2, 3, 4]
a.reverse()
a # => [4, 3, 2, 1]
If you assign the result of reverse back to a, you will overwrite all its hard work with the nonsensical return value (None), which is where your bug comes from.
list is a mutable type, so list operations are in-place, and return None.
The built-in method reverse of a list on python doesn't return the reversed list.
It reverses the list in place.
So, if you want to reverse your list, like in your code, just do:
a = [1,2,3,4]
a.reverse()
list.reverse() just doesn't return anything, because it changes the list in-place. See this example:
>>> a = [1,2,3,4]
>>> a.reverse()
>>> a
[4, 3, 2, 1]
There also is the reversed function (actually a type, but doesn't matter here), which does not change the list in-place, but instead returns an iterator with the list items in the reverse order. Try:
>>> a = [1,2,3,4]
>>> a = list(reversed(a))
>>> a
[4, 3, 2, 1]
I think what you want to do is:
a = [1,2,3,4]
a.reverse()
a is an object and the operations work on it's data, so you don't need to assign again it.
The reverse method does the reverse 'in place' (like sort) and returns None, so after calling a.reverse() a already contains the result.

Categories