DidYouMean-Python
Logic to have various kind of suggestions in case of errors (NameError, AttributeError, ImportError, TypeError, ValueError, SyntaxError, MemoryError, OverflowError, IOError, OSError).
Inspired by "Did you mean" for Ruby (Explanation, Github Page), this is a simple implementation for/in Python. I wanted to see if I could mess around and create something similar in Python and it seems to be possible.
The logic adding suggestions can be invoked in different ways :
a call to
didyoumean_enablehook()
/didyoumean_disablehook()
ensuring that the magic happens automatically (by changingsys.excepthook
and the relevant iPython function).a context manager
didyoumean_contextmanager
.a post-mortem function for interactive session
didyoumean_postmortem()
.a function decorator
@didyoumean_decorator
.
Examples to be added.
See also :
dutc/didyoumean : a quite similar project developed in pretty much the same time. A few differences though : written in C, works only for AttributeError, etc.
TheF*ck : Correct and execute your previous shell command.
PyDidYouMean : Improve "file/command not found" errors with suggestions.
danrobinson/tracestack : Search your Python error messages on the web.
Example
More examples can be found from the test file didyoumean/didyoumean_sugg_tests.py
.
NameError
Fuzzy matches on existing names (local, builtin, keywords, modules, etc)
def my_func(foo, bar):
return foob
my_func(1, 2)
#>>> Before: NameError("global name 'foob' is not defined",)
#>>> After: NameError("global name 'foob' is not defined. Did you mean 'foo' (local)?",)
leng([0])
#>>> Before: NameError("name 'leng' is not defined",)
#>>> After: NameError("name 'leng' is not defined. Did you mean 'len' (builtin)?",)
import math
maths.pi
#>>> Before: NameError("name 'maths' is not defined",)
#>>> After: NameError("name 'maths' is not defined. Did you mean 'math' (local)?",)
passs
#>>> Before: NameError("name 'passs' is not defined",)
#>>> After: NameError("name 'passs' is not defined. Did you mean 'pass' (keyword)?",)
def my_func():
foo = 1
foob +=1
my_func()
#>>> Before: UnboundLocalError("local variable 'foob' referenced before assignment",)
#>>> After: UnboundLocalError("local variable 'foob' referenced before assignment. Did you mean 'foo' (local)?",)
Checking if name is the attribute of a defined object
class Duck():
def __init__(self):
quack()
def quack(self):
pass
d = Duck()
#>>> Before: NameError("global name 'quack' is not defined",)
#>>> After: NameError("global name 'quack' is not defined. Did you mean 'self.quack'?",)
import math
pi
#>>> Before: NameError("name 'pi' is not defined",)
#>>> After: NameError("name 'pi' is not defined. Did you mean 'math.pi'?",)
Looking for missing imports
string.ascii_lowercase
#>>> Before: NameError("name 'string' is not defined",)
#>>> After: NameError("name 'string' is not defined. Did you mean to import string first?",)
Looking in missing imports
choice
#>>> Before: NameError("name 'choice' is not defined",)
#>>> After: NameError("name 'choice' is not defined. Did you mean 'choice' from random (not imported)?",)
Special cases
assert j ** 2 == -1
#>>> Before: NameError("name 'j' is not defined",)
#>>> After: NameError("name 'j' is not defined. Did you mean '1j' (imaginary unit)?",)
AttributeError
Fuzzy matches on existing attributes
lst = [1, 2, 3]
lst.appendh(4)
#>>> Before: AttributeError("'list' object has no attribute 'appendh'",)
#>>> After: AttributeError("'list' object has no attribute 'appendh'. Did you mean 'append'?",)
import math
math.pie
#>>> Before: AttributeError("'module' object has no attribute 'pie'",)
#>>> After: AttributeError("'module' object has no attribute 'pie'. Did you mean 'pi'?",)
Detection of mis-used builtins
lst = [1, 2, 3]
lst.max()
#>>> Before: AttributeError("'list' object has no attribute 'max'",)
#>>> After: AttributeError("'list' object has no attribute 'max'. Did you mean 'max(list)'?",)
Trying to find method with similar meaning (hardcoded)
lst = [1, 2, 3]
lst.add(4)
#>>> Before: AttributeError("'list' object has no attribute 'add'",)
#>>> After: AttributeError("'list' object has no attribute 'add'. Did you mean 'append'?",)
ImportError
Fuzzy matches on existing modules
from maths import pi
#>>> Before: ImportError('No module named maths',)
#>>> After: ImportError("No module named maths. Did you mean 'math'?",)
Fuzzy matches on elements of the module
from math import pie
#>>> Before: ImportError('cannot import name pie',)
#>>> After: ImportError("cannot import name pie. Did you mean 'pi'?",)
Looking for import from wrong module
from itertools import pi
#>>> Before: ImportError('cannot import name pi',)
#>>> After: ImportError("cannot import name pi. Did you mean 'from math import pi'?",)
TypeError
Fuzzy matches on keyword arguments
def my_func(abcde):
pass
my_func(abcdf=1)
#>>> Before: TypeError("my_func() got an unexpected keyword argument 'abcdf'",)
#>>> After: TypeError("my_func() got an unexpected keyword argument 'abcdf'. Did you mean 'abcde'?",)
Confusion between brackets and parenthesis
lst = [1, 2, 3]
lst(0)
#>>> Before: TypeError("'list' object is not callable",)
#>>> After: TypeError("'list' object is not callable. Did you mean 'list[value]'?",)
def my_func(a):
pass
my_func[1]
#>>> Before: TypeError("'function' object has no attribute '__getitem__'",)
#>>> After: TypeError("'function' object has no attribute '__getitem__'. Did you mean 'function(value)'?",)
ValueError
Special cases
'Foo{}'.format('bar')
#>>> Before: ValueError('zero length field name in format',)
#>>> After: ValueError('zero length field name in format. Did you mean {0}?',)
SyntaxError
Fuzzy matches when importing from future
from __future__ import divisio
#>>> Before: SyntaxError('future feature divisio is not defined',)
#>>> After: SyntaxError("future feature divisio is not defined. Did you mean 'division'?",)
Various
return
#>>> Before: SyntaxError("'return' outside function", ('<string>', 1, 0, None))
#>>> After: SyntaxError("'return' outside function. Did you mean to indent it, 'sys.exit([arg])'?", ('<string>', 1, 0, None))
MemoryError
Search for a memory-efficient equivalent
range(99999999999)
#>>> Before: MemoryError()
#>>> After: MemoryError(". Did you mean 'xrange'?",)
OverflowError
Search for a memory-efficient equivalent
range(999999999999999)
#>>> Before: OverflowError('range() result has too many items',)
#>>> After: OverflowError("range() result has too many items. Did you mean 'xrange'?",)
OSError/IOError
Suggestion for tilde/variable expansions
os.listdir('~')
#>>> Before: OSError(2, 'No such file or directory')
#>>> After: OSError(2, "No such file or directory. Did you mean '/home/user' (calling os.path.expanduser)?")
Usage
I haven't done anything fancy for the installation (yet). You'll have to clone this.
Once you have the code, it can be used in different ways :
hook on
sys.excepthook
: just calldidyoumean_enablehook
and you'll have the suggestions for any uncaught exception.decorator : just add the
@didyoumean
decorator before any function (themain()
could be a good choice) and you'll have the suggestions for any exception happening through a call to that method.context manager.
post mortem.
Please refer to the examples to be added in the introduction.
Implementation
All external APIs (decorator, hook, etc) use the same logic behind the scene. It works in a pretty simple way : when an exception happens, we try to get the relevant information out of the error message and of the backtrace to find the most relevant suggestions. To filter the best suggestions out of everything in case of fuzzy match, I am currently using difflib
.
Contributing
Feedback is welcome, feel free to :
- send me an email for any question/advice/comment/criticism
- open issues if something goes wrong (please provide at least the version of Python you are using).
Also, pull-requests are welcome to :
- fix issues
- enhance the documentation
- improve the code
- bring awesomeness
As for the technical details :
- this is under MIT License : you can do anything you want as long as you provide attribution back to this project.
- I try to follow PEP 8 and PEP 257 as much as possible. Compliancy is checked during continuous integration using the pep8 and pep257 checkers.
- I try to have most of the code covered by unit tests.
- I try to write the code in such a way that it works on all Python versions from 2.6 (included).