Repository: ipython/ipython

Issue:
? should look at __signature__
I was watching http://pyvideo.org/video/1716/python-3-metaprogramming (intersting talk btw) and it was mentioned that Python 3 has this new feature `__signature__`, which lets you redefine what the signature of an object should be, which is used by things like inspect.signature. It is pointed out that `help` doesn't support this, but it would be great if IPython's `?` does. I checked however, and it doesn't (this is Python 3):

``` py
In [2]: import inspect

In [3]: from inspect import Signature, Parameter

In [4]: a = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])

In [5]: class Test:
    __signature__ = a
   ...:

In [11]: print(inspect.signature(Test))
(test)

In [12]: Test?
Type:       type
String Form:<class '__main__.Test'>
Docstring:  <no docstring>

In [18]: def f(*args):
   ....:     pass
   ....:

In [20]: f?
Type:       function
String Form:<function f at 0x1026ac0e0>
File:       /Users/aaronmeurer/Documents/ipython/<ipython-input-18-446f067045b6>
Definition: f(*args)
Docstring:  <no docstring>

In [21]: f.__signature__ = a

In [22]: f?
Type:       function
String Form:<function f at 0x1026ac0e0>
File:       /Users/aaronmeurer/Documents/ipython/<ipython-input-18-446f067045b6>
Definition: f(*args)
Docstring:  <no docstring>

In [24]: print(inspect.signature(f))
(test)
```



Discussion:
The relevant part of the video is near 50:00 ish btw.

Good idea.

See http://www.python.org/dev/peps/pep-0362/ for the full spec.

works for function now : 

```
n [9]: f?
Signature: f(*args)
Docstring: <no docstring>
File:      ~/dev/notebook/notebook/static/notebook/<ipython-input-8-19c7cd79bebe>
Type:      function

In [10]: f.__signature__ = a

In [11]: f?
Signature: f(test)
Docstring: <no docstring>
File:      ~/dev/notebook/notebook/static/notebook/<ipython-input-8-19c7cd79bebe>
Type:      function
```

Not Class. 

`inspect.signature` reads this directly. Any reason IPython couldn't just use that (in Python 3, of course)?

@asmeurer I believe we do call `inspect.signature` on Python 3, but if I read the logic correctly, we do it on `Class.__init__` in order to get a handle on the callable (for Python 2). Since we've adopted (and backported) signatures, I think we can skip grabbing `__init__`, and it should Just Work™


Resolving patch:
diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py
--- a/IPython/core/oinspect.py
+++ b/IPython/core/oinspect.py
@@ -420,7 +420,6 @@ def pdef(self, obj, oname=''):
 
         if inspect.isclass(obj):
             header = self.__head('Class constructor information:\n')
-            obj = obj.__init__
         elif (not py3compat.PY3) and type(obj) is types.InstanceType:
             obj = obj.__call__
 
@@ -766,23 +765,29 @@ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
         # Constructor docstring for classes
         if inspect.isclass(obj):
             out['isclass'] = True
-            # reconstruct the function definition and print it:
+
+            # get the init signature:
             try:
-                obj_init =  obj.__init__
+                init_def = self._getdef(obj, oname)
             except AttributeError:
-                init_def = init_ds = None
+                init_def = None
+
+            if init_def:
+                out['init_definition'] = self.format(init_def)
+
+            # get the __init__ docstring
+            try:
+                obj_init = obj.__init__
+            except AttributeError:
+                init_ds = None
             else:
-                init_def = self._getdef(obj_init,oname)
-                init_ds  = getdoc(obj_init)
+                init_ds = getdoc(obj_init)
                 # Skip Python's auto-generated docstrings
                 if init_ds == _object_init_docstring:
                     init_ds = None
 
-            if init_def or init_ds:
-                if init_def:
-                    out['init_definition'] = self.format(init_def)
-                if init_ds:
-                    out['init_docstring'] = init_ds
+            if init_ds:
+                out['init_docstring'] = init_ds
 
         # and class docstring for instances:
         else:


Test patch:
diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py
--- a/IPython/core/tests/test_oinspect.py
+++ b/IPython/core/tests/test_oinspect.py
@@ -1,27 +1,17 @@
 """Tests for the object inspection functionality.
 """
-#-----------------------------------------------------------------------------
-#  Copyright (C) 2010-2011 The IPython Development Team.
-#
-#  Distributed under the terms of the BSD License.
-#
-#  The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
 
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
 from __future__ import print_function
 
-# Stdlib imports
 import os
 import re
 import sys
 
-# Third-party imports
 import nose.tools as nt
 
-# Our own imports
 from .. import oinspect
 from IPython.core.magic import (Magics, magics_class, line_magic,
                                 cell_magic, line_cell_magic,
@@ -32,6 +22,7 @@
 from IPython.testing.tools import AssertPrints
 from IPython.utils.path import compress_user
 from IPython.utils import py3compat
+from IPython.utils.signatures import Signature, Parameter
 
 
 #-----------------------------------------------------------------------------
@@ -49,7 +40,7 @@
 # defined, if any code is inserted above, the following line will need to be
 # updated.  Do NOT insert any whitespace between the next line and the function
 # definition below.
-THIS_LINE_NUMBER = 52  # Put here the actual number of this line
+THIS_LINE_NUMBER = 43  # Put here the actual number of this line
 def test_find_source_lines():
     nt.assert_equal(oinspect.find_source_lines(test_find_source_lines), 
                     THIS_LINE_NUMBER+1)
@@ -120,6 +111,14 @@ def __call__(self, *a, **kw):
     def method(self, x, z=2):
         """Some method's docstring"""
 
+class HasSignature(object):
+    """This is the class docstring."""
+    __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
+
+    def __init__(self, *args):
+        """This is the init docstring"""
+
+
 class SimpleClass(object):
     def method(self, x, z=2):
         """Some method's docstring"""
@@ -282,7 +281,8 @@ def test_info():
     nt.assert_equal(i['docstring'], Call.__doc__)
     nt.assert_equal(i['source'], None)
     nt.assert_true(i['isclass'])
-    nt.assert_equal(i['init_definition'], "Call(self, x, y=1)\n")
+    _self_py2 = '' if py3compat.PY3 else 'self, '
+    nt.assert_equal(i['init_definition'], "Call(%sx, y=1)\n" % _self_py2)
     nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
 
     i = inspector.info(Call, detail_level=1)
@@ -307,6 +307,11 @@ def test_info():
         nt.assert_equal(i['type_name'], 'instance')
         nt.assert_equal(i['docstring'], OldStyle.__doc__)
 
+def test_class_signature():
+    info = inspector.info(HasSignature, 'HasSignature')
+    nt.assert_equal(info['init_definition'], "HasSignature(test)\n")
+    nt.assert_equal(info['init_docstring'], HasSignature.__init__.__doc__)
+
 def test_info_awkward():
     # Just test that this doesn't throw an error.
     i = inspector.info(Awkward())