Useful introspection tools.
Project description
====================
Grasp
====================
A set of python functions to help with interactive object inspection
and discovery.
These help one grok, grasp, or get the gist of running code. They're
most useful in the context of an interactive IPython session, but can
be used in any Python shell. They're also helpful in the debugger.
They produce output that can be parsed programmatically if you find
that useful. I'll start with examples using the provided IPython
magic commands, since that's how I use them. Then I'll show how to
use them in other contexts.
There are basically three functions provided:
* gist -- object inspection
* rtype -- recursive type, deep object inspection
* apropos -- deep search for things with a given name, value, etc.
You can find code and dowloads at the Launchpad page or the PyPI page
http://launchpad.net/grasp
http://pypi.python.org/pypi/grasp/
Written and maintained by Greg Novak <greg.novak@gmail.com>
Introduction
============
gist
----
Suppose you are confronted with an unfamiliar object. What are
its characteristics? What kinds of things can it do? What kind
information does it contain?
gist returns all the attributes of the object organized by type:
In [1]: foo = 5
In [2]: %gist foo
Out[2]: {builtin_function_or_method: [bit_length, conjugate],
int: [denominator, imag, numerator, real]}
The return value is a dict with one key for each type. The value of
each key is a list of strings giving the names of all the attributes
of the given type. So integer objects have four integer attributes,
named numerator, denomintor, real, and imag, (accessed via foo.real,
etc.) as well as two function attributes (accessed via
foo.bit_length(), etc) named bit_length and conjugate.
By default, attributes with leading underscores are omitted. You can
include them by asking for verbose output with -v (the output below is
trimmed for brevity):
In [3]: %gist -v foo
Out[3]: {method-wrapper: [__abs__, __add__],
int: [denominator, imag, numerator, real],
builtin_function_or_method: [__format__, __getnewargs__,
bit_length, conjugate],
str: [__doc__],
type: [__class__]}
You can pass python code to the magic command, which is evalated.
(again, output trimmed):
In [4]: %gist numpy.array([1,2,3])
Out[4]: {buffer: [data],
int: [itemsize, nbytes, ndim, size],
builtin_function_or_method: [all, any, argmax]
tuple: [shape, strides],
ndarray: [T, imag, real]}
rtype
-----
Suppose you are confronted with a list that contains a deeply nested
structure of tuples, lists, and so on. What is this object all about?
Are there regularities in the structure? You want a function like
type(), but you want it to be recursive so that it summarizes the
type structure of the object as much as possible.
Start with a trivial case:
In [5]: %rtype 1
Out[5]: 'int'
If the object is a tuple of objects, all of the same type, say so.
In [6]: %rtype [1, 2, 3]
Out[6]: 'list of 3 int'
What if it's a tuple of heterogeneous types? List them all. Note
that the return value is now a list of strings.
In [7]: %rtype [1, 1.1, 2]
Out[7]: ['list of', 'int', 'float', 'int']
The rtype function is recursive, so this gets interesting when you add
another layer of container objects:
In [8]: %rtype [(1,2), (3,4), (5,6)]
Out[8]: ['list of 3', 'tuple of 2 int']
The rtype function knows about numpy arrays and classifies them
according to shape and type.
In [9]: %rtype [numpy.array([1,2]), numpy.array([3,4]), numpy.array([5,6])]
Out[9]: ['list of 3', 'ndarray of (2,) int64']
apropos
-------
Suppose I know that matplotlib (a Python plotting library) defines a
bunch of colormaps, but I don't have any idea where to find them
within the module. I can search recursively through the whole module
namespace, returning all of the ways to 'reach' objects with names
having to do with colormaps:
In [10]: import matplotlib
In [11]: %apropos cmap matplotlib
Out[11]: ['matplotlib.cm.cmapname',
'matplotlib.cm.get_cmap']
Note the many layers of indirection that apropos digs through to
arrive at the results. Apropos is similar to the standard %psearch
magic command that's included in IPython. The difference is that that
psearch only handles one level at a time (although it can search
intermediate modules, as long as you know how many dots separate the
target from the module).
In [12]: %psearch matplotlib.cmap*
In [13]: %psearch matplotlib.*.cmap*
Using apropos, you can also search for objects whose string
representation contains a given string. If no object to search is
given, search the entire namespace given by globals()
In [14]: %apvalue blue
You can search for objects whose docstring contains a given string.
Use quotes if the search string contains a space (this works for any
of the aporpos commands).
In [15]: %apdoc "colormap instance" matplotlib
There are versions of each of the above that accept regular
expressions.
In [16]: %apname_regex [Cc]olors
In [17]: %apvalue_regex [Cc]olors
In [18]: %adoc_regex [Cc]olors
You can also pass python code as the object in which to search and it
will be evaluated, should you find that useful. The apropos commands
assume that the first argument is the search string and everything
else is the object in which to search, so the second argument doesn't
need to be quoted if it contains spaces.
In [18]: %apdoc "colormap instance" dict(a=matplotlib, b=numpy)
You can search for python objects (rather than strings) using %apobj.
This gives the name of any object equal to the tuple (1,3,5) in the
numpy module.
In [19]: %apobj (1,3,5) numpy
If the search object contains spaces, it must be quoted
In [20]: %apobj "(1, 3, 5)" numpy
You can refer to variables in the user's namespace
In [21]: foo = numpy.array([1,2,3])
In [22]: %apobj foo numpy
In [23]: %apobj [foo,37] numpy
With the %apropos and %aobj commands, you can provide your own
function that returns True if the object should be considered a match
and false otherwise. This can be a named function on an anonymous
function (probably requiring quotation marks). In the latter case the
code will be evaluated. See the docstrings for %apropos and %apobj
for details.
In [24]: def my_search_fn(needle, name, obj): return name and needle in name
In [25]: %apropos -s my_search_fn Colors
In [26]: %apropos -s "lambda needle, name, obj: name and needle in name" Colors
For examples, see the search functions in the grasp module:
In [27]: %apname search grasp
Out[27]: ['grasp.search_doc',
'grasp.search_doc_regexp',
'grasp.search_equal',
'grasp.search_name',
'grasp.search_name_regexp',
'grasp.search_value',
'grasp.search_value_regexp']
In [28]: grasp.search_doc?
A final note is that apropos is meant to be exhaustive, so it tends to
return more than you need. You generally have to pick through the
results a little to find what you want.
Installation
============
There are several possible installation techniques listed here in
rough order of preference:
1) Using pip (https://pypi.python.org/pypi/pip):
To install system-wide:
pip install grasp
To install in the user-specific Python directory
(~/Library/Python/2.7/lib/python/site-packages or similar on OS X):
pip install --user grasp
2) Using easy_install (https://pypi.python.org/pypi/distribute):
The same as above, except substitute easy_install for pip
3) Using distutils (included with Python)
This is the standard distutils routine: download the code, extract
from the archive, and install system-wide via something like
(adjusting links and version numbers as appropriate):
wget https://pypi.python.org/packages/source/g/grasp/grasp-0.3.0.tar.gz
tar xzf grasp-0.3.0.tar.gz
cd grasp-0.3.0
python setup.py install
If you want to install into a user directory, the last line becomes
python setup.py install --user
To install somewhere under the user's home directory:
python setup.py install --home=~/some/place
Usage
=====
To use grasp from IPython, type
%load_ext grasp
There are two ways to ensure that grasp is loaded automatically
when IPython starts:
1. Open or create the file ~/.ipython/profile_default/ipython_config.py
and add this line to it:
c.InteractiveShellApp.extensions = ['grasp']
2. Put the following line into a file with the extension .ipy in the
directory ~/.ipython/profile_default/startup/
%load_ext grasp
I have used this package on Gnu/Linux and OS X. I have not tested it
on Windows. It should work fine -- the main difference will be
getting it installed.
Non-IPython Environments
========================
If you don't use IPython, you just call the python functions upon
which the magic functions are based directly. The translation between
the magic command arguments given above and the arguments of the
Python functions should be straightforward. The magic commands
sometimes have shorter, more cryptic names with a view toward saving
keystrokes during heavy interactive use. In plain Python it will look
something like this:
>>> import grasp
>>> grasp.gist([1,2,3], verbose=True)
>>> grasp.apropos_name('foo', grasp)
>>> grasp.recursive_type([1,2,3])
IPython has nice pretty-printing facilities, and I took advantage of
those in deciding how grasp presents the information it finds. Thus
gist() returns a dict and relies on IPython to format it in a readable
way for interactive use. This means that it's possible to use the
output programatically. One could, for example, do this to set
every attribute of a class with integer type to 42:
atts = grasp.gist(object)
for att in atts['int']:
setattr(object, att, 42)
Version Information
===================
Grasp passes all tests with Python 2.4 through 2.7.
When translated to Python 3 via the 2to3 script, Grasp passes all
tests on Python 3.1, 3.2, and 3.3. Strictly speaking, for 3.1 there
are errors with the unittest.expectedFailure decorator, but that seems
to be a problem with unittest, not Grasp.
The IPython magic commands work for versions of IPython with the
decorators IPython.core.magic.magics_class and
IPython.core.magic.line_magic. It looks like these were introduced in
IPython version 0.13. Making it work with older versions of IPython
would only involve registering the magic commands in a different way,
which is probably not difficult, but I see no compelling reason to do
it.
License
=======
I've released the code under the CC0 licence, essentially putting it
into the public domain. You can do whatever you want with it. If you
incorporate grasp into another project, I ask for the courtesy of two
favors:
1) Include an appropriate acknowledgement of the fact that your
project uses grasp.
2) Let me know (greg.novak@gmail.com) so that I can link to your
project from the grasp web site.
Acknowledgements
================
Grasp was written in 2006 by Greg Novak <greg.novak@gmail.com> and
cleaned up for public consumption by the same in 2013.
This was written for my own use when developing Python code to produce
and analyze simulation output using the excellent IPython, Numpy,
Scipy, and matplotlib packages. I have benefited enormously from the
work of the authors of those packages over the years.
Grasp
====================
A set of python functions to help with interactive object inspection
and discovery.
These help one grok, grasp, or get the gist of running code. They're
most useful in the context of an interactive IPython session, but can
be used in any Python shell. They're also helpful in the debugger.
They produce output that can be parsed programmatically if you find
that useful. I'll start with examples using the provided IPython
magic commands, since that's how I use them. Then I'll show how to
use them in other contexts.
There are basically three functions provided:
* gist -- object inspection
* rtype -- recursive type, deep object inspection
* apropos -- deep search for things with a given name, value, etc.
You can find code and dowloads at the Launchpad page or the PyPI page
http://launchpad.net/grasp
http://pypi.python.org/pypi/grasp/
Written and maintained by Greg Novak <greg.novak@gmail.com>
Introduction
============
gist
----
Suppose you are confronted with an unfamiliar object. What are
its characteristics? What kinds of things can it do? What kind
information does it contain?
gist returns all the attributes of the object organized by type:
In [1]: foo = 5
In [2]: %gist foo
Out[2]: {builtin_function_or_method: [bit_length, conjugate],
int: [denominator, imag, numerator, real]}
The return value is a dict with one key for each type. The value of
each key is a list of strings giving the names of all the attributes
of the given type. So integer objects have four integer attributes,
named numerator, denomintor, real, and imag, (accessed via foo.real,
etc.) as well as two function attributes (accessed via
foo.bit_length(), etc) named bit_length and conjugate.
By default, attributes with leading underscores are omitted. You can
include them by asking for verbose output with -v (the output below is
trimmed for brevity):
In [3]: %gist -v foo
Out[3]: {method-wrapper: [__abs__, __add__],
int: [denominator, imag, numerator, real],
builtin_function_or_method: [__format__, __getnewargs__,
bit_length, conjugate],
str: [__doc__],
type: [__class__]}
You can pass python code to the magic command, which is evalated.
(again, output trimmed):
In [4]: %gist numpy.array([1,2,3])
Out[4]: {buffer: [data],
int: [itemsize, nbytes, ndim, size],
builtin_function_or_method: [all, any, argmax]
tuple: [shape, strides],
ndarray: [T, imag, real]}
rtype
-----
Suppose you are confronted with a list that contains a deeply nested
structure of tuples, lists, and so on. What is this object all about?
Are there regularities in the structure? You want a function like
type(), but you want it to be recursive so that it summarizes the
type structure of the object as much as possible.
Start with a trivial case:
In [5]: %rtype 1
Out[5]: 'int'
If the object is a tuple of objects, all of the same type, say so.
In [6]: %rtype [1, 2, 3]
Out[6]: 'list of 3 int'
What if it's a tuple of heterogeneous types? List them all. Note
that the return value is now a list of strings.
In [7]: %rtype [1, 1.1, 2]
Out[7]: ['list of', 'int', 'float', 'int']
The rtype function is recursive, so this gets interesting when you add
another layer of container objects:
In [8]: %rtype [(1,2), (3,4), (5,6)]
Out[8]: ['list of 3', 'tuple of 2 int']
The rtype function knows about numpy arrays and classifies them
according to shape and type.
In [9]: %rtype [numpy.array([1,2]), numpy.array([3,4]), numpy.array([5,6])]
Out[9]: ['list of 3', 'ndarray of (2,) int64']
apropos
-------
Suppose I know that matplotlib (a Python plotting library) defines a
bunch of colormaps, but I don't have any idea where to find them
within the module. I can search recursively through the whole module
namespace, returning all of the ways to 'reach' objects with names
having to do with colormaps:
In [10]: import matplotlib
In [11]: %apropos cmap matplotlib
Out[11]: ['matplotlib.cm.cmapname',
'matplotlib.cm.get_cmap']
Note the many layers of indirection that apropos digs through to
arrive at the results. Apropos is similar to the standard %psearch
magic command that's included in IPython. The difference is that that
psearch only handles one level at a time (although it can search
intermediate modules, as long as you know how many dots separate the
target from the module).
In [12]: %psearch matplotlib.cmap*
In [13]: %psearch matplotlib.*.cmap*
Using apropos, you can also search for objects whose string
representation contains a given string. If no object to search is
given, search the entire namespace given by globals()
In [14]: %apvalue blue
You can search for objects whose docstring contains a given string.
Use quotes if the search string contains a space (this works for any
of the aporpos commands).
In [15]: %apdoc "colormap instance" matplotlib
There are versions of each of the above that accept regular
expressions.
In [16]: %apname_regex [Cc]olors
In [17]: %apvalue_regex [Cc]olors
In [18]: %adoc_regex [Cc]olors
You can also pass python code as the object in which to search and it
will be evaluated, should you find that useful. The apropos commands
assume that the first argument is the search string and everything
else is the object in which to search, so the second argument doesn't
need to be quoted if it contains spaces.
In [18]: %apdoc "colormap instance" dict(a=matplotlib, b=numpy)
You can search for python objects (rather than strings) using %apobj.
This gives the name of any object equal to the tuple (1,3,5) in the
numpy module.
In [19]: %apobj (1,3,5) numpy
If the search object contains spaces, it must be quoted
In [20]: %apobj "(1, 3, 5)" numpy
You can refer to variables in the user's namespace
In [21]: foo = numpy.array([1,2,3])
In [22]: %apobj foo numpy
In [23]: %apobj [foo,37] numpy
With the %apropos and %aobj commands, you can provide your own
function that returns True if the object should be considered a match
and false otherwise. This can be a named function on an anonymous
function (probably requiring quotation marks). In the latter case the
code will be evaluated. See the docstrings for %apropos and %apobj
for details.
In [24]: def my_search_fn(needle, name, obj): return name and needle in name
In [25]: %apropos -s my_search_fn Colors
In [26]: %apropos -s "lambda needle, name, obj: name and needle in name" Colors
For examples, see the search functions in the grasp module:
In [27]: %apname search grasp
Out[27]: ['grasp.search_doc',
'grasp.search_doc_regexp',
'grasp.search_equal',
'grasp.search_name',
'grasp.search_name_regexp',
'grasp.search_value',
'grasp.search_value_regexp']
In [28]: grasp.search_doc?
A final note is that apropos is meant to be exhaustive, so it tends to
return more than you need. You generally have to pick through the
results a little to find what you want.
Installation
============
There are several possible installation techniques listed here in
rough order of preference:
1) Using pip (https://pypi.python.org/pypi/pip):
To install system-wide:
pip install grasp
To install in the user-specific Python directory
(~/Library/Python/2.7/lib/python/site-packages or similar on OS X):
pip install --user grasp
2) Using easy_install (https://pypi.python.org/pypi/distribute):
The same as above, except substitute easy_install for pip
3) Using distutils (included with Python)
This is the standard distutils routine: download the code, extract
from the archive, and install system-wide via something like
(adjusting links and version numbers as appropriate):
wget https://pypi.python.org/packages/source/g/grasp/grasp-0.3.0.tar.gz
tar xzf grasp-0.3.0.tar.gz
cd grasp-0.3.0
python setup.py install
If you want to install into a user directory, the last line becomes
python setup.py install --user
To install somewhere under the user's home directory:
python setup.py install --home=~/some/place
Usage
=====
To use grasp from IPython, type
%load_ext grasp
There are two ways to ensure that grasp is loaded automatically
when IPython starts:
1. Open or create the file ~/.ipython/profile_default/ipython_config.py
and add this line to it:
c.InteractiveShellApp.extensions = ['grasp']
2. Put the following line into a file with the extension .ipy in the
directory ~/.ipython/profile_default/startup/
%load_ext grasp
I have used this package on Gnu/Linux and OS X. I have not tested it
on Windows. It should work fine -- the main difference will be
getting it installed.
Non-IPython Environments
========================
If you don't use IPython, you just call the python functions upon
which the magic functions are based directly. The translation between
the magic command arguments given above and the arguments of the
Python functions should be straightforward. The magic commands
sometimes have shorter, more cryptic names with a view toward saving
keystrokes during heavy interactive use. In plain Python it will look
something like this:
>>> import grasp
>>> grasp.gist([1,2,3], verbose=True)
>>> grasp.apropos_name('foo', grasp)
>>> grasp.recursive_type([1,2,3])
IPython has nice pretty-printing facilities, and I took advantage of
those in deciding how grasp presents the information it finds. Thus
gist() returns a dict and relies on IPython to format it in a readable
way for interactive use. This means that it's possible to use the
output programatically. One could, for example, do this to set
every attribute of a class with integer type to 42:
atts = grasp.gist(object)
for att in atts['int']:
setattr(object, att, 42)
Version Information
===================
Grasp passes all tests with Python 2.4 through 2.7.
When translated to Python 3 via the 2to3 script, Grasp passes all
tests on Python 3.1, 3.2, and 3.3. Strictly speaking, for 3.1 there
are errors with the unittest.expectedFailure decorator, but that seems
to be a problem with unittest, not Grasp.
The IPython magic commands work for versions of IPython with the
decorators IPython.core.magic.magics_class and
IPython.core.magic.line_magic. It looks like these were introduced in
IPython version 0.13. Making it work with older versions of IPython
would only involve registering the magic commands in a different way,
which is probably not difficult, but I see no compelling reason to do
it.
License
=======
I've released the code under the CC0 licence, essentially putting it
into the public domain. You can do whatever you want with it. If you
incorporate grasp into another project, I ask for the courtesy of two
favors:
1) Include an appropriate acknowledgement of the fact that your
project uses grasp.
2) Let me know (greg.novak@gmail.com) so that I can link to your
project from the grasp web site.
Acknowledgements
================
Grasp was written in 2006 by Greg Novak <greg.novak@gmail.com> and
cleaned up for public consumption by the same in 2013.
This was written for my own use when developing Python code to produce
and analyze simulation output using the excellent IPython, Numpy,
Scipy, and matplotlib packages. I have benefited enormously from the
work of the authors of those packages over the years.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
grasp-0.3.2.tar.gz
(24.1 kB
view details)
File details
Details for the file grasp-0.3.2.tar.gz
.
File metadata
- Download URL: grasp-0.3.2.tar.gz
- Upload date:
- Size: 24.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7e70a7ba1b7023b42cfef004103e333caa410209c8d045cef13629efa26a0818 |
|
MD5 | 06e26b8c9784908a90e098be1326b959 |
|
BLAKE2b-256 | 3ba53c825f1f63a1cbb5339730af9888497e8eb352476abfa2cdbe9229a01993 |