Repository: ipython/ipython

Issue:
Inspected functions’ signatures undecipherable when type hints are in play
In the `_getdef` method, the function signature is rendered:

https://github.com/ipython/ipython/blob/5e7f930d355f9bb65b22ed8e8b735ea75c994d33/IPython/core/oinspect.py#L365-L366

I have formatted it well in my source:

```py
def neighbors(
    adata: AnnData,
    n_neighbors: int = 15,
    n_pcs: Optional[int] = None,
    use_rep: Optional[str] = None,
    knn: bool = True,
    random_state: Optional[Union[int, RandomState]] = 0,
    method: str = 'umap',
    metric: Union[str, Callable[[np.ndarray, np.ndarray], float]] = 'euclidean',
    metric_kwds: Mapping[str, Any] = {},
    copy: bool = False
) -> Optional[AnnData]:
    ...
```

However, `inspect.Signature`’s `__str__` method just combines it all [into one unreadable line](https://github.com/python/cpython/blob/d170e594b2736c579d12b96b129a8e58975a9c0a/Lib/inspect.py#L3072):

`Signature: sc.pp.neighbors(adata: anndata.base.AnnData, n_neighbors: int = 15, n_pcs: Union[int, NoneType] = None, use_rep: Union[str, NoneType] = None, knn: bool = True, random_state: Union[int, mtrand.RandomState, NoneType] = 0, method: str = 'umap', metric: Union[str, Callable[[numpy.ndarray, numpy.ndarray], float]] = 'euclidean', metric_kwds: Mapping[str, Any] = {}, copy: bool = False) -> Union[anndata.base.AnnData, NoneType]`

We should render the signature ourselves in a clearer fashion if it’s too long.


Discussion:

Resolving patch:
diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py
--- a/IPython/core/oinspect.py
+++ b/IPython/core/oinspect.py
@@ -362,7 +362,7 @@ def _getdef(self,obj,oname=''):
         If any exception is generated, None is returned instead and the
         exception is suppressed."""
         try:
-            hdef = oname + str(signature(obj))
+            hdef = _render_signature(signature(obj), oname)
             return cast_unicode(hdef)
         except:
             return None
@@ -1016,3 +1016,44 @@ def psearch(self,pattern,ns_table,ns_search=[],
             search_result.update(tmp_res)
 
         page.page('\n'.join(sorted(search_result)))
+
+
+def _render_signature(obj_signature, obj_name):
+    """
+    This was mostly taken from inspect.Signature.__str__.
+    Look there for the comments.
+    The only change is to add linebreaks when this gets too long.
+    """
+    result = []
+    pos_only = False
+    kw_only = True
+    for param in obj_signature.parameters.values():
+        if param.kind == inspect._POSITIONAL_ONLY:
+            pos_only = True
+        elif pos_only:
+            result.append('/')
+            pos_only = False
+
+        if param.kind == inspect._VAR_POSITIONAL:
+            kw_only = False
+        elif param.kind == inspect._KEYWORD_ONLY and kw_only:
+            result.append('*')
+            kw_only = False
+
+        result.append(str(param))
+
+    if pos_only:
+        result.append('/')
+
+    # add up name, parameters, braces (2), and commas
+    if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
+        # This doesn’t fit behind “Signature: ” in an inspect window.
+        rendered = '{}(\n{})'.format(obj_name, ''.join('    {},\n'.format(result)))
+    else:
+        rendered = '{}({})'.format(obj_name, ', '.join(result))
+
+    if obj_signature.return_annotation is not inspect._empty:
+        anno = inspect.formatannotation(obj_signature.return_annotation)
+        rendered += ' -> {}'.format(anno)
+
+    return rendered


Test patch: