classification
Title: reduce() masks exception
Type: behavior Stage: test needed
Components: Interpreter Core Versions: Python 2.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: ajaksu2, akuchling, doerwalter, exarkun, rhettinger (5)
Priority: low Keywords patch

Created on 2003-01-10 14:41 by doerwalter, last changed 2009-02-13 02:31 by ajaksu2.

Files
File name Uploaded Description Edit Remove
diff.txt doerwalter, 2003-01-28 20:38
Messages (9)
msg13979 - (view) Author: Walter Dörwald (doerwalter) Date: 2003-01-10 14:41
In the following test script
-----
class Test:
   def __iter__(self):
      raise IOError

reduce(lambda x,y: x+y, Test())
-----
the real IOError exception is masked, i.e. the traceback is
-----
Traceback (most recent call last):
  File "test.py", line 5, in ?
    reduce(lambda x,y: x+y, Test())
TypeError: reduce() arg 2 must support iteration
-----
but IMHO should be
-----
Traceback (most recent call last):
  File "test.py", line 3, in ?
          raise IOError
IOError
-----
This can be fixed by removing the
PyErr_SetString(PyExc_TypeError, "reduce() arg 2 must
support iteration") call in
bltinmodule.c/buildtin_reduce().
msg13980 - (view) Author: Jean-Paul Calderone (exarkun) Date: 2003-01-11 17:08
Logged In: YES 
user_id=366566

the __iter__ method is supposed to return an object that
defines a 'next' method.  The returned object is the one
used for iteration, not the original.  So I believe the
error message is correct - Test does not support iteration.
 If you change the code to:

>>> class test:
...   def __iter__(self):
...     return self
...   def next(self):
...     raise IOError
... 
>>> reduce(operator.add, test())

You get the expected result...

Traceback (most recent call last):
&nbsp;&nbsp;File "<stdin>", line 1, in ?
&nbsp;&nbsp;File "<stdin>", line 5, in next
IOError
msg13981 - (view) Author: Walter Dörwald (doerwalter) Date: 2003-01-13 13:18
Logged In: YES 
user_id=89016

The point is that Python/bltinmodule.c/builtin_reduce()
masks the error returned from PyObject_GetIter(). Errors
from PyIter_Next() are not masked.

What about the following example:

class LazyFile:
   def __init__(self, name, mode="r"):
      self.name = name
      self.mode = mode
   def __iter__(self):
      return open(self.name, self.mode)
  
import operator
  
f = LazyFile("does not exist")
  
s = reduce(operator.add, f)

LazyFile *does* support iteration, but the underlying
problem of the non existing file is masked. Removing the
call PyErr_SetString(PyExc_TypeError, "reduce() arg 2 must
support iteration"); in builtin_reduce(), will produce the
original exception "IOError: [Errno 2] No such file or
directory: 'does not exist'" and when the second argument is
not iteratable, the original exception is just as good:

>>> reduce(lambda x,y: x+y, 42)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: iteration over non-sequence
msg13982 - (view) Author: Raymond Hettinger (rhettinger) Date: 2003-01-25 04:00
Logged In: YES 
user_id=80475

There's a lot of this going around.  map() and zip() have 
the same issue.  I recommend fixing them all.
msg13983 - (view) Author: Walter Dörwald (doerwalter) Date: 2003-01-28 20:38
Logged In: YES 
user_id=89016

Attached is a patch that fixes reduce(), map() and zip().
Unfortunately this loses the information about which
argument triggered the exception (in map() and zip())
msg13984 - (view) Author: Raymond Hettinger (rhettinger) Date: 2003-01-28 23:59
Logged In: YES 
user_id=80475

One way to mitigate the information loss is to mimic the 
style in zip() which only traps TypeErrors but passes 
through things like the IOError in your original example.  
msg13985 - (view) Author: Walter Dörwald (doerwalter) Date: 2003-01-30 11:29
Logged In: YES 
user_id=89016

Trapping only TypeError won't help:

class LazyFile:
   def __init__(self, name, mode="r"):
      self.name = name
      self.mode = mode
   def __iter__(self):
      return open(self.name, self.mode)
  
import operator
  
f = LazyFile(42)
  
s = reduce(operator.add, f)

Here the open call will raise a TypeError, that is totally
unrelated to the iterator protocol.

The cleanest solution would really be exception chaining.
msg13986 - (view) Author: A.M. Kuchling (akuchling) Date: 2006-12-21 15:35
Preserving the argument number seems difficult without exception chaining; perhaps a middle group would be to only replace TypeError exceptions.  Walter's comment at 2003-01-30 06:29 shows this can still be fooled, but a fix for 90% of the cases is still better than a fix for 0% of them.

The patch looks OK, but I think it should be reworked to take the middle-ground approach, replacing only TypeErrors.
msg81849 - (view) Author: Daniel Diniz (ajaksu2) Date: 2009-02-13 02:31
Confirmed in trunk, rev69550.
History
Date User Action Args
2009-02-13 02:31:03ajaksu2setversions: + Python 2.7
nosy: + ajaksu2
messages: + msg81849
keywords: + patch
type: behavior
stage: test needed
2003-01-10 14:41:13doerwaltercreate