This module extends parts of Python’s inspect module to Cython objects.
AUTHORS:
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:...'
Python classes with no docstring, but an __init__ docstring:
sage: class Foo:
... def __init__(self):
... 'docstring'
... pass
...
sage: sage_getdoc(Foo)
'docstring\n'
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, True)[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, there is no argspec extractable from builtins:
sage: sage_getdef(''.find, 'find')
'find( [noargspec] )'
sage: sage_getdef(str.find, 'find')
'find( [noargspec] )'
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'}))
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.
x.__init__(...) initializes x; see help(type(x)) for signature
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)
{'a': ('e', 2, [None, ({False: True}, 'pi')]), 37.0: 'temp'}
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 a Python AST ast.BinOp node.
INPUT:
OUTPUT:
AUTHOR:
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
[11, 15, 6, 2, 2, 48]
Visit a Python AST ast.BoolOp node.
INPUT:
OUTPUT:
AUTHOR:
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 a Python AST ast.Compare node.
INPUT:
OUTPUT:
AUTHOR:
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 a Python AST ast.Dict node.
INPUT:
OUTPUT:
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 a Python AST ast.List node.
INPUT:
OUTPUT:
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 a Python AST ast.Name node.
INPUT:
OUTPUT:
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']]
[<type 'bool'>, <type 'bool'>, <type 'NoneType'>, <type 'str'>, <type 'str'>]
Visit a Python AST ast.Num node.
INPUT:
OUTPUT:
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 a Python AST ast.Str node.
INPUT:
OUTPUT:
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 a Python AST ast.Tuple node.
INPUT:
OUTPUT:
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 a Python AST ast.BinOp node.
INPUT:
OUTPUT:
AUTHOR:
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]
Checks if argument is instance of non built-in class
INPUT: obj - object
EXAMPLES:
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
Return the names and default values of a function’s arguments.
INPUT:
obj, any callable object
OUTPUT:
An ArgSpec is returned. This is a named tuple (args, varargs, keywords, defaults).
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 the CachedMethod 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=['self', 'x'], varargs='args', keywords='kwds', defaults=(0,))
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,))
TESTS:
By trac ticket #9976, rather complicated cases work. In the following example, we dynamically create an extension class that returns some source code, and the example shows that the source code is taken for granted, i.e., the argspec of an instance of that class does not coincide with the argspec of its call method. That behaviour is intended, since a decorated method appears to have the generic signature *args,**kwds, but in fact it is only supposed to be called with the arguments requested by the underlying undecorated method. We saw an easy example above, namely I.groebner_basis. Here is a more difficult one:
sage: cython_code = [
... 'cdef class MyClass:',
... ' def _sage_src_(self):',
... ' return "def foo(x, a=\\\')\\\"\\\', b={(2+1):\\\'bar\\\', not 1:3, 3<<4:5}): return\\n"',
... ' def __call__(self, m,n): return "something"']
sage: cython('\n'.join(cython_code))
sage: O = MyClass()
sage: print sage.misc.sageinspect.sage_getsource(O)
def foo(x, a=')"', b={(2+1):'bar', not 1:3, 3<<4:5}): return
sage: sage.misc.sageinspect.sage_getargspec(O)
ArgSpec(args=['x', 'a', 'b'], varargs=None, keywords=None, defaults=(')"', {False: 3, 48: 5, 3: 'bar'}))
sage: sage.misc.sageinspect.sage_getargspec(O.__call__)
ArgSpec(args=['self', 'm', 'n'], varargs=None, keywords=None, defaults=None)
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, (), ()))
AUTHORS:
Return the definition header for any callable object.
INPUT:
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:
Return the docstring associated to obj as a string.
INPUT: obj, a function, module, etc.: something with a docstring.
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.
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:
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'
AUTHORS:
Return the source code associated to obj as a string, or None.
INPUT:
EXAMPLES:
sage: from sage.misc.sageinspect import sage_getsource
sage: sage_getsource(identity_matrix, True)[19:60]
'identity_matrix(ring, n=0, sparse=False):'
sage: sage_getsource(identity_matrix, False)[19:60]
'identity_matrix(ring, n=0, sparse=False):'
AUTHORS:
Return a pair ([source_lines], starting line number) of the source code associated to obj, or None.
INPUT:
OUTPUT: (source_lines, lineno) or None: source_lines is a list of strings, and lineno is an integer.
At this time we ignore is_binary in favour of a ‘do our best’ strategy.
EXAMPLES:
sage: from sage.misc.sageinspect import sage_getsourcelines
sage: sage_getsourcelines(matrix, True)[1]
732
sage: sage_getsourcelines(matrix, False)[0][0][6:]
'MatrixFactory(object):\n'
TESTS:
sage: cython('''cpdef test_funct(x,y): return''')
sage: sage_getsourcelines(test_funct)
(['cpdef test_funct(x,y): return\n'], 6)
The following tests that an instance of functools.partial is correctly dealt with (see trac ticket #9976):
sage: obj = sage.combinat.partition_algebra.SetPartitionsAk
sage: sage_getsourcelines(obj)
(['def create_set_partition_function(letter, k):\n',
...
' raise ValueError("k must be an integer or an integer + 1/2")\n'], 34)
Here are some cases that were covered in :trac`11298`; note that line numbers may easily change, and therefore we do not test them:
sage: P.<x,y> = QQ[]
sage: I = P*[x,y]
sage: sage_getsourcelines(P)
(['cdef class MPolynomialRing_libsingular(MPolynomialRing_generic):\n',
'\n',
' def __cinit__(self):\n',
...
' M.append(new_MP(self, p_Copy(tempvector, _ring)))\n',
' return M\n'], ...)
sage: sage_getsourcelines(I)
(['class MPolynomialIdeal( MPolynomialIdeal_singular_repr, \\\n',
...
' return result_ring.ideal(result)\n'], ...)
sage: x = var('x')
sage: sage_getsourcelines(x)
(['cdef class Expression(CommutativeRingElement):\n',
' cpdef object pyobject(self):\n',
...
' return self / x\n'], ...)
We show some enhancements provided by trac ticket #11768. First, we use a dummy parent class that has defined an element class by a nested class definition:
sage: from sage.misc.nested_class_test import TestNestedParent
sage: from sage.misc.sageinspect import sage_getsource
sage: P = TestNestedParent()
sage: E = P.element_class
sage: E.__bases__
(<class sage.misc.nested_class_test.TestNestedParent.Element at ...>,
<class 'sage.categories.sets_cat.Sets.element_class'>)
sage: print sage_getsource(E)
class Element:
"This is a dummy element class"
pass
sage: print sage_getsource(P)
class TestNestedParent(UniqueRepresentation, Parent):
...
class Element:
"This is a dummy element class"
pass
Here is another example that relies on a nested class definition in the background:
sage: C = Rings()
sage: HC = C.hom_category()
sage: sage_getsourcelines(HC)
([' class HomCategory(HomCategory):\n', ...], ...)
Testing against a bug that has occured during work on #11768:
sage: P.<x,y> = QQ[]
sage: I = P*[x,y]
sage: sage_getsourcelines(I)
(['class MPolynomialIdeal( MPolynomialIdeal_singular_repr, \\\n',
' MPolynomialIdeal_macaulay2_repr, \\\n',
' MPolynomialIdeal_magma_repr, \\\n',
' Ideal_generic ):\n',
' def __init__(self, ring, gens, coerce=True):\n',
...
' return result_ring.ideal(result)\n'], ...)
AUTHORS:
Attempt to get the name of a Sage object.
INPUT:
OUTPUT:
If the user has assigned an object obj to a variable name, then return that variable name. If several variables point to obj, return a sorted list of those names. If omit_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))
[]