Skip to content
This repository

IPython shell swallows exceptions in certain circumstances #851

Closed
sccolbert opened this Issue October 10, 2011 · 11 comments

3 participants

S. Chris Colbert Fernando Perez Thomas Kluyver
S. Chris Colbert

A vanilla Python shell session:

Chris-Colberts-MacBook-Pro:enaml chris$ python
Enthought Python Distribution -- www.enthought.com
Version: 7.1-2 (32-bit)

Python 2.7.2 |EPD 7.1-2 (32-bit)| (default, Jul  3 2011, 15:40:35) 
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "packages", "demo" or "enthought" for more information.
>>> from traits.api import *
>>> class Foo(HasTraits):
...     a = Int
... 
>>> def default(self):
...     self.remove_trait('a')
...     setattr(self, 'a', 42.0)
...     return 42.0
... 
>>> c = Any().as_ctrait()
>>> c.default_value(8, default)
>>> f = Foo()
>>> f.add_trait('a', c)
>>> f.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in default
  File "/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/traits/trait_handlers.py", line 168, in error
    value )
traits.trait_errors.TraitError: The 'a' trait of a Foo instance must be an integer, but a value of 42.0 <type 'float'> was specified.
>>> f.a
0

The equivalent IPython session:

Python 2.7.2 |EPD 7.1-2 (32-bit)| (default, Jul  3 2011, 15:40:35) 
Type "copyright", "credits" or "license" for more information.

IPython 0.11 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: from traits.api import *

In [2]: def default(self):
   ...:     self.remove_trait('a')
   ...:     setattr(self, 'a', 42.0)
   ...:     return 42.0
   ...: 

In [3]: class Foo(HasTraits):
   ...:     a = Int
   ...:     

In [4]: c = Any().as_ctrait()

In [5]: c.default_value(8, default)

In [6]: f = Foo()

In [7]: f.add_trait('a', c)

In [8]: f.a
Out[8]: 0

The IPython shell swallows the exception raised by the default function. It appears that IPython is doing subsequent getattr(obj, name) calls (which succeed in this case) after the exception is raised.

Fernando Perez
Owner

Ouch, very true! Here's the relevant code in standalone format so it's easier to put into a file for running:

"""
https://github.com/ipython/ipython/issues/851
"""
from traits.api import *

class Foo(HasTraits):
    a = Int

def default(self):
    self.remove_trait('a')
    setattr(self, 'a', 42.0)
    return 42.0

c = Any().as_ctrait()
c.default_value(8, default)

f = Foo()
f.add_trait('a', c)

To test, use:

In [1]: run bug851.py

In [2]: f.a
Out[2]: 0

In contrast, the expected behavior is:

>>> execfile('bug851.py')
>>> f.a
Traceback (most recent call last):
  File "", line 1, in 
  File "bug851.py", line 11, in default
    setattr(self, 'a', 42.0)
  File "/usr/lib/pymodules/python2.6/traits/trait_handlers.py", line 168, in error
    value )
traits.trait_errors.TraitError: The 'a' trait of a Foo instance must be an integer, but a value of 42.0  was specified.

Confirming this, though it may take some digging to figure out where we are being over-aggressive. But this is pretty serious. Oddly enough, it's been there since 0.10.x (which means probably for a really long time), and nobody had ever reported the problem. But no matter, we need to fix it.

S. Chris Colbert
Fernando Perez
Owner
Thomas Kluyver
Collaborator

I notice from the initial vanilla Python session that it doesn't throw an error the second time you do f.a. It seems likely that our machinery does the first attribute access somewhere before running the user code, and catches the exception. Tab completion is certainly allowed to try attribute access, and I wouldn't be surprised if something in the prefilter code does too. Attribute access changing state is nasty and unusual, so I don't think we should worry too much about it.

If you do want to see where it's failing, you might want to see whether you can override the error method to raise BaseException instead of TraitError - if we've used the standard except Exception paradigm, BaseException won't be silenced. (As an aside, this is the reason to use that rather than catching everything).

S. Chris Colbert
Thomas Kluyver
Collaborator
S. Chris Colbert
Thomas Kluyver
Collaborator
S. Chris Colbert
Thomas Kluyver
Collaborator
Fernando Perez
Owner

@sccolbert, note that you can get the behavior you're asking for, if you disable completely autocalling. Here's an example in an IPython session:

In [1]: >>> from traits.api import *

In [2]: >>> class Foo(HasTraits):
   ...:     ...     a = Int
   ...:     ... 

In [3]: >>> def default(self):
   ...:     ...     self.remove_trait('a')
   ...:     ...     setattr(self, 'a', 42.0)
   ...:     ...     return 42.0
   ...: ... 

In [4]: >>> c = Any().as_ctrait()

In [5]: >>> c.default_value(8, default)

In [6]: >>> f = Foo()

In [7]: >>> f.add_trait('a', c)

In [8]: %autocall 0
Automatic calling is: OFF

In [9]: f.a
---------------------------------------------------------------------------
TraitError                                Traceback (most recent call last)
/home/fperez/tmp/junk/<ipython-input-9-adb09fc7d97f> in <module>()
----> 1 f.a

/home/fperez/tmp/junk/<ipython-input-3-47c366e94885> in default(self)
      1 def default(self):
      2     self.remove_trait('a')
----> 3     setattr(self, 'a', 42.0)
      4     return 42.0
      5 

/usr/lib/python2.6/dist-packages/traits/trait_handlers.py in error(self, object, name, value)
    166         """
    167         raise TraitError( object, name, self.full_info( object, name, value ),
--> 168                           value )
    169 
    170     def arg_error ( self, method, arg_num, object, name, value ):

TraitError: The 'a' trait of a Foo instance must be an integer, but a value of 42.0 <type 'float'> was specified.

So the take-home message is: if you have code that relies on delicate attribute handling semantics, you may want to simply give up autocall altogether. You can permanently disable it in your config file, just look for 'autocall' in your default profile (create one if you don't have it with ipython profile create.

Note that we're discussing whether to change this default in the future on the dev list.

But in the meantime I'm closing this, since there's really nothing we can 'fix': the fix is to turn autocall off, and we'll decide whether to do it system-wide, but users can always do it now either for just one session with %autocall 0 or permanently in their profile.

Fernando Perez fperez closed this November 29, 2011
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.