Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 149 additions & 1 deletion src/sage/misc/sageinspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class definition be found starting from the ``__init__`` method.
import sys
import tokenize
import re
from inspect import Signature, Parameter

try:
import importlib.machinery as import_machinery
Expand Down Expand Up @@ -1437,7 +1438,7 @@ def sage_getargspec(obj):
FullArgSpec(args=['x', 'y', 'z', 't'], varargs='args', varkw='keywords',
defaults=(1, 2), kwonlyargs=[], kwonlydefaults=None, annotations={})

We now run sage_getargspec on some functions from the Sage library::
We now run :func:`sage_getargspec` on some functions from the Sage library::

sage: sage_getargspec(identity_matrix) # needs sage.modules
FullArgSpec(args=['ring', 'n', 'sparse'], varargs=None, varkw=None,
Expand Down Expand Up @@ -1698,6 +1699,153 @@ def foo(x, a='\')"', b={not (2+1==3):'bar'}): return
kwonlyargs=[], kwonlydefaults=None, annotations={})


def _fullargspec_to_signature(fullargspec):
"""
Converts a :class:`FullArgSpec` instance to a :class:`Signature` instance by best effort.
The opposite conversion is implemented in the source code of :func:`inspect.getfullargspec`.

EXAMPLES::

sage: from sage.misc.sageinspect import _fullargspec_to_signature
sage: from inspect import FullArgSpec
sage: fullargspec = FullArgSpec(args=['self', 'x', 'base'], varargs=None, varkw=None, defaults=(None, 0), kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (self, x=None, base=0)>

TESTS::

sage: fullargspec = FullArgSpec(args=['p', 'r'], varargs='q', varkw='s', defaults=({},), kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (p, r={}, *q, **s)>
sage: fullargspec = FullArgSpec(args=['r'], varargs=None, varkw=None, defaults=((None, 'u:doing?'),), kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (r=(None, 'u:doing?'))>
sage: fullargspec = FullArgSpec(args=['x'], varargs=None, varkw=None, defaults=('):',), kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (x='):')>
sage: fullargspec = FullArgSpec(args=['z'], varargs=None, varkw=None, defaults=({(1, 2, 3): True},), kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (z={(1, 2, 3): True})>
sage: fullargspec = FullArgSpec(args=['x', 'z'], varargs=None, varkw=None, defaults=({(1, 2, 3): True},), kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (x, z={(1, 2, 3): True})>
sage: fullargspec = FullArgSpec(args=[], varargs='args', varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (*args)>
sage: fullargspec = FullArgSpec(args=[], varargs=None, varkw='args', defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (**args)>
sage: fullargspec = FullArgSpec(args=['self', 'x'], varargs='args', varkw=None, defaults=(1,), kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (self, x=1, *args)>
sage: fullargspec = FullArgSpec(args=['self', 'x'], varargs='args', varkw=None, defaults=(1,), kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (self, x=1, *args)>
sage: fullargspec = FullArgSpec(args=['x', 'z'], varargs=None, varkw=None, defaults=('a string', {(1, 2, 3): True}), kwonlyargs=[], kwonlydefaults=None, annotations={})
sage: _fullargspec_to_signature(fullargspec)
<Signature (x='a string', z={(1, 2, 3): True})>
sage: _fullargspec_to_signature(FullArgSpec(args=['a'], varargs=None, varkw=None, defaults=None, kwonlyargs=['b', 'c'], kwonlydefaults={'b': 1}, annotations={}))
<Signature (a, *, b=1, c)>
sage: _fullargspec_to_signature(FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(1,), kwonlyargs=['c'], kwonlydefaults=None, annotations={}))
<Signature (a, b=1, *, c)>
"""
parameters = []
defaults_start = len(fullargspec.args) - len(fullargspec.defaults) if fullargspec.defaults else None

for i, arg in enumerate(fullargspec.args):
default = fullargspec.defaults[i - defaults_start] if defaults_start is not None and i >= defaults_start else Parameter.empty
param = Parameter(arg, Parameter.POSITIONAL_OR_KEYWORD, default=default)
parameters.append(param)

if fullargspec.varargs:
param = Parameter(fullargspec.varargs, Parameter.VAR_POSITIONAL)
parameters.append(param)

if fullargspec.varkw:
param = Parameter(fullargspec.varkw, Parameter.VAR_KEYWORD)
parameters.append(param)

for arg in fullargspec.kwonlyargs:
param = Parameter(arg, Parameter.KEYWORD_ONLY, default=Parameter.empty if fullargspec.kwonlydefaults is None else
fullargspec.kwonlydefaults.get(arg, Parameter.empty))
parameters.append(param)

return Signature(parameters)


def sage_signature(obj):
r"""
Return the names and default values of a function's arguments.

INPUT:

- ``obj`` -- any callable object

OUTPUT:

A :class:`Signature` is returned, as specified by the
Python library function :func:`inspect.signature`.


.. NOTE::

Currently the type information is not returned, because the output
is converted from the return value of :func:`sage_getargspec`.
This should be changed in the future.

EXAMPLES::

sage: from sage.misc.sageinspect import sage_signature
sage: def f(x, y, z=1, t=2, *args, **keywords):
....: pass
sage: sage_signature(f)
<Signature (x, y, z=1, t=2, *args, **keywords)>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

examples mostly copied from sage_getargspec, modified accordingly.


We now run :func:`sage_signature` on some functions from the Sage library::

sage: sage_signature(identity_matrix) # needs sage.modules
<Signature (ring, n=0, sparse=False)>
sage: sage_signature(factor)
<Signature (n, proof=None, int_=False, algorithm='pari', verbose=0, **kwds)>

In the case of a class or a class instance, the :class:`Signature` of the
``__new__``, ``__init__`` or ``__call__`` method is returned::

sage: P.<x,y> = QQ[]
sage: sage_signature(P) # needs sage.libs.singular
<Signature (base_ring, n, names, order='degrevlex')>
sage: sage_signature(P.__class__) # needs sage.libs.singular
<Signature (self, x=0, *args, **kwds)>

The following tests against various bugs that were fixed in
:issue:`9976`::

sage: from sage.rings.polynomial.real_roots import bernstein_polynomial_factory_ratlist # needs sage.modules
sage: sage_signature(bernstein_polynomial_factory_ratlist.coeffs_bitsize) # needs sage.modules
<Signature (self)>
sage: from sage.rings.polynomial.pbori.pbori import BooleanMonomialMonoid # needs sage.rings.polynomial.pbori
sage: sage_signature(BooleanMonomialMonoid.gen) # needs sage.rings.polynomial.pbori
<Signature (self, i=0)>
sage: I = P*[x,y]
sage: sage_signature(I.groebner_basis) # needs sage.libs.singular
<Signature (self, algorithm='', deg_bound=None, mult_bound=None, prot=False, *args, **kwds)>
sage: cython("cpdef int foo(x,y) except -1: return 1") # needs sage.misc.cython
sage: sage_signature(foo) # needs sage.misc.cython
<Signature (x, y)>

If a :func:`functools.partial` instance is involved, we see no other meaningful solution
than to return the signature of the underlying function::

sage: def f(a, b, c, d=1):
....: return a + b + c + d
sage: import functools
sage: f1 = functools.partial(f, 1, c=2)
sage: sage_signature(f1)
<Signature (a, b, c, d=1)>
"""
return _fullargspec_to_signature(sage_getargspec(obj))


def formatannotation(annotation, base_module=None):
"""
This is taken from Python 3.7's inspect.py; the only change is to
Expand Down
1 change: 1 addition & 0 deletions src/sage/repl/ipython_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ def init_inspector(self):
IPython.core.oinspect.getsource = LazyImport("sage.misc.sagedoc", "my_getsource")
IPython.core.oinspect.find_file = LazyImport("sage.misc.sageinspect", "sage_getfile")
IPython.core.oinspect.getargspec = LazyImport("sage.misc.sageinspect", "sage_getargspec")
IPython.core.oinspect.signature = LazyImport("sage.misc.sageinspect", "sage_signature") # pyright: ignore [reportPrivateImportUsage]

def init_line_transforms(self):
"""
Expand Down
21 changes: 19 additions & 2 deletions src/sage/repl/ipython_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Tests for the IPython integration

First, test the pinfo magic for Python code. This is what IPython
calls when you ask for the single-questionmark help, like `foo?` ::
calls when you ask for the single-questionmark help, like ``foo?`` ::

sage: from sage.repl.interpreter import get_test_shell
sage: shell = get_test_shell()
Expand Down Expand Up @@ -45,6 +45,23 @@
Type: type
...

Test that the signature is displayed even with ``binding=False``
as long as ``embedsignature=True`` is set
(unfortunately the type is not displayed, see ``sage_signature``)::

sage: shell.run_cell(r"""
....: %%cython
....: # cython: binding=False, embedsignature=True
....: cpdef int f(int a):
....: return a+1
....: """)
sage: shell.run_cell(u'print(f.__doc__)')
f(int a) -> int
File: ....pyx (starting at line 2)
sage: shell.run_cell(u'%pinfo f')
Signature: f(a)
...

Next, test the ``pinfo`` magic for ``R`` interface code, see :issue:`26906`::

sage: from sage.repl.interpreter import get_test_shell # optional - rpy2
Expand All @@ -62,7 +79,7 @@
...

Next, test the pinfo2 magic for Python code. This is what IPython
calls when you ask for the double-questionmark help, like `foo??` ::
calls when you ask for the double-questionmark help, like ``foo??`` ::

sage: from sage.repl.interpreter import get_test_shell
sage: shell = get_test_shell()
Expand Down
Loading