Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
r""" 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:`9976` and :trac:`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'}))
""" from __future__ import print_function, absolute_import from six.moves import range from six import iteritems, string_types, class_types, text_type from sage.misc.six import u
import ast import inspect import functools import os import tokenize import types import re EMBEDDED_MODE = False from sage.env import SAGE_SRC
def loadable_module_extension(): r""" 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 """ return os.path.extsep+'dll' else:
def isclassinstance(obj): r""" 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 """ hasattr(obj, '__class__') and \ hasattr(obj.__class__, '__module__') and \ obj.__class__.__module__ not in ('__builtin__', 'exceptions'))
import re # Parse strings of form "File: sage/rings/rational.pyx (starting at line 1080)" # "\ " protects a space in re.VERBOSE mode. __embedded_position_re = re.compile(r''' ^ # anchor to the beginning of the line File:\ (?P<FILENAME>.*?) # match File: then filename \ \(starting\ at\ line\ (?P<LINENO>\d+)\) # match line number \n? # if there is a newline, eat it (?P<ORIGINAL>.*) # the original docstring is the end \Z # anchor to the end of the string ''', re.MULTILINE | re.DOTALL | re.VERBOSE)
# Parse Python identifiers __identifier_re = re.compile(r"^[^\d\W]\w*")
def _extract_embedded_position(docstring): r""" If docstring has a Cython embedded position, return a tuple (original_docstring, filename, line). If not, return None.
INPUT: ``docstring`` (string)
EXAMPLES::
sage: from sage.misc.sageinspect import _extract_embedded_position sage: import inspect sage: _extract_embedded_position(inspect.getdoc(var))[1][-21:] 'sage/calculus/var.pyx'
TESTS:
The following has been fixed in :trac:`13916`::
sage: cython('''cpdef test_funct(x,y): return''') sage: print(open(_extract_embedded_position(inspect.getdoc(test_funct))[1]).read()) cpdef test_funct(x,y): return
Ensure that the embedded filename of the compiled function is correct. In particular it should be relative to ``SPYX_TMP`` in order for certain docmentation functions to work properly. See :trac:`24097`::
sage: from sage.env import DOT_SAGE sage: from sage.misc.sage_ostools import restore_cwd sage: with restore_cwd(DOT_SAGE): ....: cython('''cpdef test_funct(x,y): return''') sage: print(open(_extract_embedded_position(inspect.getdoc(test_funct))[1]).read()) cpdef test_funct(x,y): return
AUTHORS:
- William Stein - Extensions by Nick Alexander - Extension for interactive Cython code by Simon King """
# Try some common path prefixes for Cython modules built by/for Sage # 1) Module in the sage src tree # 2) Module compiled by Sage's inline cython() compiler os.path.join(SAGE_SRC, raw_filename), os.path.join(SPYX_TMP, '_'.join(raw_filename.split('_')[:-1]), raw_filename) ] # Otherwise we keep the relative path and just hope it's relative to # the cwd; otherwise there's no way to be sure.
def _extract_embedded_signature(docstring, name): r""" If docstring starts with the embedded of a method called ``name``, return a tuple (original_docstring, argspec). If not, return (docstring, None).
See :trac:`17814`.
INPUT: ``docstring`` (string)
AUTHORS:
- Simon King
EXAMPLES::
sage: from sage.misc.sageinspect import _extract_embedded_signature sage: from sage.misc.nested_class import MainClass sage: print(_extract_embedded_signature(MainClass.NestedClass.NestedSubClass.dummy.__doc__, 'dummy')[0]) File: sage/misc/nested_class.pyx (starting at line 314) ... sage: _extract_embedded_signature(MainClass.NestedClass.NestedSubClass.dummy.__doc__, 'dummy')[1] ArgSpec(args=['self', 'x', 'r'], varargs='args', keywords='kwds', defaults=((1, 2, 3.4),)) sage: _extract_embedded_signature(range.__call__.__doc__, '__call__') ('x.__call__(...) <==> x(...)', None)
""" # If there is an embedded signature, it is in the first line # It is possible that the signature is of the form ClassName.method_name, # and thus we need to do the following:
class BlockFinder: """ Provide a tokeneater() method to detect the end of a code block.
This is the Python library's :class:`inspect.BlockFinder` modified to recognize Cython definitions. """ def __init__(self):
def tokeneater(self, type, token, srow_scol, erow_ecol, line): # look for the first "(cp)def", "class" or "lambda" self.islambda = True raise inspect.EndOfBlock # the end of matching indent/dedent pairs end a block # (note that this only works for "def"/"class" blocks, # not e.g. for "if: else:" or "try: finally:" blocks) # any other token on the same indentation level end the previous # block as well, except the pseudo-tokens COMMENT and NL.
def _getblock(lines): """ Extract the block of code at the top of the given list of lines.
This is the Python library's :func:`inspect.getblock`, except that it uses an instance of our custom :class:`BlockFinder`. """
def _extract_source(lines, lineno): r""" Given a list of lines or a multiline string and a starting lineno, _extract_source returns [source_lines]. [source_lines] is the smallest indentation block starting at lineno.
INPUT:
- ``lines`` - string or list of strings - ``lineno`` - positive integer
EXAMPLES::
sage: from sage.misc.sageinspect import _extract_source sage: s2 = "#hello\n\n class f():\n pass\n\n#goodbye" sage: _extract_source(s2, 3) [' class f():\n', ' pass\n'] """ raise ValueError("Line numbering starts at 1! (tried to extract line {})".format(lineno))
# Fixes an issue with getblock
class SageArgSpecVisitor(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 :func:`_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'] """ def visit_Name(self, node): """ Visit a Python AST :class:`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'>] """
def visit_Num(self, node): """ Visit a Python AST :class:`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] """
def visit_Str(self, node): r""" Visit a Python AST :class:`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'] """
def visit_List(self, node): """ Visit a Python AST :class:`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']]] """
def visit_Tuple(self, node): """ Visit a Python AST :class:`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')] """
def visit_Dict(self, node): """ Visit a Python AST :class:`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}] """
def visit_BoolOp(self, node): """ Visit a Python AST :class:`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]
"""
def visit_Compare(self, node): """ Visit a Python AST :class:`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]
""" if not left<=right: return False if not left>=right: return False return False return False
def visit_BinOp(self, node): """ Visit a Python AST :class:`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 # py2 [11, 15, 6, 2, 2, 48] """ return self.visit(node.left)&self.visit(node.right) return self.visit(node.left) == self.visit(node.right) return self.visit(node.left) != self.visit(node.right) return self.visit(node.left) not in self.visit(node.right) return self.visit(node.left) ** self.visit(node.right) return self.visit(node.left) >> self.visit(node.right) if op == 'Sub': return self.visit(node.left) - self.visit(node.right) if op == 'Gt': return self.visit(node.left) > self.visit(node.right) if op == 'GtE': return self.visit(node.left) >= self.visit(node.right) if op == 'In': return self.visit(node.left) in self.visit(node.right) if op == 'Is': return self.visit(node.left) is self.visit(node.right) if op == 'IsNot': return self.visit(node.left) is not self.visit(node.right) if op == 'Lt': return self.visit(node.left) < self.visit(node.right) if op == 'LtE': return self.visit(node.left) <= self.visit(node.right) if op == 'Mod': return self.visit(node.left) % self.visit(node.right)
def visit_UnaryOp(self, node): """ Visit a Python AST :class:`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]
"""
def _grep_first_pair_of_parentheses(s): """ Return the first matching pair of parentheses in a code string.
INPUT:
A string
OUTPUT:
A substring of the input, namely the part between the first (outmost) matching pair of parentheses (including the parentheses).
Parentheses between single or double quotation marks do not count. If no matching pair of parentheses can be found, a ``SyntaxError`` is raised.
EXAMPLES::
sage: from sage.misc.sageinspect import _grep_first_pair_of_parentheses sage: code = 'def foo(a="\'):", b=4):\n return' sage: _grep_first_pair_of_parentheses(code) '(a="\'):", b=4)' sage: code = 'def foo(a="%s):", \'b=4):\n return'%("'") sage: _grep_first_pair_of_parentheses(code) Traceback (most recent call last): ... SyntaxError: The given string does not contain balanced parentheses
""" escaped = not escaped else:
def _split_syntactical_unit(s): """ Split off a sub-expression from the start of a given string.
INPUT:
- ``s``, a string
OUTPUT:
A pair ``unit, s2``, such that ``unit`` is the string representation of a string (single or double quoted) or of a sub-expression surrounded by brackets (round, square or curly brackets), or of an identifier, or a single character, if none of the above is available. The given string ``s`` is obtained by appending some whitespace followed by ``s2`` to ``unit``.
Blank space between the units is removed.
EXAMPLES::
sage: from sage.misc.sageinspect import _split_syntactical_unit sage: s = "(Hel) lo_1=[)\"!\" ] '''? {world} '''?" sage: while s: ....: u, s = _split_syntactical_unit(s) ....: print(u) (Hel) lo_1 = [)"!"] '''? {world} ''' ?
If the string ends before the unit is completed (mispatching parentheses or missing quotation mark), then a syntax error is raised::
sage: s = "'''({SAGE}]" sage: _split_syntactical_unit(s) Traceback (most recent call last): ... SyntaxError: EOF while scanning string literal sage: s = "({SAGE}]" sage: _split_syntactical_unit(s) Traceback (most recent call last): ... SyntaxError: Syntactical group starting with '(' did not end with ')'
Numbers are not recognised::
sage: _split_syntactical_unit('123') ('1', '23')
TESTS:
The following was fixed in :trac:`16309`::
sage: _split_syntactical_unit('()): pass') ('()', '): pass')
""" return s # Split a given string at the next unescaped quotation mark # 1. s is a triple-quoted string a,b = split_string(s[3:], '"""') return '"""'+a+'"""', b.strip() a,b = split_string(s[4:], '"""') return 'r"""'+a+'"""', b.strip() a,b = split_string(s[4:], "'''") return "r'''"+a+"'''", b.strip()
# 2. s is a single-quoted string a,b = split_string(s[2:], '"') return 'r"'+a+'"', b.strip() a,b = split_string(s[2:], "'") return "r'"+a+"'", b.strip()
# 3. s is not a string # note that python would raise a syntax error # if the line contains anything but whitespace # after the backslash. But we assume here that # the input is syntactically correct. return _split_syntactical_unit(s[1:]) linebreak = s.index(os.linesep) if linebreak == -1: return '','' return '', s[linebreak:].strip() else:
def _sage_getargspec_from_ast(source): r""" Return an argspec for a Python function or method by compiling its source to an abstract-syntax tree (AST) and walking its ``args`` subtrees with :class:`SageArgSpecVisitor`. We use this in :func:`_sage_getargspec_cython`.
INPUT:
- ``source`` - a string; the function's (or method's) source code definition. The function's body is ignored.
OUTPUT:
- an instance of :obj:`inspect.ArgSpec`, i.e., a named tuple
EXAMPLES::
sage: import inspect, sage.misc.sageinspect as sms sage: from_ast = sms._sage_getargspec_from_ast sage: s = "def f(a, b=2, c={'a': [4, 5.5, False]}, d=(None, True)):\n return" sage: from_ast(s) ArgSpec(args=['a', 'b', 'c', 'd'], varargs=None, keywords=None, defaults=(2, {'a': [4, 5.5, False]}, (None, True))) sage: context = {} sage: exec compile(s, '<string>', 'single') in context sage: inspect.getargspec(context['f']) ArgSpec(args=['a', 'b', 'c', 'd'], varargs=None, keywords=None, defaults=(2, {'a': [4, 5.5, False]}, (None, True))) sage: from_ast(s) == inspect.getargspec(context['f']) True sage: set(from_ast(sms.sage_getsource(x)) == inspect.getargspec(x) for x in [factor, identity_matrix, Graph.__init__]) {True} """
tuple(defaults) if defaults else None)
def _sage_getargspec_cython(source): r""" inspect.getargspec from source code. That is, get the names and default values of a function's arguments.
INPUT:
- ``source`` - a string; the function's (or method's) source code definition. The function's body is ignored. The definition may contain type definitions for the function arguments.
OUTPUT:
- an instance of :obj:`inspect.ArgSpec`, i.e., a named tuple
EXAMPLES::
sage: from sage.misc.sageinspect import _sage_getargspec_cython as sgc sage: sgc("cpdef double abc(self, Element x=None, Parent base=0):") ArgSpec(args=['self', 'x', 'base'], varargs=None, keywords=None, defaults=(None, 0)) sage: sgc("def __init__(self, x=None, unsigned int base=0):") ArgSpec(args=['self', 'x', 'base'], varargs=None, keywords=None, defaults=(None, 0)) sage: sgc('def o(p, r={}, *q, **s) except? -1:') ArgSpec(args=['p', 'r'], varargs='q', keywords='s', defaults=({},)) sage: sgc('cpdef how(r=(None, "u:doing?")):') ArgSpec(args=['r'], varargs=None, keywords=None, defaults=((None, 'u:doing?'),)) sage: sgc('def _(x="):"):') ArgSpec(args=['x'], varargs=None, keywords=None, defaults=('):',)) sage: sgc('def f(z = {(1, 2, 3): True}):\n return z') ArgSpec(args=['z'], varargs=None, keywords=None, defaults=({(1, 2, 3): True},)) sage: sgc('def f(double x, z = {(1, 2, 3): True}):\n return z') ArgSpec(args=['x', 'z'], varargs=None, keywords=None, defaults=({(1, 2, 3): True},)) sage: sgc('def f(*args): pass') ArgSpec(args=[], varargs='args', keywords=None, defaults=None) sage: sgc('def f(**args): pass') ArgSpec(args=[], varargs=None, keywords='args', defaults=None)
Some malformed input is detected::
sage: sgc('def f(x,y') Traceback (most recent call last): ... SyntaxError: Unexpected EOF while parsing argument list sage: sgc('def f(*x = 5, z = {(1,2,3): True}): pass') Traceback (most recent call last): ... SyntaxError: invalid syntax sage: sgc('def f(int *x = 5, z = {(1,2,3): True}): pass') Traceback (most recent call last): ... SyntaxError: Pointer types not allowed in def or cpdef functions sage: sgc('def f(x = , z = {(1,2,3): True}): pass') Traceback (most recent call last): ... SyntaxError: Definition of a default argument expected sage: sgc('def f(int x = 5, , z = {(1,2,3): True}): pass') Traceback (most recent call last): ... SyntaxError: invalid syntax
TESTS:
Some input that is malformed in Python but wellformed in Cython is correctly parsed::
sage: def dummy_python(self,*args,x=1): pass Traceback (most recent call last): ... SyntaxError: invalid syntax sage: cython("def dummy_cython(self,*args,x=1): pass") sage: sgc("def dummy_cython(self,*args,x=1): pass") ArgSpec(args=['self', 'x'], varargs='args', keywords=None, defaults=(1,))
In some examples above, a syntax error was raised when a type definition contains a pointer. An exception is made for ``char*``, since C strings are acceptable input in public Cython functions::
sage: sgc('def f(char *x = "a string", z = {(1,2,3): True}): pass') ArgSpec(args=['x', 'z'], varargs=None, keywords=None, defaults=('a string', {(1, 2, 3): True}))
AUTHORS:
- Nick Alexander: original version - Simon King (02-2013): recognise varargs and default values in cython code, and return an ``ArgSpec``.
""" raise SyntaxError("Function definition must contain an argument list")
# We remove the type declarations, build a dummy Python function, and # then call _get_argspec_from_ast. This should be # better than creating a complete parser for Cython syntax, # even though _split_syntactical_unit does part of the parsing work anyway.
raise SyntaxError("The %s argument has no default"%('varargs' if nb_stars==1 else 'keywords')) continue else: continue else: raise SyntaxError("Unexpected EOF while parsing argument list") raise SyntaxError("The %s argument has no default"%('varargs' if nb_stars==1 else 'keywords')) else: # Is "name" part of a type definition? # If it is the last identifier before '=' or ',', # then it *is* a variable name, # skip the "=" or ",", since varargs # is treated separately else: raise SyntaxError("varargs can't be defined twice") # skip the "=" or ",", since varargs # is treated separately else: raise SyntaxError("varargs can't be defined twice") else: raise SyntaxError("variable declaration comprises at most two '*'") else: else: else: +varargs+keywords+'): pass')
def sage_getfile(obj): r""" 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:`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:`11298`::
sage: P.<x,y> = QQ[] sage: sage_getfile(P) '...sage/rings/polynomial/multi_polynomial_libsingular.pyx'
A problem fixed in :trac:`16309`::
sage: cython(''' ....: class Bar: pass ....: cdef class Foo: pass ....: ''') sage: sage_getfile(Bar) '...pyx' sage: sage_getfile(Foo) '...pyx'
By :trac:`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 """ # We try to extract from docstrings, but not using Python's inspect # because _sage_getdoc_unformatted is more robust.
# The instance case
# No go? fall back to inspect.
def sage_getargspec(obj): r""" 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)``.
- ``args`` is a list of the argument names (it may contain nested lists).
- ``varargs`` and ``keywords`` are the names of the ``*`` and ``**`` arguments or ``None``.
- ``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 the :class:`~sage.misc.cachefunc.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=['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:`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:`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 <BLANKLINE> 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:`11913`::
sage: sage.misc.sageinspect.sage_getargspec(r.lm)
The following was fixed in :trac:`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:`17009` is fixed::
sage: sage_getargspec(gap) ArgSpec(args=['self', 'x', 'name'], varargs=None, keywords=None, defaults=(None,))
By :trac:`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:`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. """ source = sage_getsource(obj) return inspect.ArgSpec(*_sage_getargspec_cython(source)) raise TypeError("obj is not a code object") # If we are lucky, the function signature is embedded in the docstring. # Note that this may give a wrong result for the constants! except (TypeError, AttributeError): pass # we try to find the definition and parse it by _sage_getargspec_ast # To fix trac #10860. See #11913 for more information. obj.__name__ == 'next'): # Handle sage.rings.ring.FiniteFieldIterator.next and similar # slot wrappers. This is mainly to suppress Sphinx warnings. return ['self'], None, None, None else: # We try to get the argspec by reading the source, which may be # expensive, but should only be needed for functions defined outside # of the Sage library (since otherwise the signature should be # embedded in the docstring) else:
# Otherwise we're (hopefully!) plain Python, so use inspect # The above "hopefully" was wishful thinking: # The best we can do is to return a generic argspec
def sage_getdef(obj, obj_name=''): r""" Return the definition header for any callable object.
INPUT:
- ``obj`` - function - ``obj_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:`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 """ # for use with typesetting the definition with the notebook: # sometimes s contains "*args" or "**keywds", and the # asterisks confuse ReST/sphinx/docutils, so escape them: # change * to \*, and change ** to \**. s = s.replace('**', '\\**') # replace ** with \** t = '' while True: # replace * with \* i = s.find('*') if i == -1: break elif i > 0 and s[i-1] == '\\': if s[i+1] == "*": t += s[:i+2] s = s[i+2:] else: t += s[:i+1] s = s[i+1:] continue elif i > 0 and s[i-1] == '*': t += s[:i+1] s = s[i+1:] continue else: t += s[:i] + '\\*' s = s[i+1:] s = t + s except (AttributeError, TypeError, ValueError): return '%s( [noargspec] )'%obj_name
def _sage_getdoc_unformatted(obj): r""" Return the unformatted docstring associated to ``obj`` as a string.
If ``obj`` is a Cython object with an embedded position in its docstring, the embedded position is **not** stripped.
INPUT:
- ``obj`` -- a function, module, etc.: something with a docstring.
EXAMPLES::
sage: from sage.misc.sageinspect import _sage_getdoc_unformatted sage: print(_sage_getdoc_unformatted(sage.rings.integer.Integer)) Integer(x=None, base=0) File: sage/rings/integer.pyx (starting at line ...) <BLANKLINE> The ``Integer`` class represents arbitrary precision integers. It derives from the ``Element`` class, so integers can be used as ring elements anywhere in Sage. ...
TESTS:
Test that we suppress useless built-in output (:trac:`3342`)::
sage: from sage.misc.sageinspect import _sage_getdoc_unformatted sage: _sage_getdoc_unformatted(isinstance.__class__) ''
Construct an object raising an exception when accessing the ``__doc__`` attribute. This should not give an error in ``_sage_getdoc_unformatted``, see :trac:`19671`::
sage: class NoSageDoc(object): ....: @property ....: def __doc__(self): ....: raise Exception("no doc here") sage: obj = NoSageDoc() sage: obj.__doc__ Traceback (most recent call last): ... Exception: no doc here sage: _sage_getdoc_unformatted(obj) ''
AUTHORS:
- William Stein - extensions by Nick Alexander """ return ''
# Check if the __doc__ attribute was actually a string, and # not a 'getset_descriptor' or similar. # On Python 2, we want str, not unicode return r.encode('utf-8', 'ignore') else: # Not a string of any kind
def sage_getdoc_original(obj): r""" 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 of ``obj.__init__`` is returned instead.
Feed the results from this into the function :func:`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)) <BLANKLINE> The ``Integer`` class represents arbitrary precision integers. It derives from the ``Element`` class, so integers can be used as ring elements anywhere in Sage. ...
If the class does not have a docstring, the docstring of the ``__init__`` method is used, but not the ``__init__`` method of the base class (this was fixed in :trac:`24936`)::
sage: from sage.categories.category import Category sage: class A(Category): ....: def __init__(self): ....: '''The __init__ docstring''' sage: sage_getdoc_original(A) 'The __init__ docstring' sage: class B(Category): ....: pass sage: sage_getdoc_original(B) ''
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
""" # typ is the type corresponding to obj, which is obj itself if # that was a type or old-style class else:
# The docstring of obj is empty. To get something, we want to use # the documentation of the __init__ method, but only if it belongs # to (the type of) obj.
def sage_getdoc(obj, obj_name='', embedded_override=False): r""" 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 """
# Fix object naming obj_name = obj_name[:i]
def sage_getsource(obj): r""" 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 """ #First we should check if the object has a _sage_src_ #method. If it does, we just return the output from #that. This is useful for getting pexpect interface #elements to behave similar to regular Python objects #with respect to introspection.
return None
def _sage_getsourcelines_name_with_dot(obj): r""" Get the source lines of an object whose name contains a dot and whose source lines can not be obtained by different methods.
EXAMPLES::
sage: C = Rings() sage: from sage.misc.sageinspect import sage_getsource sage: print(sage_getsource(C.parent_class)) #indirect doctest class ParentMethods: ... Returns the Lie bracket `[x, y] = x y - y x` of `x` and `y`. ...
TESTS:
The following was fixed in :trac:`16309`::
sage: cython(''' ....: class A: ....: def __init__(self): ....: "some init doc" ....: pass ....: class B: ....: "some class doc" ....: class A(A): ....: pass ....: ''') sage: B.A.__name__ 'A' sage: B.A.__qualname__ 'B.A' sage: sage_getsource(B.A) ' class A(A):\n pass\n\n'
Note that for this example to work, it is essential that the class ``B`` has a docstring. Otherwise, the code of ``B`` could not be found (Cython inserts embedding information into the docstring) and thus the code of ``B.A`` couldn't be found either.
AUTHOR:
- Simon King (2011-09) """ # First, split the name: else: splitted_name = obj.__name__ except ImportError: try: B = obj.__base__ if B is None: raise AttributeError except AttributeError: raise IOError("could not get source code") return sage_getsourcelines(B) # M should just be the top-most module. # Hence, normally it is just 'sage' except AttributeError: try: B = obj.__base__ if B is None: raise AttributeError except AttributeError: raise IOError("could not get source code") return sage_getsourcelines(B)
# the rest of the function is copied from # inspect.findsource raise IOError('could not get source code')
return lines, base_lineno
# make some effort to find the best matching class definition: # use the one with the least indentation, which is the one # that's most probably not inside a function definition. # if it's at toplevel, it's already the best one return inspect.getblock(lines[i:]), i+base_lineno # else add whitespace to candidate list # this will sort by whitespace, and by line number, # less whitespace first else: raise IOError('could not find class definition')
if inspect.ismethod(obj): obj = obj.__func__ if inspect.isfunction(obj): obj = obj.__code__ if inspect.istraceback(obj): obj = obj.tb_frame if inspect.isframe(obj): obj = obj.f_code if inspect.iscode(obj): if not hasattr(obj, 'co_firstlineno'): raise IOError('could not find function definition') pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)') pmatch = pat.match # fperez - fix: sometimes, co_firstlineno can give a number larger than # the length of lines, which causes an error. Safeguard against that. lnum = min(obj.co_firstlineno,len(lines))-1 while lnum > 0: if pmatch(lines[lnum]): break lnum -= 1
return inspect.getblock(lines[lnum:]), lnum+base_lineno raise IOError('could not find code object')
def sage_getsourcelines(obj): r""" 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, and ``lineno`` is an integer.
EXAMPLES::
sage: from sage.misc.sageinspect import sage_getsourcelines sage: sage_getsourcelines(matrix)[1] 28 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 for :class:`CachedFunction`::
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'
TESTS::
sage: cython('''cpdef test_funct(x,y): return''') sage: sage_getsourcelines(test_funct) (['cpdef test_funct(x,y): return\n'], 1)
The following tests that an instance of ``functools.partial`` is correctly dealt with (see :trac:`9976`)::
sage: from sage.tests.functools_partial_src import test_func sage: sage_getsourcelines(test_func) (['def base(x):\n', ... ' return x\n'], 7)
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', ...) sage: sage_getsourcelines(I) (['class MPolynomialIdeal( MPolynomialIdeal_singular_repr, \\\n', ...) sage: x = var('x') sage: sage_getsourcelines(x) (['cdef class Expression(CommutativeRingElement):\n', ' cpdef object pyobject(self):\n', ...) sage: sage_getsourcelines(x)[0][-1] # last line ' return S\n'
We show some enhancements provided by :trac:`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 = AdditiveMagmas() sage: HC = C.Homsets() sage: sage_getsourcelines(HC) ([' class Homsets(HomsetsCategory):\n', ...], ...)
Testing against a bug that has occured during work on :trac:`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', ...)
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. """ # First try the method _sage_src_lines_(), which is meant to give # the source lines of an object (not of its type!). else: # NotImplementedError can be raised by _sage_src_lines_() # to indicate that it didn't find the source lines. # # TypeError can happen when obj is a type and # obj._sage_src_lines_ is an unbound method. In this case, # we don't want to use _sage_src_lines_(), we just want to # get the source of the type itself.
# Check if we deal with an instance else:
# First, we deal with nested classes. Their name contains a dot, and we # have a special function for that purpose. # That happens for ParentMethods # of categories
# Next, we try _sage_getdoc_unformatted() # BEWARE HERE # inspect gives str (=bytes) in python2 # and str (=unicode) in python3
except AttributeError: pass else: except AttributeError: B = None
except IOError: try: from sage.misc.misc import SPYX_TMP raw_name = filename.split('/')[-1] newname = os.path.join(SPYX_TMP, '_'.join(raw_name.split('_')[:-1]), raw_name) source_lines = open(newname).readlines() except IOError: return None
# It is possible that the source lines belong to the __init__ method, # rather than to the class. So, we try to look back and find the class # definition. if double_quote is None: double_quote=False if double_quote is False: ignore = not ignore # We are not inside a doc string. So, if the indentation # is less than the indentation of the __init__ method # then we must be at the class definition!
def sage_getvariablename(self, omit_underscore_names=True): """ Attempt to get the name of a Sage object.
INPUT:
- ``self`` -- any object.
- ``omit_underscore_names`` -- boolean, default ``True``.
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)) [] """ else:
__internal_teststring = ''' import os # 1 # preceding comment not include # 2 def test1(a, b=2): # 3 if a: # 4 return 1 # 5 return b # 6 # intervening comment not included # 7 class test2(): # 8 pass # 9 # indented comment not included # 10 # trailing comment not included # 11 def test3(b, # 12 a=2): # 13 pass # EOF # 14'''
def __internal_tests(): r""" Test internals of the sageinspect module.
EXAMPLES::
sage: from sage.misc.sageinspect import * sage: from sage.misc.sageinspect import _extract_source, _extract_embedded_position, _sage_getargspec_cython, __internal_teststring
If docstring is None, nothing bad happens::
sage: sage_getdoc(None) ''
sage: sage_getsource(sage) '...all...'
A cython function with default arguments (one of which is a string)::
sage: sage_getdef(sage.rings.integer.Integer.factor, obj_name='factor') "factor(algorithm='pari', proof=None, limit=None, int_=False, verbose=0)"
This used to be problematic, but was fixed in :trac:`10094`::
sage: sage_getsource(sage.rings.integer.Integer.__init__) ' def __init__(self, x=None, base=0):\n...' sage: sage_getdef(sage.rings.integer.Integer.__init__, obj_name='__init__') '__init__(x=None, base=0)'
Test _extract_source with some likely configurations, including no trailing newline at the end of the file::
sage: s = __internal_teststring.strip() sage: es = lambda ls, l: ''.join(_extract_source(ls, l)).rstrip()
sage: print(es(s, 3)) def test1(a, b=2): # 3 if a: # 4 return 1 # 5 return b # 6
sage: print(es(s, 8)) class test2(): # 8 pass # 9
sage: print(es(s, 12)) def test3(b, # 12 a=2): # 13 pass # EOF # 14
Test _sage_getargspec_cython with multiple default arguments and a type::
sage: _sage_getargspec_cython("def init(self, x=None, base=0):") ArgSpec(args=['self', 'x', 'base'], varargs=None, keywords=None, defaults=(None, 0)) sage: _sage_getargspec_cython("def __init__(self, x=None, base=0):") ArgSpec(args=['self', 'x', 'base'], varargs=None, keywords=None, defaults=(None, 0)) sage: _sage_getargspec_cython("def __init__(self, x=None, unsigned int base=0, **keys):") ArgSpec(args=['self', 'x', 'base'], varargs=None, keywords='keys', defaults=(None, 0))
Test _extract_embedded_position:
We cannot test the filename since it depends on SAGE_SRC.
Make sure things work with no trailing newline::
sage: _extract_embedded_position('File: sage/rings/rational.pyx (starting at line 1080)') ('', '.../rational.pyx', 1080)
And with a trailing newline::
sage: s = 'File: sage/rings/rational.pyx (starting at line 1080)\n' sage: _extract_embedded_position(s) ('', '.../rational.pyx', 1080)
And with an original docstring::
sage: s = 'File: sage/rings/rational.pyx (starting at line 1080)\noriginal' sage: _extract_embedded_position(s) ('original', '.../rational.pyx', 1080)
And with a complicated original docstring::
sage: s = 'File: sage/rings/rational.pyx (starting at line 1080)\n\n\noriginal test\noriginal' sage: _extract_embedded_position(s) ('\n\noriginal test\noriginal', ..., 1080)
sage: s = 'no embedded position' sage: _extract_embedded_position(s) is None True """ |