Re: [Tutor] Iterator vs. iterable cheatsheet, was Re: iter class

2014-01-24 Thread spir

On 01/25/2014 04:13 AM, eryksun wrote:

On Fri, Jan 24, 2014 at 8:50 AM, spir  wrote:


xs is an iterator (__next__ is there), then Python uses it directly, thus
what is the point of __iter__ there? In any case, python must check whether


Python doesn't check whether a type is already an iterator. It's
simpler to require that iterators implement __iter__, like any other
non-sequence iterable. This technically allows an iterator to return a
new iterator when __iter__ is called:

 class C:
 def __iter__(self): return D()
 def __next__(self): return 'C'

 class D:
 def __iter__(self): return C()
 def __next__(self): return 'D'

 it1 = iter(C())
 it2 = iter(it1)

 >>> next(it1)
 'D'
 >>> next(it2)
 'C'

That said, it's supposed to return self.


All right, thank you, Peter & Eryksun.

d

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Iterator vs. iterable cheatsheet, was Re: iter class

2014-01-24 Thread eryksun
On Fri, Jan 24, 2014 at 8:50 AM, spir  wrote:
>
> xs is an iterator (__next__ is there), then Python uses it directly, thus
> what is the point of __iter__ there? In any case, python must check whether

Python doesn't check whether a type is already an iterator. It's
simpler to require that iterators implement __iter__, like any other
non-sequence iterable. This technically allows an iterator to return a
new iterator when __iter__ is called:

class C:
def __iter__(self): return D()
def __next__(self): return 'C'

class D:
def __iter__(self): return C()
def __next__(self): return 'D'

it1 = iter(C())
it2 = iter(it1)

>>> next(it1)
'D'
>>> next(it2)
'C'

That said, it's supposed to return self.
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Iterator vs. iterable cheatsheet, was Re: iter class

2014-01-24 Thread spir

On 01/24/2014 06:44 PM, Peter Otten wrote:

There is no infinite recursion. The for loop is currently implemented as

# expect an iterable
# handle iterators through an idempotent iter()
tmp = iter(xs)


# here you must check that tmp actually implements the iterator protocol,
# else raise an error


while True:
 try:
 x = next(tmp)
 except StopIteration:
 break
 # use x

If I understand you correctly you suggest the following:

# expect an iterator
# fall back to getting an iterator through iter()
try:
 tmp = xs.__next__
except AttributeError:
 tmp = iter(xs).__next__
while True:
 try:
 x = tmp()
 except StopIteration:
 break

How is that simpler?


see above

d
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Iterator vs. iterable cheatsheet, was Re: iter class

2014-01-24 Thread Peter Otten
spir wrote:

> On 01/24/2014 10:22 AM, Peter Otten wrote:
>>
>> There's an odd outlier that I probably shouldn't tell you about [...]
> 
> I guess there is a whole class of outliers; not really sure how to
> classify them. 

I think you are focusing on the details too much. In

> class Angles:
>  def __getitem__ (self, i):
>  return self.angles[i] * 360 / TAU
>  def __iter__ (self):
>  return iter(self.angles)

iter(self.angles) is just an implementation detail. The important point is 
that __iter__() returns a new iterator on each invocation, thus making 
Angles an "iterable" according to the naming scheme I was trying to 
establish. The __getitem__() method can be ignored as it is used for element 
lookup only, not for iteration.


> Side question: what is the point of __iter__ on iterators? Take a 'for'
> loop like: for x in xs: f(x)
> In the case where xs is not an iterator (no __next__), python calls
> iter(xs), which IIUC may call xs.__iter__() unless it is a builtin. But if
> xs is an iterator (__next__ is there), then Python uses it directly, thus
> what is the point of __iter__ there? In any case, python must check
> whether xs is an iterator (__next__). So there is no sense in calling
> __iter__ on an iterator. Logically, this would lead to an infinite
> recursion (calling __iter__ again and again). But python does it anyway
> (and stops after the first call, indeed):

There is no infinite recursion. The for loop is currently implemented as

# expect an iterable
# handle iterators through an idempotent iter()
tmp = iter(xs)
while True:
try:
x = next(tmp)
except StopIteration:
break
# use x

If I understand you correctly you suggest the following:

# expect an iterator
# fall back to getting an iterator through iter()
try:
tmp = xs.__next__
except AttributeError:
tmp = iter(xs).__next__
while True:
try:
x = tmp()
except StopIteration:
break

How is that simpler?

> The only theoretical case I can find is iterators which do implement the
> protocol (__next__) but are not to be used (!), instead delegate to
> another iterator. 

Again: the important differentiation is between iterator and iterable, not 
how the iterator is implemented.

> Then, why do they bear __next__ at all? why are they
> iterators at all?

Python allows you to do things that make no sense from the point of view of 
a human being. You can also implement an integer type with a negative abs():

>>> class Int(int):
... def __abs__(self): return self
... 
>>> abs(Int(-1))
-1

My advice: don't do it unless you have a good reason.

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Iterator vs. iterable cheatsheet, was Re: iter class

2014-01-24 Thread spir

On 01/24/2014 10:22 AM, Peter Otten wrote:


There's an odd outlier that I probably shouldn't tell you about [...]


I guess there is a whole class of outliers; not really sure how to classify 
them. This is the case of defining a wrapper or "proxy" type, for a underlying 
data structure which is iterable, typically a builtin collection. This was 
evoked (but not specifically termed as "wrapper" or such) in previous message of 
the orginal thread. In that case, __iter__ would neither return self (it is not 
an iterator), nore a hand-baked iterator, but the builtin (or already defined) 
one of the underlying iterable. Two example, rather artificial (but code works):


First, say you have an array of angles, which for the user are measured in 
degrees but internally use radians. (A better example may be of internal 
machine-friendly RGB colors and external user-friendly HSL colors [not HSV! 
g...].) At the interface, there is conversion. Iteration actually is 
iterating on the internal array, thus just uses iter().


import math ; TAU = 2 * math.pi
class Angles:
def __init__ (self):
self.angles = list()
def put (self, angle):
self.angles.append(angle * TAU / 360)
def __getitem__ (self, i):
return self.angles[i] * 360 / TAU
def __iter__ (self):
return iter(self.angles)

angles = Angles()
angles.put(100) ; angles.put(200) ; angles.put(300)
print(angles[1])
for a in angles: print(a)

Second, we build an associative array (for few entries) as a plain association 
list à la Lisp, but implemented as a pair of lists instead as a list of pairs 
(this saves an intermediate notion of Pair). Iterating is here on the pair of 
list, zipped (in the python sense) together:


class AssList:
def __init__ (self):
self.keys, self.vals = list(), list()
def put (self, key, val):
self.keys.append(key)
self.vals.append(val)
def __getitem__ (self, i):
return self.keys[i], self.vals[i]
def __iter__ (self):
return iter(zip(self.keys, self.vals))

al = AssList()
al.put(1,'a') ; al.put(2,'b') ; al.put(3,'c')
print(al[1])
for k,v in al: print(k,v)

Side question: what is the point of __iter__ on iterators? Take a 'for' loop 
like:
for x in xs: f(x)
In the case where xs is not an iterator (no __next__), python calls iter(xs), 
which IIUC may call xs.__iter__() unless it is a builtin. But if xs is an 
iterator (__next__ is there), then Python uses it directly, thus what is the 
point of __iter__ there? In any case, python must check whether xs is an 
iterator (__next__). So there is no sense in calling __iter__ on an iterator. 
Logically, this would lead to an infinite recursion (calling __iter__ again and 
again). But python does it anyway (and stops after the first call, indeed):


class Iter:
def __init__ (self): self.n = 0
def __next__ (self):
if self.n == 10: raise StopIteration
self.n += 1
return self.n
def __iter__ (self):
print("*** __iter__ ***") # *
return self
it = Iter()
for i in it: print(i, end=" ")
print()

huh?

The only theoretical case I can find is iterators which do implement the 
protocol (__next__) but are not to be used (!), instead delegate to another 
iterator. Then, why do they bear __next__ at all? why are they iterators at all?


denis
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor


[Tutor] Iterator vs. iterable cheatsheet, was Re: iter class

2014-01-24 Thread Peter Otten

+--+++
|  | __iter__   | __next__   |
+--+++
| iterable | return an iterator | not available  |
+--+++
| iterator | return self| return next item   |
|  || or raise StopIteration |
+--+++

iter(x) is x --> True:  x is an iterator
 False: x is an iterable
 raises Exception: x is neither iterator nor iterable

Once next(it) raises a StopIteration on a well-behaved iterator it must 
continue to raise `StopIteration`s on subsequent next(it) calls.

There's an odd outlier that I probably shouldn't tell you about: 
classes with a __getitem__() method behave like iterators. An eventual 
IndexError exception is propagated as StopIteration:

>>> class A:
... def __getitem__(self, index):
... if index > 2:
... raise IndexError
... return index * index
... 
>>> for item in A():
... print(item)
... 
0
1
4


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor