Inspect Python, Sage, and Cython objects¶
This module extends parts of Python’s inspect module to Cython objects.
AUTHORS:
- originally taken from Fernando Perez’s IPython
- William Stein (extensive modifications)
- Nick Alexander (extensions)
- Nick Alexander (testing)
- Simon King (some extension for Cython, generalisation of SageArgSpecVisitor)
EXAMPLES:
sage: from sage.misc.sageinspect import *
Test introspection of modules defined in Python and Cython files:
Cython modules:
sage: sage_getfile(sage.rings.rational)
'.../rational.pyx'
sage: sage_getdoc(sage.rings.rational).lstrip()
'Rational Numbers...'
sage: sage_getsource(sage.rings.rational)[5:]
'Rational Numbers...'
Python modules:
sage: sage_getfile(sage.misc.sageinspect)
'.../sageinspect.py'
sage: print(sage_getdoc(sage.misc.sageinspect).lstrip()[:40])
Inspect Python, Sage, and Cython objects
sage: sage_getsource(sage.misc.sageinspect).lstrip()[5:-1]
'Inspect Python, Sage, and Cython objects...'
Test introspection of classes defined in Python and Cython files:
Cython classes:
sage: sage_getfile(sage.rings.rational.Rational)
'.../rational.pyx'
sage: sage_getdoc(sage.rings.rational.Rational).lstrip()
'A rational number...'
sage: sage_getsource(sage.rings.rational.Rational)
'cdef class Rational...'
Python classes:
sage: sage_getfile(BlockFinder)
'.../sage/misc/sageinspect.py'
sage: sage_getdoc(BlockFinder).lstrip()
'Provide a tokeneater() method to detect the...'
sage: sage_getsource(BlockFinder)
'class BlockFinder:...'
Test introspection of functions defined in Python and Cython files:
Cython functions:
sage: sage_getdef(sage.rings.rational.make_rational, obj_name='mr')
'mr(s)'
sage: sage_getfile(sage.rings.rational.make_rational)
'.../rational.pyx'
sage: sage_getdoc(sage.rings.rational.make_rational).lstrip()
'Make a rational number ...'
sage: sage_getsource(sage.rings.rational.make_rational)[4:]
'make_rational(s):...'
Python functions:
sage: sage_getdef(sage.misc.sageinspect.sage_getfile, obj_name='sage_getfile')
'sage_getfile(obj)'
sage: sage_getfile(sage.misc.sageinspect.sage_getfile)
'.../sageinspect.py'
sage: sage_getdoc(sage.misc.sageinspect.sage_getfile).lstrip()
'Get the full file name associated to "obj" as a string...'
sage: sage_getsource(sage.misc.sageinspect.sage_getfile)[4:]
'sage_getfile(obj):...'
Unfortunately, no argspec is extractable from builtins. Hence, we use a generic argspec:
sage: sage_getdef(''.find, 'find')
'find(*args, **kwds)'
sage: sage_getdef(str.find, 'find')
'find(*args, **kwds)'
By trac ticket #9976 and trac ticket #14017, introspection also works for interactively defined Cython code, and with rather tricky argument lines:
sage: cython('def foo(unsigned int x=1, a=\')"\', b={not (2+1==3):\'bar\'}, *args, **kwds): return')
sage: print(sage_getsource(foo))
def foo(unsigned int x=1, a=')"', b={not (2+1==3):'bar'}, *args, **kwds): return
sage: sage_getargspec(foo)
ArgSpec(args=['x', 'a', 'b'], varargs='args', keywords='kwds', defaults=(1, ')"', {False: 'bar'}))
-
class
sage.misc.sageinspect.
BlockFinder
¶ Provide a tokeneater() method to detect the end of a code block.
This is the Python library’s
inspect.BlockFinder
modified to recognize Cython definitions.-
tokeneater
(type, token, srow_scol, erow_ecol, line)¶
-
-
class
sage.misc.sageinspect.
SageArgSpecVisitor
¶ Bases:
ast.NodeVisitor
A simple visitor class that walks an abstract-syntax tree (AST) for a Python function’s argspec. It returns the contents of nodes representing the basic Python types: None, booleans, numbers, strings, lists, tuples, and dictionaries. We use this class in
_sage_getargspec_from_ast()
to extract an argspec from a function’s or method’s source code.EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: visitor.visit(ast.parse('[1,2,3]').body[0].value) [1, 2, 3] sage: visitor.visit(ast.parse("{'a':('e',2,[None,({False:True},'pi')]), 37.0:'temp'}").body[0].value) {37.0: 'temp', 'a': ('e', 2, [None, ({False: True}, 'pi')])} sage: v = ast.parse("jc = ['veni', 'vidi', 'vici']").body[0]; v <_ast.Assign object at ...> sage: [x for x in dir(v) if not x.startswith('__')] ['_attributes', '_fields', 'col_offset', 'lineno', 'targets', 'value'] sage: visitor.visit(v.targets[0]) 'jc' sage: visitor.visit(v.value) ['veni', 'vidi', 'vici']
-
visit_BinOp
(node)¶ Visit a Python AST
ast.BinOp
node.INPUT:
node
- the node instance to visit
OUTPUT:
- The result that
node
represents
AUTHOR:
- Simon King
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit(ast.parse(x).body[0].value) sage: [vis(d) for d in ['(3+(2*4))', '7|8', '5^3', '7/3', '7//3', '3<<4']] #indirect doctest # optional - python2 [11, 15, 6, 2, 2, 48]
-
visit_BoolOp
(node)¶ Visit a Python AST
ast.BoolOp
node.INPUT:
node
- the node instance to visit
OUTPUT:
- The result that
node
represents
AUTHOR:
- Simon King
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit(ast.parse(x).body[0].value) sage: [vis(d) for d in ['True and 1', 'False or 3 or None', '3 and 4']] #indirect doctest [1, 3, 4]
-
visit_Compare
(node)¶ Visit a Python AST
ast.Compare
node.INPUT:
node
- the node instance to visit
OUTPUT:
- The result that
node
represents
AUTHOR:
- Simon King
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit_Compare(ast.parse(x).body[0].value) sage: [vis(d) for d in ['1<2==2!=3', '1==1>2', '1<2>1', '1<3<2<4']] [True, False, True, False]
-
visit_Dict
(node)¶ Visit a Python AST
ast.Dict
node.INPUT:
node
- the node instance to visit
OUTPUT:
- the dictionary the
node
represents
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit_Dict(ast.parse(x).body[0].value) sage: [vis(d) for d in ['{}', "{1:one, 'two':2, other:bother}"]] [{}, {1: 'one', 'other': 'bother', 'two': 2}]
-
visit_List
(node)¶ Visit a Python AST
ast.List
node.INPUT:
node
- the node instance to visit
OUTPUT:
- the list the
node
represents
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit_List(ast.parse(x).body[0].value) sage: [vis(l) for l in ['[]', "['s', 't', 'u']", '[[e], [], [pi]]']] [[], ['s', 't', 'u'], [['e'], [], ['pi']]]
-
visit_Name
(node)¶ Visit a Python AST
ast.Name
node.INPUT:
node
- the node instance to visit
OUTPUT:
- None, True, False, or the
node
‘s name as a string.
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit_Name(ast.parse(x).body[0].value) sage: [vis(n) for n in ['True', 'False', 'None', 'foo', 'bar']] [True, False, None, 'foo', 'bar'] sage: [type(vis(n)) for n in ['True', 'False', 'None', 'foo', 'bar']] [<... 'bool'>, <... 'bool'>, <... 'NoneType'>, <... 'str'>, <... 'str'>]
-
visit_Num
(node)¶ Visit a Python AST
ast.Num
node.INPUT:
node
- the node instance to visit
OUTPUT:
- the number the
node
represents
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit_Num(ast.parse(x).body[0].value) sage: [vis(n) for n in ['123', '0.0', str(-pi.n())]] [123, 0.0, -3.14159265358979]
-
visit_Str
(node)¶ Visit a Python AST
ast.Str
node.INPUT:
node
- the node instance to visit
OUTPUT:
- the string the
node
represents
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit_Str(ast.parse(x).body[0].value) sage: [vis(s) for s in ['"abstract"', "u'syntax'", '''r"tr\ee"''']] ['abstract', u'syntax', 'tr\\ee']
-
visit_Tuple
(node)¶ Visit a Python AST
ast.Tuple
node.INPUT:
node
- the node instance to visit
OUTPUT:
- the tuple the
node
represents
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit_Tuple(ast.parse(x).body[0].value) sage: [vis(t) for t in ['()', '(x,y)', '("Au", "Al", "Cu")']] [(), ('x', 'y'), ('Au', 'Al', 'Cu')]
-
visit_UnaryOp
(node)¶ Visit a Python AST
ast.BinOp
node.INPUT:
node
- the node instance to visit
OUTPUT:
- The result that
node
represents
AUTHOR:
- Simon King
EXAMPLES:
sage: import ast, sage.misc.sageinspect as sms sage: visitor = sms.SageArgSpecVisitor() sage: vis = lambda x: visitor.visit_UnaryOp(ast.parse(x).body[0].value) sage: [vis(d) for d in ['+(3*2)', '-(3*2)']] [6, -6]
-
-
sage.misc.sageinspect.
isclassinstance
(obj)¶ Checks if argument is instance of non built-in class
INPUT:
obj
- objectEXAMPLES:
sage: from sage.misc.sageinspect import isclassinstance sage: isclassinstance(int) False sage: isclassinstance(FreeModule) True sage: class myclass: pass sage: isclassinstance(myclass) False sage: class mymetaclass(type): pass sage: class myclass2: ....: __metaclass__ = mymetaclass sage: isclassinstance(myclass2) False
-
sage.misc.sageinspect.
loadable_module_extension
()¶ Return the filename extension of loadable modules, including the dot. It is ‘.dll’ on cygwin, ‘.so’ otherwise.
EXAMPLES:
sage: from sage.misc.sageinspect import loadable_module_extension sage: sage.structure.sage_object.__file__.endswith(loadable_module_extension()) True
-
sage.misc.sageinspect.
sage_getargspec
(obj)¶ Return the names and default values of a function’s arguments.
INPUT:
obj
, any callable objectOUTPUT:
An
ArgSpec
is returned. This is a named tuple(args, varargs, keywords, defaults)
.args
is a list of the argument names (it may contain nested lists).varargs
andkeywords
are the names of the*
and**
arguments orNone
.defaults
is an \(n\)-tuple of the default values of the last \(n\) arguments.
NOTE:
If the object has a method
_sage_argspec_
then the output of that method is transformed into a named tuple and then returned.If a class instance has a method
_sage_src_
then its output is studied to determine the argspec. This is because currently theCachedMethod
decorator has no_sage_argspec_
method.EXAMPLES:
sage: from sage.misc.sageinspect import sage_getargspec sage: def f(x, y, z=1, t=2, *args, **keywords): ....: pass sage: sage_getargspec(f) ArgSpec(args=['x', 'y', 'z', 't'], varargs='args', keywords='keywords', defaults=(1, 2))
We now run sage_getargspec on some functions from the Sage library:
sage: sage_getargspec(identity_matrix) ArgSpec(args=['ring', 'n', 'sparse'], varargs=None, keywords=None, defaults=(0, False)) sage: sage_getargspec(factor) ArgSpec(args=['n', 'proof', 'int_', 'algorithm', 'verbose'], varargs=None, keywords='kwds', defaults=(None, False, 'pari', 0))
In the case of a class or a class instance, the
ArgSpec
of the__new__
,__init__
or__call__
method is returned:sage: P.<x,y> = QQ[] sage: sage_getargspec(P) ArgSpec(args=['base_ring', 'n', 'names', 'order'], varargs=None, keywords=None, defaults=('degrevlex',)) sage: sage_getargspec(P.__class__) ArgSpec(args=['self', 'x'], varargs='args', keywords='kwds', defaults=(0,))
The following tests against various bugs that were fixed in trac ticket #9976:
sage: from sage.rings.polynomial.real_roots import bernstein_polynomial_factory_ratlist sage: sage_getargspec(bernstein_polynomial_factory_ratlist.coeffs_bitsize) ArgSpec(args=['self'], varargs=None, keywords=None, defaults=None) sage: from sage.rings.polynomial.pbori import BooleanMonomialMonoid sage: sage_getargspec(BooleanMonomialMonoid.gen) ArgSpec(args=['self', 'i'], varargs=None, keywords=None, defaults=(0,)) sage: I = P*[x,y] sage: sage_getargspec(I.groebner_basis) ArgSpec(args=['self', 'algorithm', 'deg_bound', 'mult_bound', 'prot'], varargs='args', keywords='kwds', defaults=('', None, None, False)) sage: cython("cpdef int foo(x,y) except -1: return 1") sage: sage_getargspec(foo) ArgSpec(args=['x', 'y'], varargs=None, keywords=None, defaults=None)
If a
functools.partial
instance is involved, we see no other meaningful solution than to return the argspec 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_getargspec(f1) ArgSpec(args=['a', 'b', 'c', 'd'], varargs=None, keywords=None, defaults=(1,))
sage: cython('def foo(x, a=\'\\\')"\', b={not (2+1==3):\'bar\'}): return') sage: print(sage.misc.sageinspect.sage_getsource(foo)) def foo(x, a='\')"', b={not (2+1==3):'bar'}): return sage: sage.misc.sageinspect.sage_getargspec(foo) ArgSpec(args=['x', 'a', 'b'], varargs=None, keywords=None, defaults=('\')"', {False: 'bar'}))
The following produced a syntax error before the patch at trac ticket #11913:
sage: sage.misc.sageinspect.sage_getargspec(r.lm)
The following was fixed in trac ticket #16309:
sage: cython(''' ....: class Foo: ....: @staticmethod ....: def join(categories, bint as_list = False, tuple ignore_axioms=(), tuple axioms=()): pass ....: cdef class Bar: ....: @staticmethod ....: def join(categories, bint as_list = False, tuple ignore_axioms=(), tuple axioms=()): pass ....: cpdef meet(categories, bint as_list = False, tuple ignore_axioms=(), tuple axioms=()): pass ....: ''') sage: sage_getargspec(Foo.join) ArgSpec(args=['categories', 'as_list', 'ignore_axioms', 'axioms'], varargs=None, keywords=None, defaults=(False, (), ())) sage: sage_getargspec(Bar.join) ArgSpec(args=['categories', 'as_list', 'ignore_axioms', 'axioms'], varargs=None, keywords=None, defaults=(False, (), ())) sage: sage_getargspec(Bar.meet) ArgSpec(args=['categories', 'as_list', 'ignore_axioms', 'axioms'], varargs=None, keywords=None, defaults=(False, (), ()))
Test that trac ticket #17009 is fixed:
sage: sage_getargspec(gap) ArgSpec(args=['self', 'x', 'name'], varargs=None, keywords=None, defaults=(None,))
By trac ticket #17814, the following gives the correct answer (previously, the defaults would have been found
None
):sage: from sage.misc.nested_class import MainClass sage: sage_getargspec(MainClass.NestedClass.NestedSubClass.dummy) ArgSpec(args=['self', 'x', 'r'], varargs='args', keywords='kwds', defaults=((1, 2, 3.4),))
In trac ticket #18249 was decided to return a generic signature for Python builtin functions, rather than to raise an error (which is what Python’s inspect module does):
sage: import inspect sage: inspect.getargspec(range) Traceback (most recent call last): ... TypeError: <built-in function range> is not a Python function sage: sage_getargspec(range) ArgSpec(args=[], varargs='args', keywords='kwds', defaults=None)
AUTHORS:
- William Stein: a modified version of inspect.getargspec from the Python Standard Library, which was taken from IPython for use in Sage.
- Extensions by Nick Alexander
- Simon King: Return an
ArgSpec
, fix some bugs.
-
sage.misc.sageinspect.
sage_getdef
(obj, obj_name='')¶ Return the definition header for any callable object.
INPUT:
obj
- functionobj_name
- string (optional, default ‘’)
obj_name
is prepended to the output.EXAMPLES:
sage: from sage.misc.sageinspect import sage_getdef sage: sage_getdef(identity_matrix) '(ring, n=0, sparse=False)' sage: sage_getdef(identity_matrix, 'identity_matrix') 'identity_matrix(ring, n=0, sparse=False)'
Check that trac ticket #6848 has been fixed:
sage: sage_getdef(RDF.random_element) '(min=-1, max=1)'
If an exception is generated, None is returned instead and the exception is suppressed.
AUTHORS:
- William Stein
- extensions by Nick Alexander
-
sage.misc.sageinspect.
sage_getdoc
(obj, obj_name='', embedded_override=False)¶ Return the docstring associated to
obj
as a string.If
obj
is a Cython object with an embedded position in its docstring, the embedded position is stripped.If optional argument
embedded_override
is False (its default value), then the string is formatted according to the value of EMBEDDED_MODE. If this argument is True, then it is formatted as if EMBEDDED_MODE were True.INPUT:
obj
– a function, module, etc.: something with a docstring.
EXAMPLES:
sage: from sage.misc.sageinspect import sage_getdoc sage: sage_getdoc(identity_matrix)[87:124] 'Return the n x n identity matrix over' 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: f.__doc__ = "original documentation" sage: f1.__doc__ = "specialised documentation" sage: sage_getdoc(f) 'original documentation\n' sage: sage_getdoc(f1) 'specialised documentation\n'
AUTHORS:
- William Stein
- extensions by Nick Alexander
-
sage.misc.sageinspect.
sage_getdoc_original
(obj)¶ Return the unformatted docstring associated to
obj
as a string.If
obj
is a Cython object with an embedded position or signature in its docstring, the embedded information is stripped. If the stripped docstring is empty, then the stripped docstring ofobj.__init__
is returned instead.Feed the results from this into the function
sage.misc.sagedoc.format()
for printing to the screen.INPUT:
obj
– a function, module, etc.: something with a docstring.
EXAMPLES:
sage: from sage.misc.sageinspect import sage_getdoc_original
Here is a class that has its own docstring:
sage: print(sage_getdoc_original(sage.rings.integer.Integer)) The ``Integer`` class represents arbitrary precision integers. It derives from the ``Element`` class, so integers can be used as ring elements anywhere in Sage. ...
Here is a class that does not have its own docstring, so that the docstring of the
__init__
method is used:sage: print(sage_getdoc_original(Parent)) Base class for all parents. Parents are the Sage/mathematical analogues of container objects in computer science. ...
Old-style classes are supported:
sage: class OldStyleClass: ....: def __init__(self): ....: '''The __init__ docstring''' ....: pass sage: print(sage_getdoc_original(OldStyleClass)) The __init__ docstring
When there is no
__init__
method, we just get an empty string:sage: class OldStyleClass: ....: pass sage: sage_getdoc_original(OldStyleClass) ''
If an instance of a class does not have its own docstring, the docstring of its class results:
sage: sage_getdoc_original(sage.plot.colors.aliceblue) == sage_getdoc_original(sage.plot.colors.Color) True
-
sage.misc.sageinspect.
sage_getfile
(obj)¶ Get the full file name associated to
obj
as a string.INPUT:
obj
, a Sage object, module, etc.EXAMPLES:
sage: from sage.misc.sageinspect import sage_getfile sage: sage_getfile(sage.rings.rational)[-23:] 'sage/rings/rational.pyx' sage: sage_getfile(Sq)[-42:] 'sage/algebras/steenrod/steenrod_algebra.py'
The following tests against some bugs fixed in trac ticket #9976:
sage: obj = sage.combinat.partition_algebra.SetPartitionsAk sage: obj = sage.combinat.partition_algebra.SetPartitionsAk sage: sage_getfile(obj) '...sage/combinat/partition_algebra.py'
And here is another bug, fixed in trac ticket #11298:
sage: P.<x,y> = QQ[] sage: sage_getfile(P) '...sage/rings/polynomial/multi_polynomial_libsingular.pyx'
A problem fixed in trac ticket #16309:
sage: cython(''' ....: class Bar: pass ....: cdef class Foo: pass ....: ''') sage: sage_getfile(Bar) '...pyx' sage: sage_getfile(Foo) '...pyx'
By trac ticket #18249, we return an empty string for Python builtins. In that way, there is no error when the user types, for example,
range?
:sage: sage_getfile(range) ''
AUTHORS:
- Nick Alexander
- Simon King
-
sage.misc.sageinspect.
sage_getsource
(obj)¶ Return the source code associated to obj as a string, or None.
INPUT:
obj
– function, etc.
EXAMPLES:
sage: from sage.misc.sageinspect import sage_getsource sage: sage_getsource(identity_matrix)[19:60] 'identity_matrix(ring, n=0, sparse=False):' sage: sage_getsource(identity_matrix)[19:60] 'identity_matrix(ring, n=0, sparse=False):'
AUTHORS:
- William Stein
- extensions by Nick Alexander
-
sage.misc.sageinspect.
sage_getsourcelines
(obj)¶ Return a pair ([source_lines], starting line number) of the source code associated to obj, or None.
INPUT:
obj
– function, etc.
OUTPUT:
(source_lines, lineno) or None:
source_lines
is a list of strings, andlineno
is an integer.EXAMPLES:
sage: from sage.misc.sageinspect import sage_getsourcelines sage: sage_getsourcelines(matrix)[1] 26 sage: sage_getsourcelines(matrix)[0][0][6:] 'MatrixFactory(object):\n'
Some classes customize this using a
_sage_src_lines_
method, which gives the source lines of a class instance, but not the class itself. We demonstrate this forCachedFunction
:sage: cachedfib = cached_function(fibonacci) sage: sage_getsourcelines(cachedfib)[0][0] 'def fibonacci(n, algorithm="pari"):\n' sage: sage_getsourcelines(type(cachedfib))[0][0] 'cdef class CachedFunction(object):\n'
AUTHORS:
- William Stein
- Extensions by Nick Alexander
- Extension to interactive Cython code by Simon King
- Simon King: If a class has no docstring then let the class
definition be found starting from the
__init__
method. - Simon King: Get source lines for dynamic classes.
-
sage.misc.sageinspect.
sage_getvariablename
(self, omit_underscore_names=True)¶ Attempt to get the name of a Sage object.
INPUT:
self
– any object.omit_underscore_names
– boolean, defaultTrue
.
OUTPUT:
If the user has assigned an object
obj
to a variable name, then return that variable name. If several variables point toobj
, return a sorted list of those names. Ifomit_underscore_names
is True (the default) then omit names starting with an underscore “_”.This is a modified version of code taken from http://pythonic.pocoo.org/2009/5/30/finding-objects-names, written by Georg Brandl.
EXAMPLES:
sage: from sage.misc.sageinspect import sage_getvariablename sage: A = random_matrix(ZZ, 100) sage: sage_getvariablename(A) 'A' sage: B = A sage: sage_getvariablename(A) ['A', 'B']
If an object is not assigned to a variable, an empty list is returned:
sage: sage_getvariablename(random_matrix(ZZ, 60)) []