Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

pinfo code duplication #443

Merged
merged 4 commits into from

2 participants

@takluyver
Owner

I found that in core/oinspect, the info and pinfo methods had largely identical code. With this change, pinfo calls info, and then simply takes care of formatting the result for display.

@fperez
Owner

Quick question: do we have any tests that exercise this codepath? If not, it would be a good idea to add one now as we go over this. Regarding the code itself, I don't see any problems, and this kind of duplication removal is always a net win.

@takluyver
Owner

There were some tests that covered part of it, specifically the function on top of it that generates the text for tooltips. I've added a more detailed test of the .info() method. I haven't tried to test pinfo, because that's largely just formatting now.

@fperez
Owner

Excellent, merge away!

@takluyver takluyver merged commit ab70883 into ipython:master
@damianavila damianavila referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@damianavila damianavila referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 108 additions and 230 deletions.
  1. +78 −230 IPython/core/oinspect.py
  2. +30 −0 IPython/core/tests/test_oinspect.py
View
308 IPython/core/oinspect.py
@@ -18,7 +18,6 @@
# stdlib modules
import __builtin__
-import StringIO
import inspect
import linecache
import os
@@ -75,7 +74,7 @@
'call_def', 'call_docstring',
# These won't be printed but will be used to determine how to
# format the object
- 'ismagic', 'isalias', 'argspec', 'found', 'name',
+ 'ismagic', 'isalias', 'isclass', 'argspec', 'found', 'name'
]
@@ -227,16 +226,6 @@ def call_tip(oinfo, format_call=True):
return call_line, doc
-#****************************************************************************
-# Class definitions
-
-class myStringIO(StringIO.StringIO):
- """Adds a writeln method to normal StringIO."""
- def writeln(self,*arg,**kw):
- """Does a write() and then a write('\n')"""
- self.write(*arg,**kw)
- self.write('\n')
-
class Inspector:
def __init__(self, color_table=InspectColors,
@@ -377,7 +366,41 @@ def pfile(self,obj,oname=''):
# getsourcelines returns lineno with 1-offset and page() uses
# 0-offset, so we must adjust.
page.page(self.format(open(ofile).read()),lineno-1)
-
+
+ def _format_fields(self, fields, title_width=12):
+ """Formats a list of fields for display.
+
+ Parameters
+ ----------
+ fields : list
+ A list of 2-tuples: (field_title, field_content)
+ title_width : int
+ How many characters to pad titles to. Default 12.
+ """
+ out = []
+ header = self.__head
+ for title, content in fields:
+ if len(content.splitlines()) > 1:
+ title = header(title + ":") + "\n"
+ else:
+ title = header((title+":").ljust(title_width))
+ out.append(title + content)
+ return "\n".join(out)
+
+ # The fields to be displayed by pinfo: (fancy_name, key_in_info_dict)
+ pinfo_fields1 = [("Type", "type_name"),
+ ("Base Class", "base_class"),
+ ("String Form", "string_form"),
+ ("Namespace", "namespace"),
+ ("Length", "length"),
+ ("File", "file"),
+ ("Definition", "definition")]
+
+ pinfo_fields_obj = [("Class Docstring", "class_docstring"),
+ ("Constructor Docstring","init_docstring"),
+ ("Call def", "call_def"),
+ ("Call docstring", "call_docstring")]
+
def pinfo(self,obj,oname='',formatter=None,info=None,detail_level=0):
"""Show detailed information about an object.
@@ -392,215 +415,42 @@ def pinfo(self,obj,oname='',formatter=None,info=None,detail_level=0):
- detail_level: if set to 1, more information is given.
"""
-
- obj_type = type(obj)
-
- header = self.__head
- if info is None:
- ismagic = 0
- isalias = 0
- ospace = ''
- else:
- ismagic = info.ismagic
- isalias = info.isalias
- ospace = info.namespace
- # Get docstring, special-casing aliases:
- if isalias:
- if not callable(obj):
- try:
- ds = "Alias to the system command:\n %s" % obj[1]
- except:
- ds = "Alias: " + str(obj)
- else:
- ds = "Alias to " + str(obj)
- if obj.__doc__:
- ds += "\nDocstring:\n" + obj.__doc__
- else:
- ds = getdoc(obj)
- if ds is None:
- ds = '<no docstring>'
- if formatter is not None:
- ds = formatter(ds)
-
- # store output in a list which gets joined with \n at the end.
- out = myStringIO()
+ info = self.info(obj, oname=oname, formatter=formatter,
+ info=info, detail_level=detail_level)
+ displayfields = []
+ for title, key in self.pinfo_fields1:
+ field = info[key]
+ if field is not None:
+ displayfields.append((title, field.rstrip()))
- string_max = 200 # max size of strings to show (snipped if longer)
- shalf = int((string_max -5)/2)
-
- if ismagic:
- obj_type_name = 'Magic function'
- elif isalias:
- obj_type_name = 'System alias'
+ # Source or docstring, depending on detail level and whether
+ # source found.
+ if detail_level > 0 and info['source'] is not None:
+ displayfields.append(("Source", info['source']))
+ elif info['docstring'] is not None:
+ displayfields.append(("Docstring", info["docstring"]))
+
+ # Constructor info for classes
+ if info['isclass']:
+ if info['init_definition'] or info['init_docstring']:
+ displayfields.append(("Constructor information", ""))
+ if info['init_definition'] is not None:
+ displayfields.append((" Definition",
+ info['init_definition'].rstrip()))
+ if info['init_docstring'] is not None:
+ displayfields.append((" Docstring",
+ indent(info['init_docstring'])))
+
+ # Info for objects:
else:
- obj_type_name = obj_type.__name__
- out.writeln(header('Type:\t\t')+obj_type_name)
-
- try:
- bclass = obj.__class__
- out.writeln(header('Base Class:\t')+str(bclass))
- except: pass
-
- # String form, but snip if too long in ? form (full in ??)
- if detail_level >= self.str_detail_level:
- try:
- ostr = str(obj)
- str_head = 'String Form:'
- if not detail_level and len(ostr)>string_max:
- ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
- ostr = ("\n" + " " * len(str_head.expandtabs())).\
- join(q.strip() for q in ostr.split("\n"))
- if ostr.find('\n') > -1:
- # Print multi-line strings starting at the next line.
- str_sep = '\n'
- else:
- str_sep = '\t'
- out.writeln("%s%s%s" % (header(str_head),str_sep,ostr))
- except:
- pass
-
- if ospace:
- out.writeln(header('Namespace:\t')+ospace)
-
- # Length (for strings and lists)
- try:
- length = str(len(obj))
- out.writeln(header('Length:\t\t')+length)
- except: pass
-
- # Filename where object was defined
- binary_file = False
- try:
- try:
- fname = inspect.getabsfile(obj)
- except TypeError:
- # For an instance, the file that matters is where its class was
- # declared.
- if hasattr(obj,'__class__'):
- fname = inspect.getabsfile(obj.__class__)
- if fname.endswith('<string>'):
- fname = 'Dynamically generated function. No source code available.'
- if (fname.endswith('.so') or fname.endswith('.dll')):
- binary_file = True
- out.writeln(header('File:\t\t')+fname)
- except:
- # if anything goes wrong, we don't want to show source, so it's as
- # if the file was binary
- binary_file = True
-
- # reconstruct the function definition and print it:
- defln = self._getdef(obj,oname)
- if defln:
- out.write(header('Definition:\t')+self.format(defln))
-
- # Docstrings only in detail 0 mode, since source contains them (we
- # avoid repetitions). If source fails, we add them back, see below.
- if ds and detail_level == 0:
- out.writeln(header('Docstring:\n') + indent(ds))
-
- # Original source code for any callable
- if detail_level:
- # Flush the source cache because inspect can return out-of-date
- # source
- linecache.checkcache()
- source_success = False
- try:
- try:
- src = getsource(obj,binary_file)
- except TypeError:
- if hasattr(obj,'__class__'):
- src = getsource(obj.__class__,binary_file)
- if src is not None:
- source = self.format(src)
- out.write(header('Source:\n')+source.rstrip()+'\n')
- source_success = True
- except Exception, msg:
- pass
-
- if ds and not source_success:
- out.writeln(header('Docstring [source file open failed]:\n')
- + indent(ds))
-
- # Constructor docstring for classes
- if inspect.isclass(obj):
- # reconstruct the function definition and print it:
- try:
- obj_init = obj.__init__
- except AttributeError:
- init_def = init_ds = None
- else:
- init_def = self._getdef(obj_init,oname)
- init_ds = getdoc(obj_init)
- # Skip Python's auto-generated docstrings
- if init_ds and \
- init_ds.startswith('x.__init__(...) initializes'):
- init_ds = None
-
- if init_def or init_ds:
- out.writeln(header('Constructor information:'))
- if init_def:
- out.write(header('Definition:\t')+ self.format(init_def))
- if init_ds:
- out.writeln(header('Docstring:\n') + indent(init_ds))
- # and class docstring for instances:
- elif obj_type is types.InstanceType or \
- isinstance(obj,object):
-
- # First, check whether the instance docstring is identical to the
- # class one, and print it separately if they don't coincide. In
- # most cases they will, but it's nice to print all the info for
- # objects which use instance-customized docstrings.
- if ds:
- try:
- cls = getattr(obj,'__class__')
- except:
- class_ds = None
- else:
- class_ds = getdoc(cls)
- # Skip Python's auto-generated docstrings
- if class_ds and \
- (class_ds.startswith('function(code, globals[,') or \
- class_ds.startswith('instancemethod(function, instance,') or \
- class_ds.startswith('module(name[,') ):
- class_ds = None
- if class_ds and ds != class_ds:
- out.writeln(header('Class Docstring:\n') +
- indent(class_ds))
-
- # Next, try to show constructor docstrings
- try:
- init_ds = getdoc(obj.__init__)
- # Skip Python's auto-generated docstrings
- if init_ds and \
- init_ds.startswith('x.__init__(...) initializes'):
- init_ds = None
- except AttributeError:
- init_ds = None
- if init_ds:
- out.writeln(header('Constructor Docstring:\n') +
- indent(init_ds))
-
- # Call form docstring for callable instances
- if hasattr(obj,'__call__'):
- #out.writeln(header('Callable:\t')+'Yes')
- call_def = self._getdef(obj.__call__,oname)
- #if call_def is None:
- # out.writeln(header('Call def:\t')+
- # 'Calling definition not available.')
- if call_def is not None:
- out.writeln(header('Call def:\t')+self.format(call_def))
- call_ds = getdoc(obj.__call__)
- # Skip Python's auto-generated docstrings
- if call_ds and call_ds.startswith('x.__call__(...) <==> x(...)'):
- call_ds = None
- if call_ds:
- out.writeln(header('Call docstring:\n') + indent(call_ds))
-
- # Finally send to printer/pager
- output = out.getvalue()
- if output:
- page.page(output)
- # end pinfo
+ for title, key in self.pinfo_fields_obj:
+ field = info[key]
+ if field is not None:
+ displayfields.append((title, field.rstrip()))
+
+ # Finally send to printer/pager:
+ if displayfields:
+ page.page(self._format_fields(displayfields))
def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
"""Compute a dict with detailed information about an object.
@@ -675,11 +525,6 @@ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
ostr = ("\n" + " " * len(str_head.expandtabs())).\
join(q.strip() for q in ostr.split("\n"))
- if ostr.find('\n') > -1:
- # Print multi-line strings starting at the next line.
- str_sep = '\n'
- else:
- str_sep = '\t'
out[str_head] = ostr
except:
pass
@@ -727,7 +572,7 @@ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
# Flush the source cache because inspect can return out-of-date
# source
linecache.checkcache()
- source_success = False
+ source = None
try:
try:
src = getsource(obj,binary_file)
@@ -737,12 +582,16 @@ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
if src is not None:
source = self.format(src)
out['source'] = source.rstrip()
- source_success = True
- except Exception, msg:
+ except Exception:
pass
+
+ if ds and source is None:
+ out['docstring'] = ds
+
# Constructor docstring for classes
if inspect.isclass(obj):
+ out['isclass'] = True
# reconstruct the function definition and print it:
try:
obj_init = obj.__init__
@@ -763,8 +612,7 @@ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
out['init_docstring'] = init_ds
# and class docstring for instances:
- elif obj_type is types.InstanceType or \
- isinstance(obj, object):
+ else:
# First, check whether the instance docstring is identical to the
# class one, and print it separately if they don't coincide. In
# most cases they will, but it's nice to print all the info for
View
30 IPython/core/tests/test_oinspect.py
@@ -87,3 +87,33 @@ def test_calltip_function2():
def test_calltip_builtin():
check_calltip(sum, 'sum', None, sum.__doc__)
+
+def test_info():
+ "Check that Inspector.info fills out various fields as expected."
+ i = inspector.info(Call, oname='Call')
+ nt.assert_equal(i['type_name'], 'type')
+ nt.assert_equal(i['base_class'], "<type 'type'>")
+ nt.assert_equal(i['string_form'], "<class 'IPython.core.tests.test_oinspect.Call'>")
+ fname = __file__
+ if fname.endswith(".pyc"):
+ fname = fname[:-1]
+ nt.assert_equal(i['file'], fname)
+ nt.assert_equal(i['definition'], 'Call(self, *a, **kw)\n')
+ nt.assert_equal(i['docstring'], Call.__doc__)
+ nt.assert_is(i['source'], None)
+ nt.assert_true(i['isclass'])
+ nt.assert_equal(i['init_definition'], "Call(self, x, y=1)\n")
+ nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
+
+ i = inspector.info(Call, detail_level=1)
+ nt.assert_is_not(i['source'], None)
+ nt.assert_is(i['docstring'], None)
+
+ c = Call(1)
+ c.__doc__ = "Modified instance docstring"
+ i = inspector.info(c)
+ nt.assert_equal(i['type_name'], 'Call')
+ nt.assert_equal(i['docstring'], "Modified instance docstring")
+ nt.assert_equal(i['class_docstring'], Call.__doc__)
+ nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
+ nt.assert_equal(i['call_docstring'], c.__call__.__doc__)
Something went wrong with that request. Please try again.