passagemath-objects 10.6.41__cp314-cp314t-macosx_13_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of passagemath-objects might be problematic. Click here for more details.
- passagemath_objects/.dylibs/libgmp.10.dylib +0 -0
- passagemath_objects/__init__.py +3 -0
- passagemath_objects-10.6.41.dist-info/METADATA +115 -0
- passagemath_objects-10.6.41.dist-info/RECORD +280 -0
- passagemath_objects-10.6.41.dist-info/WHEEL +6 -0
- passagemath_objects-10.6.41.dist-info/top_level.txt +3 -0
- sage/all__sagemath_objects.py +37 -0
- sage/arith/all__sagemath_objects.py +5 -0
- sage/arith/long.pxd +411 -0
- sage/arith/numerical_approx.cpython-314t-darwin.so +0 -0
- sage/arith/numerical_approx.pxd +35 -0
- sage/arith/numerical_approx.pyx +75 -0
- sage/arith/power.cpython-314t-darwin.so +0 -0
- sage/arith/power.pxd +31 -0
- sage/arith/power.pyx +127 -0
- sage/categories/action.cpython-314t-darwin.so +0 -0
- sage/categories/action.pxd +29 -0
- sage/categories/action.pyx +641 -0
- sage/categories/algebra_functor.py +745 -0
- sage/categories/all__sagemath_objects.py +33 -0
- sage/categories/basic.py +62 -0
- sage/categories/cartesian_product.py +295 -0
- sage/categories/category.py +3401 -0
- sage/categories/category_cy_helper.cpython-314t-darwin.so +0 -0
- sage/categories/category_cy_helper.pxd +8 -0
- sage/categories/category_cy_helper.pyx +322 -0
- sage/categories/category_singleton.cpython-314t-darwin.so +0 -0
- sage/categories/category_singleton.pxd +3 -0
- sage/categories/category_singleton.pyx +342 -0
- sage/categories/category_types.py +637 -0
- sage/categories/category_with_axiom.py +2876 -0
- sage/categories/covariant_functorial_construction.py +703 -0
- sage/categories/facade_sets.py +228 -0
- sage/categories/functor.cpython-314t-darwin.so +0 -0
- sage/categories/functor.pxd +7 -0
- sage/categories/functor.pyx +691 -0
- sage/categories/homset.py +1338 -0
- sage/categories/homsets.py +364 -0
- sage/categories/isomorphic_objects.py +73 -0
- sage/categories/map.cpython-314t-darwin.so +0 -0
- sage/categories/map.pxd +34 -0
- sage/categories/map.pyx +2112 -0
- sage/categories/morphism.cpython-314t-darwin.so +0 -0
- sage/categories/morphism.pxd +14 -0
- sage/categories/morphism.pyx +895 -0
- sage/categories/objects.py +167 -0
- sage/categories/primer.py +1696 -0
- sage/categories/pushout.py +4834 -0
- sage/categories/quotients.py +64 -0
- sage/categories/realizations.py +200 -0
- sage/categories/sets_cat.py +3228 -0
- sage/categories/sets_with_partial_maps.py +52 -0
- sage/categories/subobjects.py +64 -0
- sage/categories/subquotients.py +21 -0
- sage/categories/with_realizations.py +311 -0
- sage/cpython/__init__.py +19 -0
- sage/cpython/_py2_random.py +619 -0
- sage/cpython/all.py +3 -0
- sage/cpython/atexit.cpython-314t-darwin.so +0 -0
- sage/cpython/atexit.pyx +269 -0
- sage/cpython/builtin_types.cpython-314t-darwin.so +0 -0
- sage/cpython/builtin_types.pyx +7 -0
- sage/cpython/cython_metaclass.cpython-314t-darwin.so +0 -0
- sage/cpython/cython_metaclass.h +117 -0
- sage/cpython/cython_metaclass.pxd +3 -0
- sage/cpython/cython_metaclass.pyx +130 -0
- sage/cpython/debug.cpython-314t-darwin.so +0 -0
- sage/cpython/debug.pyx +302 -0
- sage/cpython/dict_del_by_value.cpython-314t-darwin.so +0 -0
- sage/cpython/dict_del_by_value.pxd +9 -0
- sage/cpython/dict_del_by_value.pyx +191 -0
- sage/cpython/dict_internal.h +245 -0
- sage/cpython/getattr.cpython-314t-darwin.so +0 -0
- sage/cpython/getattr.pxd +9 -0
- sage/cpython/getattr.pyx +439 -0
- sage/cpython/pycore_long.h +97 -0
- sage/cpython/pycore_long.pxd +10 -0
- sage/cpython/python_debug.h +44 -0
- sage/cpython/python_debug.pxd +47 -0
- sage/cpython/pyx_visit.h +13 -0
- sage/cpython/string.cpython-314t-darwin.so +0 -0
- sage/cpython/string.pxd +76 -0
- sage/cpython/string.pyx +34 -0
- sage/cpython/string_impl.h +60 -0
- sage/cpython/type.cpython-314t-darwin.so +0 -0
- sage/cpython/type.pxd +2 -0
- sage/cpython/type.pyx +40 -0
- sage/cpython/wrapperdescr.pxd +67 -0
- sage/ext/all__sagemath_objects.py +3 -0
- sage/ext/ccobject.h +64 -0
- sage/ext/cplusplus.pxd +17 -0
- sage/ext/mod_int.h +30 -0
- sage/ext/mod_int.pxd +24 -0
- sage/ext/stdsage.pxd +39 -0
- sage/groups/all__sagemath_objects.py +1 -0
- sage/groups/group.cpython-314t-darwin.so +0 -0
- sage/groups/group.pxd +14 -0
- sage/groups/group.pyx +322 -0
- sage/groups/old.cpython-314t-darwin.so +0 -0
- sage/groups/old.pxd +14 -0
- sage/groups/old.pyx +219 -0
- sage/libs/all__sagemath_objects.py +3 -0
- sage/libs/gmp/__init__.py +1 -0
- sage/libs/gmp/all.pxd +6 -0
- sage/libs/gmp/binop.pxd +23 -0
- sage/libs/gmp/misc.pxd +8 -0
- sage/libs/gmp/mpf.pxd +88 -0
- sage/libs/gmp/mpn.pxd +57 -0
- sage/libs/gmp/mpq.pxd +57 -0
- sage/libs/gmp/mpz.pxd +202 -0
- sage/libs/gmp/pylong.cpython-314t-darwin.so +0 -0
- sage/libs/gmp/pylong.pxd +12 -0
- sage/libs/gmp/pylong.pyx +150 -0
- sage/libs/gmp/random.pxd +25 -0
- sage/libs/gmp/randomize.pxd +59 -0
- sage/libs/gmp/types.pxd +53 -0
- sage/libs/gmpxx.pxd +19 -0
- sage/misc/abstract_method.py +276 -0
- sage/misc/all__sagemath_objects.py +43 -0
- sage/misc/bindable_class.py +253 -0
- sage/misc/c3_controlled.cpython-314t-darwin.so +0 -0
- sage/misc/c3_controlled.pxd +2 -0
- sage/misc/c3_controlled.pyx +1402 -0
- sage/misc/cachefunc.cpython-314t-darwin.so +0 -0
- sage/misc/cachefunc.pxd +43 -0
- sage/misc/cachefunc.pyx +3781 -0
- sage/misc/call.py +188 -0
- sage/misc/classcall_metaclass.cpython-314t-darwin.so +0 -0
- sage/misc/classcall_metaclass.pxd +14 -0
- sage/misc/classcall_metaclass.pyx +599 -0
- sage/misc/constant_function.cpython-314t-darwin.so +0 -0
- sage/misc/constant_function.pyx +130 -0
- sage/misc/decorators.py +747 -0
- sage/misc/fast_methods.cpython-314t-darwin.so +0 -0
- sage/misc/fast_methods.pxd +20 -0
- sage/misc/fast_methods.pyx +351 -0
- sage/misc/flatten.py +90 -0
- sage/misc/fpickle.cpython-314t-darwin.so +0 -0
- sage/misc/fpickle.pyx +177 -0
- sage/misc/function_mangling.cpython-314t-darwin.so +0 -0
- sage/misc/function_mangling.pxd +11 -0
- sage/misc/function_mangling.pyx +308 -0
- sage/misc/inherit_comparison.cpython-314t-darwin.so +0 -0
- sage/misc/inherit_comparison.pxd +5 -0
- sage/misc/inherit_comparison.pyx +105 -0
- sage/misc/instancedoc.cpython-314t-darwin.so +0 -0
- sage/misc/instancedoc.pyx +331 -0
- sage/misc/lazy_attribute.cpython-314t-darwin.so +0 -0
- sage/misc/lazy_attribute.pyx +607 -0
- sage/misc/lazy_format.py +135 -0
- sage/misc/lazy_import.cpython-314t-darwin.so +0 -0
- sage/misc/lazy_import.pyx +1299 -0
- sage/misc/lazy_import_cache.py +36 -0
- sage/misc/lazy_list.cpython-314t-darwin.so +0 -0
- sage/misc/lazy_list.pxd +19 -0
- sage/misc/lazy_list.pyx +1187 -0
- sage/misc/lazy_string.cpython-314t-darwin.so +0 -0
- sage/misc/lazy_string.pxd +7 -0
- sage/misc/lazy_string.pyx +546 -0
- sage/misc/misc.py +1066 -0
- sage/misc/misc_c.cpython-314t-darwin.so +0 -0
- sage/misc/misc_c.pxd +3 -0
- sage/misc/misc_c.pyx +766 -0
- sage/misc/namespace_package.py +37 -0
- sage/misc/nested_class.cpython-314t-darwin.so +0 -0
- sage/misc/nested_class.pxd +3 -0
- sage/misc/nested_class.pyx +394 -0
- sage/misc/persist.cpython-314t-darwin.so +0 -0
- sage/misc/persist.pyx +1251 -0
- sage/misc/prandom.py +418 -0
- sage/misc/randstate.cpython-314t-darwin.so +0 -0
- sage/misc/randstate.pxd +30 -0
- sage/misc/randstate.pyx +1059 -0
- sage/misc/repr.py +203 -0
- sage/misc/reset.cpython-314t-darwin.so +0 -0
- sage/misc/reset.pyx +196 -0
- sage/misc/sage_ostools.cpython-314t-darwin.so +0 -0
- sage/misc/sage_ostools.pyx +323 -0
- sage/misc/sage_timeit.py +276 -0
- sage/misc/sage_timeit_class.cpython-314t-darwin.so +0 -0
- sage/misc/sage_timeit_class.pyx +120 -0
- sage/misc/sage_unittest.py +637 -0
- sage/misc/sageinspect.py +2768 -0
- sage/misc/session.cpython-314t-darwin.so +0 -0
- sage/misc/session.pyx +392 -0
- sage/misc/superseded.py +557 -0
- sage/misc/test_nested_class.py +228 -0
- sage/misc/timing.py +264 -0
- sage/misc/unknown.py +222 -0
- sage/misc/verbose.py +253 -0
- sage/misc/weak_dict.cpython-314t-darwin.so +0 -0
- sage/misc/weak_dict.pxd +15 -0
- sage/misc/weak_dict.pyx +1231 -0
- sage/modules/all__sagemath_objects.py +1 -0
- sage/modules/module.cpython-314t-darwin.so +0 -0
- sage/modules/module.pxd +5 -0
- sage/modules/module.pyx +329 -0
- sage/rings/all__sagemath_objects.py +3 -0
- sage/rings/integer_fake.h +22 -0
- sage/rings/integer_fake.pxd +55 -0
- sage/sets/all__sagemath_objects.py +3 -0
- sage/sets/pythonclass.cpython-314t-darwin.so +0 -0
- sage/sets/pythonclass.pxd +9 -0
- sage/sets/pythonclass.pyx +247 -0
- sage/structure/__init__.py +4 -0
- sage/structure/all.py +30 -0
- sage/structure/category_object.cpython-314t-darwin.so +0 -0
- sage/structure/category_object.pxd +28 -0
- sage/structure/category_object.pyx +1087 -0
- sage/structure/coerce.cpython-314t-darwin.so +0 -0
- sage/structure/coerce.pxd +44 -0
- sage/structure/coerce.pyx +2107 -0
- sage/structure/coerce_actions.cpython-314t-darwin.so +0 -0
- sage/structure/coerce_actions.pxd +27 -0
- sage/structure/coerce_actions.pyx +988 -0
- sage/structure/coerce_dict.cpython-314t-darwin.so +0 -0
- sage/structure/coerce_dict.pxd +51 -0
- sage/structure/coerce_dict.pyx +1557 -0
- sage/structure/coerce_exceptions.py +23 -0
- sage/structure/coerce_maps.cpython-314t-darwin.so +0 -0
- sage/structure/coerce_maps.pxd +28 -0
- sage/structure/coerce_maps.pyx +718 -0
- sage/structure/debug_options.cpython-314t-darwin.so +0 -0
- sage/structure/debug_options.pxd +6 -0
- sage/structure/debug_options.pyx +54 -0
- sage/structure/dynamic_class.py +541 -0
- sage/structure/element.cpython-314t-darwin.so +0 -0
- sage/structure/element.pxd +272 -0
- sage/structure/element.pyx +4772 -0
- sage/structure/element_wrapper.cpython-314t-darwin.so +0 -0
- sage/structure/element_wrapper.pxd +12 -0
- sage/structure/element_wrapper.pyx +582 -0
- sage/structure/factorization.py +1422 -0
- sage/structure/factorization_integer.py +105 -0
- sage/structure/factory.cpython-314t-darwin.so +0 -0
- sage/structure/factory.pyx +786 -0
- sage/structure/formal_sum.py +489 -0
- sage/structure/gens_py.py +73 -0
- sage/structure/global_options.py +1743 -0
- sage/structure/indexed_generators.py +863 -0
- sage/structure/list_clone.cpython-314t-darwin.so +0 -0
- sage/structure/list_clone.pxd +65 -0
- sage/structure/list_clone.pyx +1867 -0
- sage/structure/list_clone_demo.cpython-314t-darwin.so +0 -0
- sage/structure/list_clone_demo.pyx +248 -0
- sage/structure/list_clone_timings.py +179 -0
- sage/structure/list_clone_timings_cy.cpython-314t-darwin.so +0 -0
- sage/structure/list_clone_timings_cy.pyx +86 -0
- sage/structure/mutability.cpython-314t-darwin.so +0 -0
- sage/structure/mutability.pxd +21 -0
- sage/structure/mutability.pyx +348 -0
- sage/structure/nonexact.py +69 -0
- sage/structure/parent.cpython-314t-darwin.so +0 -0
- sage/structure/parent.pxd +112 -0
- sage/structure/parent.pyx +3093 -0
- sage/structure/parent_base.cpython-314t-darwin.so +0 -0
- sage/structure/parent_base.pxd +13 -0
- sage/structure/parent_base.pyx +44 -0
- sage/structure/parent_gens.cpython-314t-darwin.so +0 -0
- sage/structure/parent_gens.pxd +22 -0
- sage/structure/parent_gens.pyx +377 -0
- sage/structure/parent_old.cpython-314t-darwin.so +0 -0
- sage/structure/parent_old.pxd +25 -0
- sage/structure/parent_old.pyx +294 -0
- sage/structure/proof/__init__.py +1 -0
- sage/structure/proof/all.py +243 -0
- sage/structure/proof/proof.py +300 -0
- sage/structure/richcmp.cpython-314t-darwin.so +0 -0
- sage/structure/richcmp.pxd +213 -0
- sage/structure/richcmp.pyx +495 -0
- sage/structure/sage_object.cpython-314t-darwin.so +0 -0
- sage/structure/sage_object.pxd +3 -0
- sage/structure/sage_object.pyx +988 -0
- sage/structure/sage_object_test.py +19 -0
- sage/structure/sequence.py +937 -0
- sage/structure/set_factories.py +1178 -0
- sage/structure/set_factories_example.py +527 -0
- sage/structure/support_view.py +179 -0
- sage/structure/test_factory.py +56 -0
- sage/structure/unique_representation.py +1359 -0
sage/misc/sageinspect.py
ADDED
|
@@ -0,0 +1,2768 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-objects
|
|
2
|
+
r"""
|
|
3
|
+
Inspect Python, Sage, and Cython objects
|
|
4
|
+
|
|
5
|
+
This module extends parts of Python's inspect module to Cython objects.
|
|
6
|
+
|
|
7
|
+
EXAMPLES::
|
|
8
|
+
|
|
9
|
+
sage: from sage.misc.sageinspect import *
|
|
10
|
+
|
|
11
|
+
Test introspection of modules defined in Python and Cython files:
|
|
12
|
+
|
|
13
|
+
Cython modules::
|
|
14
|
+
|
|
15
|
+
sage: sage_getfile(sage.rings.rational)
|
|
16
|
+
'.../rational.pyx'
|
|
17
|
+
sage: sage_getdoc(sage.rings.rational).lstrip()
|
|
18
|
+
'Rational Numbers...'
|
|
19
|
+
sage: sage_getsource(sage.rings.rational)
|
|
20
|
+
'...Rational Numbers...'
|
|
21
|
+
|
|
22
|
+
Python modules::
|
|
23
|
+
|
|
24
|
+
sage: sage_getfile(sage.misc.sageinspect)
|
|
25
|
+
'.../sageinspect.py'
|
|
26
|
+
sage: print(sage_getdoc(sage.misc.sageinspect).lstrip()[:40])
|
|
27
|
+
Inspect Python, Sage, and Cython objects
|
|
28
|
+
sage: sage_getsource(sage.misc.sageinspect).lstrip()[51:-1]
|
|
29
|
+
'Inspect Python, Sage, and Cython objects...'
|
|
30
|
+
|
|
31
|
+
Test introspection of classes defined in Python and Cython files:
|
|
32
|
+
|
|
33
|
+
Cython classes::
|
|
34
|
+
|
|
35
|
+
sage: sage_getfile(sage.rings.rational.Rational)
|
|
36
|
+
'.../rational.pyx'
|
|
37
|
+
sage: sage_getdoc(sage.rings.rational.Rational).lstrip()
|
|
38
|
+
'A rational number...'
|
|
39
|
+
sage: sage_getsource(sage.rings.rational.Rational)
|
|
40
|
+
'cdef class Rational...'
|
|
41
|
+
|
|
42
|
+
Python classes::
|
|
43
|
+
|
|
44
|
+
sage: sage_getfile(BlockFinder)
|
|
45
|
+
'.../sage/misc/sageinspect.py'
|
|
46
|
+
sage: sage_getdoc(BlockFinder).lstrip()[:50] # needs sphinx
|
|
47
|
+
'Provide a "tokeneater()" method to detect the end '
|
|
48
|
+
sage: sage_getsource(BlockFinder)
|
|
49
|
+
'class BlockFinder:...'
|
|
50
|
+
|
|
51
|
+
Test introspection of functions defined in Python and Cython files:
|
|
52
|
+
|
|
53
|
+
Cython functions::
|
|
54
|
+
|
|
55
|
+
sage: sage_getdef(sage.rings.rational.make_rational, obj_name='mr')
|
|
56
|
+
'mr(s)'
|
|
57
|
+
sage: sage_getfile(sage.rings.rational.make_rational)
|
|
58
|
+
'.../rational.pyx'
|
|
59
|
+
sage: sage_getdoc(sage.rings.rational.make_rational).lstrip()
|
|
60
|
+
'Make a rational number ...'
|
|
61
|
+
sage: sage_getsource(sage.rings.rational.make_rational)
|
|
62
|
+
'@cython.binding(True)\ndef make_rational(s):...'
|
|
63
|
+
|
|
64
|
+
Python functions::
|
|
65
|
+
|
|
66
|
+
sage: sage_getdef(sage.misc.sageinspect.sage_getfile, obj_name='sage_getfile')
|
|
67
|
+
'sage_getfile(obj)'
|
|
68
|
+
sage: sage_getfile(sage.misc.sageinspect.sage_getfile)
|
|
69
|
+
'.../sageinspect.py'
|
|
70
|
+
sage: sage_getdoc(sage.misc.sageinspect.sage_getfile).lstrip()
|
|
71
|
+
'Get the full file name associated to "obj" as a string...'
|
|
72
|
+
sage: sage_getsource(sage.misc.sageinspect.sage_getfile)[4:]
|
|
73
|
+
'sage_getfile(obj):...'
|
|
74
|
+
|
|
75
|
+
Unfortunately, no argspec is extractable from builtins. Hence, we use a
|
|
76
|
+
generic argspec::
|
|
77
|
+
|
|
78
|
+
sage: sage_getdef(''.find, 'find')
|
|
79
|
+
'find(*args, **kwds)'
|
|
80
|
+
sage: sage_getdef(str.find, 'find')
|
|
81
|
+
'find(*args, **kwds)'
|
|
82
|
+
|
|
83
|
+
By :issue:`9976` and :issue:`14017`, introspection also works for interactively
|
|
84
|
+
defined Cython code, and with rather tricky argument lines::
|
|
85
|
+
|
|
86
|
+
sage: # needs sage.misc.cython
|
|
87
|
+
sage: cython('def foo(unsigned int x=1, a=\')"\', b={not (2+1==3):\'bar\'}, *args, **kwds): return')
|
|
88
|
+
sage: print(sage_getsource(foo))
|
|
89
|
+
def foo(unsigned int x=1, a=')"', b={not (2+1==3):'bar'}, *args, **kwds): return
|
|
90
|
+
sage: sage_getargspec(foo)
|
|
91
|
+
FullArgSpec(args=['x', 'a', 'b'], varargs='args', varkw='kwds', defaults=(1, ')"', {False: 'bar'}), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
92
|
+
|
|
93
|
+
AUTHORS:
|
|
94
|
+
|
|
95
|
+
- Originally taken from Fernando Perez's IPython
|
|
96
|
+
- William Stein: extensive modifications
|
|
97
|
+
- William Stein: in :func:`_sage_getargspec_cython`, a modified version of
|
|
98
|
+
``inspect.getargspec`` from the Python Standard Library, which was taken from
|
|
99
|
+
IPython for use in Sage
|
|
100
|
+
- Nick Alexander: extensions, testing
|
|
101
|
+
- Simon King: some extension for Cython, generalisation of SageArgSpecVisitor
|
|
102
|
+
- Simon King: in :func:`sage_getsourcelines`, if a class has no docstring then let the
|
|
103
|
+
class definition be found starting from the ``__init__`` method.
|
|
104
|
+
- Simon King: in :func:`sage_getsourcelines`, get source lines for dynamic classes
|
|
105
|
+
- Simon King: in :func:`_sage_getargspec_cython`, return an ``ArgSpec``, fix some bugs
|
|
106
|
+
- Simon King (2011-09): added :func:`_sage_getsourcelines_name_with_dot`
|
|
107
|
+
- Simon King (2013-02): in :func:`_sage_getargspec_cython`, recognise varargs and
|
|
108
|
+
default values in cython code, and return an ``ArgSpec``
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
import ast
|
|
112
|
+
import inspect
|
|
113
|
+
import functools
|
|
114
|
+
import os
|
|
115
|
+
import sys
|
|
116
|
+
import tokenize
|
|
117
|
+
import re
|
|
118
|
+
from inspect import Signature, Parameter
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
import importlib.machinery as import_machinery
|
|
122
|
+
except ImportError:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def is_function_or_cython_function(obj):
|
|
127
|
+
"""
|
|
128
|
+
Check whether something is a function.
|
|
129
|
+
|
|
130
|
+
This is a variant of :func:`inspect.isfunction`:
|
|
131
|
+
We assume that anything which has a genuine ``__code__``
|
|
132
|
+
attribute (not using ``__getattr__`` overrides) is a function.
|
|
133
|
+
This is meant to support Cython functions.
|
|
134
|
+
|
|
135
|
+
Think twice before using this function (or any function from the
|
|
136
|
+
:mod:`inspect` or :mod:`sage.misc.sageinspect` modules). Most uses of
|
|
137
|
+
:func:`inspect.isfunction` in ordinary library code can be replaced by
|
|
138
|
+
:func:`callable`.
|
|
139
|
+
|
|
140
|
+
EXAMPLES::
|
|
141
|
+
|
|
142
|
+
sage: from sage.misc.sageinspect import is_function_or_cython_function
|
|
143
|
+
sage: def f(): pass
|
|
144
|
+
sage: is_function_or_cython_function(f)
|
|
145
|
+
True
|
|
146
|
+
sage: is_function_or_cython_function(lambda x:x)
|
|
147
|
+
True
|
|
148
|
+
sage: from sage.categories.coercion_methods import _mul_parent
|
|
149
|
+
sage: is_function_or_cython_function(_mul_parent)
|
|
150
|
+
True
|
|
151
|
+
sage: is_function_or_cython_function(Integer.digits) # unbound method
|
|
152
|
+
False
|
|
153
|
+
sage: is_function_or_cython_function(Integer(1).digits) # bound method
|
|
154
|
+
False
|
|
155
|
+
|
|
156
|
+
TESTS:
|
|
157
|
+
|
|
158
|
+
Verify that ipywidgets can correctly determine signatures of Cython
|
|
159
|
+
functions::
|
|
160
|
+
|
|
161
|
+
sage: from ipywidgets.widgets.interaction import signature
|
|
162
|
+
sage: from sage.dynamics.complex_dynamics.mandel_julia_helper import fast_mandelbrot_plot # needs sage.plot sage.symbolic
|
|
163
|
+
sage: signature(fast_mandelbrot_plot) # random # needs sage.plot sage.symbolic
|
|
164
|
+
<IPython.utils._signatures.Signature object at 0x7f3ec8274e10>
|
|
165
|
+
"""
|
|
166
|
+
# We use type(obj) instead of just obj to avoid __getattr__().
|
|
167
|
+
# Some types, like methods, will return the __code__ of the
|
|
168
|
+
# underlying function in __getattr__() but we don't want to
|
|
169
|
+
# detect those as functions.
|
|
170
|
+
return hasattr(type(obj), "__code__")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def isclassinstance(obj):
|
|
174
|
+
r"""
|
|
175
|
+
Check if argument is instance of non built-in class.
|
|
176
|
+
|
|
177
|
+
INPUT:
|
|
178
|
+
|
|
179
|
+
- ``obj`` -- object
|
|
180
|
+
|
|
181
|
+
EXAMPLES::
|
|
182
|
+
|
|
183
|
+
sage: from sage.misc.sageinspect import isclassinstance
|
|
184
|
+
sage: isclassinstance(int)
|
|
185
|
+
False
|
|
186
|
+
sage: class myclass: pass
|
|
187
|
+
sage: isclassinstance(myclass())
|
|
188
|
+
True
|
|
189
|
+
sage: isclassinstance(myclass)
|
|
190
|
+
False
|
|
191
|
+
sage: class mymetaclass(type): pass
|
|
192
|
+
sage: class myclass2(metaclass=mymetaclass): pass
|
|
193
|
+
sage: isclassinstance(myclass2)
|
|
194
|
+
False
|
|
195
|
+
"""
|
|
196
|
+
builtin_mods = {'__builtin__', 'builtins', 'exceptions'}
|
|
197
|
+
|
|
198
|
+
return (not inspect.isclass(obj) and
|
|
199
|
+
hasattr(obj, '__class__') and
|
|
200
|
+
hasattr(obj.__class__, '__module__') and
|
|
201
|
+
obj.__class__.__module__ not in builtin_mods and
|
|
202
|
+
# Starting with Cython 3, Cython's builtin types have __module__ set
|
|
203
|
+
# to the shared module names like _cython_3_0_0.
|
|
204
|
+
not (isinstance(obj.__class__.__module__, str) and
|
|
205
|
+
obj.__class__.__module__.startswith('_cython_')) and
|
|
206
|
+
# In Cython 3.1, they have 'member_descriptor' type
|
|
207
|
+
'cython_function_or_method' not in str(obj.__class__.__module__))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Parse strings of form "File: sage/rings/rational.pyx (starting at line 1080)"
|
|
211
|
+
# "\ " protects a space in re.VERBOSE mode.
|
|
212
|
+
__embedded_position_re = re.compile(r'''
|
|
213
|
+
^ # anchor to the beginning of the line
|
|
214
|
+
File:\ (?P<FILENAME>.*?) # match File: then filename
|
|
215
|
+
\ \(starting\ at\ line\ (?P<LINENO>\d+)\) # match line number
|
|
216
|
+
\n? # if there is a newline, eat it
|
|
217
|
+
(?P<ORIGINAL>.*) # the original docstring is the end
|
|
218
|
+
\Z # anchor to the end of the string
|
|
219
|
+
''', re.MULTILINE | re.DOTALL | re.VERBOSE)
|
|
220
|
+
|
|
221
|
+
# Parse Python identifiers
|
|
222
|
+
__identifier_re = re.compile(r"^[^\d\W]\w*")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _extract_embedded_position(docstring):
|
|
226
|
+
r"""
|
|
227
|
+
If docstring has a Cython embedded position, return a tuple
|
|
228
|
+
(original_docstring, filename, line). If not, return None.
|
|
229
|
+
|
|
230
|
+
INPUT:
|
|
231
|
+
|
|
232
|
+
- ``docstring`` -- string
|
|
233
|
+
|
|
234
|
+
EXAMPLES::
|
|
235
|
+
|
|
236
|
+
sage: from sage.misc.sageinspect import _extract_embedded_position
|
|
237
|
+
sage: import inspect
|
|
238
|
+
sage: _extract_embedded_position(inspect.getdoc(var))[1][-21:] # needs sage.symbolic
|
|
239
|
+
'sage/calculus/var.pyx'
|
|
240
|
+
|
|
241
|
+
TESTS:
|
|
242
|
+
|
|
243
|
+
The following has been fixed in :issue:`13916`::
|
|
244
|
+
|
|
245
|
+
sage: cython('''cpdef test_funct(x, y): return''') # needs sage.misc.cython
|
|
246
|
+
sage: func_doc = inspect.getdoc(test_funct) # needs sage.misc.cython
|
|
247
|
+
sage: with open(_extract_embedded_position(func_doc)[1]) as f: # needs sage.misc.cython
|
|
248
|
+
....: print(f.read())
|
|
249
|
+
cpdef test_funct(x, y): return
|
|
250
|
+
|
|
251
|
+
Ensure that the embedded filename of the compiled function is
|
|
252
|
+
correct. In particular it should be relative to ``spyx_tmp()`` in
|
|
253
|
+
order for certain documentation functions to work properly. See
|
|
254
|
+
:issue:`24097`::
|
|
255
|
+
|
|
256
|
+
sage: from sage.env import DOT_SAGE
|
|
257
|
+
sage: from sage.misc.sage_ostools import restore_cwd
|
|
258
|
+
sage: with restore_cwd(DOT_SAGE): # needs sage.misc.cython
|
|
259
|
+
....: cython('''cpdef test_funct(x, y): return''')
|
|
260
|
+
sage: func_doc = inspect.getdoc(test_funct) # needs sage.misc.cython
|
|
261
|
+
sage: with open(_extract_embedded_position(func_doc)[1]) as f: # needs sage.misc.cython
|
|
262
|
+
....: print(f.read())
|
|
263
|
+
cpdef test_funct(x, y): return
|
|
264
|
+
"""
|
|
265
|
+
try:
|
|
266
|
+
res = __embedded_position_re.search(docstring)
|
|
267
|
+
except TypeError:
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
if res is None:
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
raw_filename = res.group('FILENAME')
|
|
274
|
+
filename = raw_filename
|
|
275
|
+
|
|
276
|
+
if not os.path.isabs(filename):
|
|
277
|
+
# Try some common path prefixes for Cython modules built by/for Sage
|
|
278
|
+
# 1) Module in the sage src tree
|
|
279
|
+
# 2) Module compiled by Sage's inline cython() compiler
|
|
280
|
+
from sage.misc.temporary_file import spyx_tmp
|
|
281
|
+
if raw_filename.startswith('sage/'):
|
|
282
|
+
import sage
|
|
283
|
+
from sage.env import SAGE_SRC
|
|
284
|
+
try_filenames = [os.path.join(directory, raw_filename.removeprefix('sage/'))
|
|
285
|
+
for directory in sage.__path__]
|
|
286
|
+
try_filenames.append(os.path.join(SAGE_SRC, raw_filename)) # meson editable install
|
|
287
|
+
else:
|
|
288
|
+
try_filenames = []
|
|
289
|
+
try_filenames.append(
|
|
290
|
+
os.path.join(spyx_tmp(), '_'.join(raw_filename.split('_')[:-1]),
|
|
291
|
+
raw_filename))
|
|
292
|
+
for try_filename in try_filenames:
|
|
293
|
+
if os.path.exists(try_filename):
|
|
294
|
+
filename = try_filename
|
|
295
|
+
break
|
|
296
|
+
# Otherwise we keep the relative path and just hope it's relative to
|
|
297
|
+
# the cwd; otherwise there's no way to be sure.
|
|
298
|
+
|
|
299
|
+
lineno = int(res.group('LINENO'))
|
|
300
|
+
original = res.group('ORIGINAL')
|
|
301
|
+
return (original, filename, lineno)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _extract_embedded_signature(docstring, name):
|
|
305
|
+
r"""
|
|
306
|
+
If docstring starts with the embedded of a method called ``name``, return
|
|
307
|
+
a tuple (original_docstring, argspec). If not, return (docstring, None).
|
|
308
|
+
|
|
309
|
+
See :issue:`17814`.
|
|
310
|
+
|
|
311
|
+
INPUT:
|
|
312
|
+
|
|
313
|
+
- ``docstring`` -- string
|
|
314
|
+
|
|
315
|
+
EXAMPLES::
|
|
316
|
+
|
|
317
|
+
sage: from sage.misc.sageinspect import _extract_embedded_signature
|
|
318
|
+
sage: from sage.misc.nested_class import MainClass
|
|
319
|
+
sage: print(_extract_embedded_signature(MainClass.NestedClass.NestedSubClass.dummy.__doc__, 'dummy')[0])
|
|
320
|
+
File: ...sage/misc/nested_class.pyx (starting at line ...)
|
|
321
|
+
...
|
|
322
|
+
sage: _extract_embedded_signature(MainClass.NestedClass.NestedSubClass.dummy.__doc__, 'dummy')[1]
|
|
323
|
+
FullArgSpec(args=['self', 'x', 'r'], varargs='args', varkw='kwds', defaults=((1, 2, 3.4),), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
324
|
+
sage: _extract_embedded_signature(range.__call__.__doc__, '__call__')
|
|
325
|
+
('Call self as a function.', None)
|
|
326
|
+
|
|
327
|
+
"""
|
|
328
|
+
# If there is an embedded signature, it is in the first line
|
|
329
|
+
L = docstring.split(os.linesep, 1)
|
|
330
|
+
firstline = L[0]
|
|
331
|
+
# It is possible that the signature is of the form ClassName.method_name,
|
|
332
|
+
# and thus we need to do the following:
|
|
333
|
+
if name not in firstline:
|
|
334
|
+
return docstring, None
|
|
335
|
+
signature = firstline.split(name, 1)[-1]
|
|
336
|
+
if signature.startswith("(") and signature.endswith(")"):
|
|
337
|
+
docstring = L[1] if len(L) > 1 else '' # Remove first line, keep the rest
|
|
338
|
+
def_string = "def " + name + signature + ": pass"
|
|
339
|
+
try:
|
|
340
|
+
return docstring, inspect.FullArgSpec(*_sage_getargspec_cython(def_string))
|
|
341
|
+
except SyntaxError:
|
|
342
|
+
docstring = os.linesep.join(L)
|
|
343
|
+
return docstring, None
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class BlockFinder:
|
|
347
|
+
"""
|
|
348
|
+
Provide a :meth:`tokeneater` method to detect the end of a code block.
|
|
349
|
+
|
|
350
|
+
This is the Python library's :class:`inspect.BlockFinder` modified
|
|
351
|
+
to recognize Cython definitions.
|
|
352
|
+
"""
|
|
353
|
+
def __init__(self):
|
|
354
|
+
self.indent = 0
|
|
355
|
+
self.islambda = False
|
|
356
|
+
self.started = False
|
|
357
|
+
self.passline = False
|
|
358
|
+
self.last = 1
|
|
359
|
+
|
|
360
|
+
def tokeneater(self, type, token, srow_scol, erow_ecol, line):
|
|
361
|
+
srow, scol = srow_scol
|
|
362
|
+
erow, ecol = erow_ecol
|
|
363
|
+
if not self.started:
|
|
364
|
+
# look for the first "(cp)def", "class" or "lambda"
|
|
365
|
+
if token in ("def", "cpdef", "class", "lambda"):
|
|
366
|
+
if token == "lambda":
|
|
367
|
+
self.islambda = True
|
|
368
|
+
self.started = True
|
|
369
|
+
self.passline = True # skip to the end of the line
|
|
370
|
+
elif type == tokenize.NEWLINE:
|
|
371
|
+
self.passline = False # stop skipping when a NEWLINE is seen
|
|
372
|
+
self.last = srow
|
|
373
|
+
if self.islambda: # lambdas always end at the first NEWLINE
|
|
374
|
+
raise inspect.EndOfBlock
|
|
375
|
+
elif self.passline:
|
|
376
|
+
pass
|
|
377
|
+
elif type == tokenize.INDENT:
|
|
378
|
+
self.indent = self.indent + 1
|
|
379
|
+
self.passline = True
|
|
380
|
+
elif type == tokenize.DEDENT:
|
|
381
|
+
self.indent = self.indent - 1
|
|
382
|
+
# the end of matching indent/dedent pairs end a block
|
|
383
|
+
# (note that this only works for "def"/"class" blocks,
|
|
384
|
+
# not e.g. for "if: else:" or "try: finally:" blocks)
|
|
385
|
+
if self.indent <= 0:
|
|
386
|
+
raise inspect.EndOfBlock
|
|
387
|
+
elif self.indent == 0 and type not in (tokenize.COMMENT, tokenize.NL):
|
|
388
|
+
# any other token on the same indentation level end the previous
|
|
389
|
+
# block as well, except the pseudo-tokens COMMENT and NL.
|
|
390
|
+
raise inspect.EndOfBlock
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _getblock(lines):
|
|
394
|
+
"""
|
|
395
|
+
Extract the block of code at the top of the given list of lines.
|
|
396
|
+
|
|
397
|
+
This is the Python library's :func:`inspect.getblock`, except that
|
|
398
|
+
it uses an instance of our custom :class:`BlockFinder`.
|
|
399
|
+
"""
|
|
400
|
+
blockfinder = BlockFinder()
|
|
401
|
+
iter_lines = iter(lines)
|
|
402
|
+
tokenizer = tokenize.tokenize
|
|
403
|
+
|
|
404
|
+
def readline():
|
|
405
|
+
return next(iter_lines).encode('utf-8')
|
|
406
|
+
try:
|
|
407
|
+
for tok in tokenizer(readline):
|
|
408
|
+
blockfinder.tokeneater(*tok)
|
|
409
|
+
except (inspect.EndOfBlock, IndentationError):
|
|
410
|
+
pass
|
|
411
|
+
return lines[:blockfinder.last]
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _extract_source(lines, lineno):
|
|
415
|
+
r"""
|
|
416
|
+
Given a list of lines or a multiline string and a starting lineno,
|
|
417
|
+
_extract_source returns [source_lines]. [source_lines] is the smallest
|
|
418
|
+
indentation block starting at lineno.
|
|
419
|
+
|
|
420
|
+
INPUT:
|
|
421
|
+
|
|
422
|
+
- ``lines`` -- string or list of strings
|
|
423
|
+
- ``lineno`` -- positive integer
|
|
424
|
+
|
|
425
|
+
EXAMPLES::
|
|
426
|
+
|
|
427
|
+
sage: from sage.misc.sageinspect import _extract_source
|
|
428
|
+
sage: s2 = "#hello\n\n class f():\n pass\n\n#goodbye"
|
|
429
|
+
sage: _extract_source(s2, 3)
|
|
430
|
+
[' class f():\n', ' pass\n']
|
|
431
|
+
"""
|
|
432
|
+
if lineno < 1:
|
|
433
|
+
raise ValueError("Line numbering starts at 1! (tried to extract line {})".format(lineno))
|
|
434
|
+
lineno -= 1
|
|
435
|
+
|
|
436
|
+
if isinstance(lines, str):
|
|
437
|
+
lines = lines.splitlines(True) # true keeps the '\n'
|
|
438
|
+
if len(lines):
|
|
439
|
+
# Fixes an issue with getblock
|
|
440
|
+
lines[-1] += '\n'
|
|
441
|
+
|
|
442
|
+
return _getblock(lines[lineno:])
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
class SageArgSpecVisitor(ast.NodeVisitor):
|
|
446
|
+
"""
|
|
447
|
+
A simple visitor class that walks an abstract-syntax tree (AST)
|
|
448
|
+
for a Python function's argspec. It returns the contents of nodes
|
|
449
|
+
representing the basic Python types: None, booleans, numbers,
|
|
450
|
+
strings, lists, tuples, and dictionaries. We use this class in
|
|
451
|
+
:func:`_sage_getargspec_from_ast` to extract an argspec from a
|
|
452
|
+
function's or method's source code.
|
|
453
|
+
|
|
454
|
+
EXAMPLES::
|
|
455
|
+
|
|
456
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
457
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
458
|
+
sage: visitor.visit(ast.parse('[1,2,3]').body[0].value)
|
|
459
|
+
[1, 2, 3]
|
|
460
|
+
sage: v = visitor.visit(ast.parse("{'a':('e',2,[None,({False:True},'pi')]), 37.0:'temp'}").body[0].value)
|
|
461
|
+
sage: sorted(v.items(), key=lambda x: str(x[0]))
|
|
462
|
+
[(37.0, 'temp'), ('a', ('e', 2, [None, ({False: True}, 'pi')]))]
|
|
463
|
+
sage: v = ast.parse("jc = ['veni', 'vidi', 'vici']").body[0]
|
|
464
|
+
sage: isinstance(v, ast.Assign)
|
|
465
|
+
True
|
|
466
|
+
sage: attrs = [x for x in dir(v) if not x.startswith('__')]
|
|
467
|
+
sage: '_attributes' in attrs and '_fields' in attrs and 'col_offset' in attrs
|
|
468
|
+
True
|
|
469
|
+
sage: visitor.visit(v.targets[0])
|
|
470
|
+
'jc'
|
|
471
|
+
sage: visitor.visit(v.value)
|
|
472
|
+
['veni', 'vidi', 'vici']
|
|
473
|
+
"""
|
|
474
|
+
def visit_Name(self, node):
|
|
475
|
+
"""
|
|
476
|
+
Visit a Python AST :class:`ast.Name` node.
|
|
477
|
+
|
|
478
|
+
INPUT:
|
|
479
|
+
|
|
480
|
+
- ``node`` -- the node instance to visit
|
|
481
|
+
|
|
482
|
+
OUTPUT: ``None``, ``True``, ``False``, or the ``node``'s name as a string
|
|
483
|
+
|
|
484
|
+
EXAMPLES::
|
|
485
|
+
|
|
486
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
487
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
488
|
+
sage: vis = lambda x: visitor.visit_Name(ast.parse(x).body[0].value)
|
|
489
|
+
sage: [vis(n) for n in ['foo', 'bar']]
|
|
490
|
+
['foo', 'bar']
|
|
491
|
+
sage: [type(vis(n)) for n in ['foo', 'bar']]
|
|
492
|
+
[<class 'str'>, <class 'str'>]
|
|
493
|
+
"""
|
|
494
|
+
return node.id
|
|
495
|
+
|
|
496
|
+
def visit_arg(self, node):
|
|
497
|
+
r"""
|
|
498
|
+
Visit a Python AST :class:`ast.arg` node.
|
|
499
|
+
|
|
500
|
+
This node type is only on Python 3, where function arguments are
|
|
501
|
+
more complex than just an identifier (e.g. they may also include
|
|
502
|
+
annotations).
|
|
503
|
+
|
|
504
|
+
For now we simply return the argument identifier as a string.
|
|
505
|
+
|
|
506
|
+
INPUT:
|
|
507
|
+
|
|
508
|
+
- ``node`` -- the node instance to visit
|
|
509
|
+
|
|
510
|
+
OUTPUT: the argument name
|
|
511
|
+
|
|
512
|
+
EXAMPLES::
|
|
513
|
+
|
|
514
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
515
|
+
sage: s = "def f(a, b=2, c={'a': [4, 5.5, False]}, d=(None, True)):\n return"
|
|
516
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
517
|
+
sage: args = ast.parse(s).body[0].args.args
|
|
518
|
+
sage: [visitor.visit_arg(n) for n in args]
|
|
519
|
+
['a', 'b', 'c', 'd']
|
|
520
|
+
"""
|
|
521
|
+
return node.arg
|
|
522
|
+
|
|
523
|
+
def visit_List(self, node):
|
|
524
|
+
"""
|
|
525
|
+
Visit a Python AST :class:`ast.List` node.
|
|
526
|
+
|
|
527
|
+
INPUT:
|
|
528
|
+
|
|
529
|
+
- ``node`` -- the node instance to visit
|
|
530
|
+
|
|
531
|
+
OUTPUT: the list the ``node`` represents
|
|
532
|
+
|
|
533
|
+
EXAMPLES::
|
|
534
|
+
|
|
535
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
536
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
537
|
+
sage: vis = lambda x: visitor.visit_List(ast.parse(x).body[0].value)
|
|
538
|
+
sage: [vis(l) for l in ['[]', "['s', 't', 'u']", '[[e], [], [pi]]']]
|
|
539
|
+
[[], ['s', 't', 'u'], [['e'], [], ['pi']]]
|
|
540
|
+
"""
|
|
541
|
+
return [self.visit(n) for n in node.elts]
|
|
542
|
+
|
|
543
|
+
def visit_Tuple(self, node):
|
|
544
|
+
"""
|
|
545
|
+
Visit a Python AST :class:`ast.Tuple` node.
|
|
546
|
+
|
|
547
|
+
INPUT:
|
|
548
|
+
|
|
549
|
+
- ``node`` -- the node instance to visit
|
|
550
|
+
|
|
551
|
+
OUTPUT: the tuple the ``node`` represents
|
|
552
|
+
|
|
553
|
+
EXAMPLES::
|
|
554
|
+
|
|
555
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
556
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
557
|
+
sage: vis = lambda x: visitor.visit_Tuple(ast.parse(x).body[0].value)
|
|
558
|
+
sage: [vis(t) for t in ['()', '(x,y)', '("Au", "Al", "Cu")']]
|
|
559
|
+
[(), ('x', 'y'), ('Au', 'Al', 'Cu')]
|
|
560
|
+
"""
|
|
561
|
+
return tuple(self.visit(n) for n in node.elts)
|
|
562
|
+
|
|
563
|
+
def visit_Dict(self, node):
|
|
564
|
+
"""
|
|
565
|
+
Visit a Python AST :class:`ast.Dict` node.
|
|
566
|
+
|
|
567
|
+
INPUT:
|
|
568
|
+
|
|
569
|
+
- ``node`` -- the node instance to visit
|
|
570
|
+
|
|
571
|
+
OUTPUT: the dictionary the ``node`` represents
|
|
572
|
+
|
|
573
|
+
EXAMPLES::
|
|
574
|
+
|
|
575
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
576
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
577
|
+
sage: vis = lambda x: visitor.visit_Dict(ast.parse(x).body[0].value)
|
|
578
|
+
sage: v = [vis(d) for d in ['{}', "{1:one, 'two':2, other:bother}"]]
|
|
579
|
+
sage: [sorted(d.items(), key=lambda x: str(x[0])) for d in v]
|
|
580
|
+
[[], [(1, 'one'), ('other', 'bother'), ('two', 2)]]
|
|
581
|
+
"""
|
|
582
|
+
d = {}
|
|
583
|
+
for k, v in zip(node.keys, node.values):
|
|
584
|
+
d[self.visit(k)] = self.visit(v)
|
|
585
|
+
return d
|
|
586
|
+
|
|
587
|
+
def visit_BoolOp(self, node):
|
|
588
|
+
"""
|
|
589
|
+
Visit a Python AST :class:`ast.BoolOp` node.
|
|
590
|
+
|
|
591
|
+
INPUT:
|
|
592
|
+
|
|
593
|
+
- ``node`` -- the node instance to visit
|
|
594
|
+
|
|
595
|
+
OUTPUT: the result that ``node`` represents
|
|
596
|
+
|
|
597
|
+
EXAMPLES::
|
|
598
|
+
|
|
599
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
600
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
601
|
+
sage: vis = lambda x: visitor.visit(ast.parse(x).body[0].value)
|
|
602
|
+
sage: [vis(d) for d in ['True and 1', 'False or 3 or None', '3 and 4']] #indirect doctest
|
|
603
|
+
[1, 3, 4]
|
|
604
|
+
"""
|
|
605
|
+
op = node.op.__class__.__name__
|
|
606
|
+
L = list(node.values)
|
|
607
|
+
out = self.visit(L.pop(0))
|
|
608
|
+
if op == 'And':
|
|
609
|
+
while L:
|
|
610
|
+
next = self.visit(L.pop(0))
|
|
611
|
+
out = out and next
|
|
612
|
+
return out
|
|
613
|
+
if op == 'Or':
|
|
614
|
+
while L:
|
|
615
|
+
next = self.visit(L.pop(0))
|
|
616
|
+
out = out or next
|
|
617
|
+
return out
|
|
618
|
+
|
|
619
|
+
def visit_Compare(self, node):
|
|
620
|
+
"""
|
|
621
|
+
Visit a Python AST :class:`ast.Compare` node.
|
|
622
|
+
|
|
623
|
+
INPUT:
|
|
624
|
+
|
|
625
|
+
- ``node`` -- the node instance to visit
|
|
626
|
+
|
|
627
|
+
OUTPUT: the result that ``node`` represents
|
|
628
|
+
|
|
629
|
+
EXAMPLES::
|
|
630
|
+
|
|
631
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
632
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
633
|
+
sage: vis = lambda x: visitor.visit_Compare(ast.parse(x).body[0].value)
|
|
634
|
+
sage: [vis(d) for d in ['1<2==2!=3', '1==1>2', '1<2>1', '1<3<2<4']]
|
|
635
|
+
[True, False, True, False]
|
|
636
|
+
"""
|
|
637
|
+
left = self.visit(node.left)
|
|
638
|
+
ops = list(node.ops)
|
|
639
|
+
comparators = list(node.comparators) # the things to be compared with.
|
|
640
|
+
while ops:
|
|
641
|
+
op = ops.pop(0).__class__.__name__
|
|
642
|
+
right = self.visit(comparators.pop(0))
|
|
643
|
+
if op == 'Lt':
|
|
644
|
+
if not left < right:
|
|
645
|
+
return False
|
|
646
|
+
elif op == 'LtE':
|
|
647
|
+
if not left <= right:
|
|
648
|
+
return False
|
|
649
|
+
elif op == 'Gt':
|
|
650
|
+
if not left > right:
|
|
651
|
+
return False
|
|
652
|
+
elif op == 'GtE':
|
|
653
|
+
if not left >= right:
|
|
654
|
+
return False
|
|
655
|
+
elif op == 'Eq':
|
|
656
|
+
if not left == right:
|
|
657
|
+
return False
|
|
658
|
+
elif op == 'NotEq':
|
|
659
|
+
if not left != right:
|
|
660
|
+
return False
|
|
661
|
+
left = right
|
|
662
|
+
return True
|
|
663
|
+
|
|
664
|
+
def visit_BinOp(self, node):
|
|
665
|
+
"""
|
|
666
|
+
Visit a Python AST :class:`ast.BinOp` node.
|
|
667
|
+
|
|
668
|
+
INPUT:
|
|
669
|
+
|
|
670
|
+
- ``node`` -- the node instance to visit
|
|
671
|
+
|
|
672
|
+
OUTPUT: the result that ``node`` represents
|
|
673
|
+
|
|
674
|
+
EXAMPLES::
|
|
675
|
+
|
|
676
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
677
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
678
|
+
sage: vis = lambda x: visitor.visit(ast.parse(x).body[0].value)
|
|
679
|
+
sage: [vis(d) for d in ['(3+(2*4))', '7|8', '5^3', '7/3', '7//3', '3<<4']] #indirect doctest
|
|
680
|
+
[11, 15, 6, 2.3333333333333335, 2, 48]
|
|
681
|
+
"""
|
|
682
|
+
op = node.op.__class__.__name__
|
|
683
|
+
if op == 'Add':
|
|
684
|
+
return self.visit(node.left) + self.visit(node.right)
|
|
685
|
+
if op == 'Mult':
|
|
686
|
+
return self.visit(node.left) * self.visit(node.right)
|
|
687
|
+
if op == 'BitAnd':
|
|
688
|
+
return self.visit(node.left) & self.visit(node.right)
|
|
689
|
+
if op == 'BitOr':
|
|
690
|
+
return self.visit(node.left) | self.visit(node.right)
|
|
691
|
+
if op == 'BitXor':
|
|
692
|
+
return self.visit(node.left) ^ self.visit(node.right)
|
|
693
|
+
if op == 'Div':
|
|
694
|
+
return self.visit(node.left) / self.visit(node.right)
|
|
695
|
+
if op == 'Eq':
|
|
696
|
+
return self.visit(node.left) == self.visit(node.right)
|
|
697
|
+
if op == 'FloorDiv':
|
|
698
|
+
return self.visit(node.left) // self.visit(node.right)
|
|
699
|
+
if op == 'NotEq':
|
|
700
|
+
return self.visit(node.left) != self.visit(node.right)
|
|
701
|
+
if op == 'NotIn':
|
|
702
|
+
return self.visit(node.left) not in self.visit(node.right)
|
|
703
|
+
if op == 'Pow':
|
|
704
|
+
return self.visit(node.left) ** self.visit(node.right)
|
|
705
|
+
if op == 'RShift':
|
|
706
|
+
return self.visit(node.left) >> self.visit(node.right)
|
|
707
|
+
if op == 'LShift':
|
|
708
|
+
return self.visit(node.left) << self.visit(node.right)
|
|
709
|
+
if op == 'Sub':
|
|
710
|
+
return self.visit(node.left) - self.visit(node.right)
|
|
711
|
+
if op == 'Gt':
|
|
712
|
+
return self.visit(node.left) > self.visit(node.right)
|
|
713
|
+
if op == 'GtE':
|
|
714
|
+
return self.visit(node.left) >= self.visit(node.right)
|
|
715
|
+
if op == 'In':
|
|
716
|
+
return self.visit(node.left) in self.visit(node.right)
|
|
717
|
+
if op == 'Is':
|
|
718
|
+
return self.visit(node.left) is self.visit(node.right)
|
|
719
|
+
if op == 'IsNot':
|
|
720
|
+
return self.visit(node.left) is not self.visit(node.right)
|
|
721
|
+
if op == 'Lt':
|
|
722
|
+
return self.visit(node.left) < self.visit(node.right)
|
|
723
|
+
if op == 'LtE':
|
|
724
|
+
return self.visit(node.left) <= self.visit(node.right)
|
|
725
|
+
if op == 'Mod':
|
|
726
|
+
return self.visit(node.left) % self.visit(node.right)
|
|
727
|
+
|
|
728
|
+
def visit_UnaryOp(self, node):
|
|
729
|
+
"""
|
|
730
|
+
Visit a Python AST :class:`ast.BinOp` node.
|
|
731
|
+
|
|
732
|
+
INPUT:
|
|
733
|
+
|
|
734
|
+
- ``node`` -- the node instance to visit
|
|
735
|
+
|
|
736
|
+
OUTPUT: the result that ``node`` represents
|
|
737
|
+
|
|
738
|
+
EXAMPLES::
|
|
739
|
+
|
|
740
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
741
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
742
|
+
sage: vis = lambda x: visitor.visit_UnaryOp(ast.parse(x).body[0].value)
|
|
743
|
+
sage: [vis(d) for d in ['+(3*2)', '-(3*2)']]
|
|
744
|
+
[6, -6]
|
|
745
|
+
"""
|
|
746
|
+
op = node.op.__class__.__name__
|
|
747
|
+
if op == 'Not':
|
|
748
|
+
return not self.visit(node.operand)
|
|
749
|
+
if op == 'UAdd':
|
|
750
|
+
return self.visit(node.operand)
|
|
751
|
+
if op == 'USub':
|
|
752
|
+
return -self.visit(node.operand)
|
|
753
|
+
|
|
754
|
+
def visit_Constant(self, node):
|
|
755
|
+
"""
|
|
756
|
+
Visit a Python AST :class:`ast.Constant` node.
|
|
757
|
+
|
|
758
|
+
INPUT:
|
|
759
|
+
|
|
760
|
+
- ``node`` -- the node instance to visit
|
|
761
|
+
|
|
762
|
+
OUTPUT: the constant value the ``node`` represents
|
|
763
|
+
|
|
764
|
+
EXAMPLES::
|
|
765
|
+
|
|
766
|
+
sage: import ast, sage.misc.sageinspect as sms
|
|
767
|
+
sage: visitor = sms.SageArgSpecVisitor()
|
|
768
|
+
sage: vis = lambda x: visitor.visit_Constant(ast.parse(x).body[0].value)
|
|
769
|
+
sage: [vis(n) for n in ['123', '0', '3.14', '"hello"', 'True', 'False', 'None']]
|
|
770
|
+
[123, 0, 3.14, 'hello', True, False, None]
|
|
771
|
+
sage: [type(vis(n)) for n in ['123', '0', '3.14', '"hello"', 'True', 'False', 'None']]
|
|
772
|
+
[<class 'int'>, <class 'int'>, <class 'float'>, <class 'str'>, <class 'bool'>, <class 'bool'>, <class 'NoneType'>]
|
|
773
|
+
"""
|
|
774
|
+
return node.value
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def _grep_first_pair_of_parentheses(s):
|
|
778
|
+
r"""
|
|
779
|
+
Return the first matching pair of parentheses in a code string.
|
|
780
|
+
|
|
781
|
+
INPUT:
|
|
782
|
+
|
|
783
|
+
- ``s`` -- string
|
|
784
|
+
|
|
785
|
+
OUTPUT:
|
|
786
|
+
|
|
787
|
+
A substring of the input, namely the part between the first
|
|
788
|
+
(outmost) matching pair of parentheses (including the
|
|
789
|
+
parentheses).
|
|
790
|
+
|
|
791
|
+
Parentheses between single or double quotation marks do not
|
|
792
|
+
count. If no matching pair of parentheses can be found, a
|
|
793
|
+
:exc:`SyntaxError` is raised.
|
|
794
|
+
|
|
795
|
+
EXAMPLES::
|
|
796
|
+
|
|
797
|
+
sage: from sage.misc.sageinspect import _grep_first_pair_of_parentheses
|
|
798
|
+
sage: code = 'def foo(a="\'):", b=4):\n return'
|
|
799
|
+
sage: _grep_first_pair_of_parentheses(code)
|
|
800
|
+
'(a="\'):", b=4)'
|
|
801
|
+
sage: code = 'def foo(a="%s):", \'b=4):\n return'%("'")
|
|
802
|
+
sage: _grep_first_pair_of_parentheses(code)
|
|
803
|
+
Traceback (most recent call last):
|
|
804
|
+
...
|
|
805
|
+
SyntaxError: The given string does not contain balanced parentheses
|
|
806
|
+
"""
|
|
807
|
+
out = []
|
|
808
|
+
single_quote = False
|
|
809
|
+
double_quote = False
|
|
810
|
+
escaped = False
|
|
811
|
+
level = 0
|
|
812
|
+
for c in s:
|
|
813
|
+
if level > 0:
|
|
814
|
+
out.append(c)
|
|
815
|
+
if c == '(' and not single_quote and not double_quote and not escaped:
|
|
816
|
+
level += 1
|
|
817
|
+
elif c == '"' and not single_quote and not escaped:
|
|
818
|
+
double_quote = not double_quote
|
|
819
|
+
elif c == "'" and not double_quote and not escaped:
|
|
820
|
+
single_quote = not single_quote
|
|
821
|
+
elif c == ')' and not single_quote and not double_quote and not escaped:
|
|
822
|
+
if level == 1:
|
|
823
|
+
return '(' + ''.join(out)
|
|
824
|
+
level -= 1
|
|
825
|
+
elif c == "\\" and (single_quote or double_quote):
|
|
826
|
+
escaped = not escaped
|
|
827
|
+
else:
|
|
828
|
+
escaped = False
|
|
829
|
+
raise SyntaxError("The given string does not contain balanced parentheses")
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def _split_syntactical_unit(s):
|
|
833
|
+
"""
|
|
834
|
+
Split off a sub-expression from the start of a given string.
|
|
835
|
+
|
|
836
|
+
INPUT:
|
|
837
|
+
|
|
838
|
+
- ``s`` -- string
|
|
839
|
+
|
|
840
|
+
OUTPUT:
|
|
841
|
+
|
|
842
|
+
A pair ``unit, s2``, such that ``unit`` is the string representation of a
|
|
843
|
+
string (single or double quoted) or of a sub-expression surrounded by
|
|
844
|
+
brackets (round, square or curly brackets), or of an identifier, or a
|
|
845
|
+
single character, if none of the above is available. The given string ``s``
|
|
846
|
+
is obtained by appending some whitespace followed by ``s2`` to ``unit``.
|
|
847
|
+
|
|
848
|
+
Blank space between the units is removed.
|
|
849
|
+
|
|
850
|
+
EXAMPLES::
|
|
851
|
+
|
|
852
|
+
sage: from sage.misc.sageinspect import _split_syntactical_unit
|
|
853
|
+
sage: s = "(Hel) lo_1=[)\"!\" ] '''? {world} '''?"
|
|
854
|
+
sage: while s:
|
|
855
|
+
....: u, s = _split_syntactical_unit(s)
|
|
856
|
+
....: print(u)
|
|
857
|
+
(Hel)
|
|
858
|
+
lo_1
|
|
859
|
+
=
|
|
860
|
+
[)"!"]
|
|
861
|
+
'''? {world} '''
|
|
862
|
+
?
|
|
863
|
+
|
|
864
|
+
If the string ends before the unit is completed (mispatching parentheses
|
|
865
|
+
or missing quotation mark), then a syntax error is raised::
|
|
866
|
+
|
|
867
|
+
sage: s = "'''({SAGE}]"
|
|
868
|
+
sage: _split_syntactical_unit(s)
|
|
869
|
+
Traceback (most recent call last):
|
|
870
|
+
...
|
|
871
|
+
SyntaxError: EOF while scanning string literal
|
|
872
|
+
sage: s = "({SAGE}]"
|
|
873
|
+
sage: _split_syntactical_unit(s)
|
|
874
|
+
Traceback (most recent call last):
|
|
875
|
+
...
|
|
876
|
+
SyntaxError: Syntactical group starting with '(' did not end with ')'
|
|
877
|
+
|
|
878
|
+
Numbers are not recognised::
|
|
879
|
+
|
|
880
|
+
sage: _split_syntactical_unit('123')
|
|
881
|
+
('1', '23')
|
|
882
|
+
|
|
883
|
+
TESTS:
|
|
884
|
+
|
|
885
|
+
The following was fixed in :issue:`16309`::
|
|
886
|
+
|
|
887
|
+
sage: _split_syntactical_unit('()): pass')
|
|
888
|
+
('()', '): pass')
|
|
889
|
+
"""
|
|
890
|
+
s = s.strip()
|
|
891
|
+
if not s:
|
|
892
|
+
return s
|
|
893
|
+
|
|
894
|
+
# Split a given string at the next unescaped quotation mark
|
|
895
|
+
def split_string(s, quot):
|
|
896
|
+
escaped = False
|
|
897
|
+
l = len(quot)
|
|
898
|
+
for i in range(len(s)):
|
|
899
|
+
if s[i] == '\\':
|
|
900
|
+
escaped = not escaped
|
|
901
|
+
continue
|
|
902
|
+
if not escaped and s[i:i + l] == quot:
|
|
903
|
+
return s[:i], s[i + l:]
|
|
904
|
+
escaped = False
|
|
905
|
+
raise SyntaxError("EOF while scanning string literal")
|
|
906
|
+
# 1. s is a triple-quoted string
|
|
907
|
+
if s.startswith('"""'):
|
|
908
|
+
a, b = split_string(s[3:], '"""')
|
|
909
|
+
return '"""' + a + '"""', b.strip()
|
|
910
|
+
if s.startswith('r"""'):
|
|
911
|
+
a, b = split_string(s[4:], '"""')
|
|
912
|
+
return 'r"""'+a+'"""', b.strip()
|
|
913
|
+
if s.startswith("'''"):
|
|
914
|
+
a, b = split_string(s[3:], "'''")
|
|
915
|
+
return "'''"+a+"'''", b.strip()
|
|
916
|
+
if s.startswith("r'''"):
|
|
917
|
+
a, b = split_string(s[4:], "'''")
|
|
918
|
+
return "r'''"+a+"'''", b.strip()
|
|
919
|
+
|
|
920
|
+
# 2. s is a single-quoted string
|
|
921
|
+
if s.startswith('"'):
|
|
922
|
+
a, b = split_string(s[1:], '"')
|
|
923
|
+
return '"'+a+'"', b.strip()
|
|
924
|
+
if s.startswith("'"):
|
|
925
|
+
a, b = split_string(s[1:], "'")
|
|
926
|
+
return "'"+a+"'", b.strip()
|
|
927
|
+
if s.startswith('r"'):
|
|
928
|
+
a, b = split_string(s[2:], '"')
|
|
929
|
+
return 'r"'+a+'"', b.strip()
|
|
930
|
+
if s.startswith("r'"):
|
|
931
|
+
a, b = split_string(s[2:], "'")
|
|
932
|
+
return "r'"+a+"'", b.strip()
|
|
933
|
+
|
|
934
|
+
# 3. s is not a string
|
|
935
|
+
start = s[0]
|
|
936
|
+
out = [start]
|
|
937
|
+
if start == '(':
|
|
938
|
+
stop = ')'
|
|
939
|
+
elif start == '[':
|
|
940
|
+
stop = ']'
|
|
941
|
+
elif start == '{':
|
|
942
|
+
stop = '}'
|
|
943
|
+
elif start == '\\':
|
|
944
|
+
# note that python would raise a syntax error
|
|
945
|
+
# if the line contains anything but whitespace
|
|
946
|
+
# after the backslash. But we assume here that
|
|
947
|
+
# the input is syntactically correct.
|
|
948
|
+
return _split_syntactical_unit(s[1:])
|
|
949
|
+
elif start == '#':
|
|
950
|
+
linebreak = s.index(os.linesep)
|
|
951
|
+
if linebreak == -1:
|
|
952
|
+
return '', ''
|
|
953
|
+
return '', s[linebreak:].strip()
|
|
954
|
+
else:
|
|
955
|
+
M = __identifier_re.search(s)
|
|
956
|
+
if M is None:
|
|
957
|
+
return s[0], s[1:].strip()
|
|
958
|
+
return M.group(), s[M.end():].strip()
|
|
959
|
+
|
|
960
|
+
s = s[1:]
|
|
961
|
+
while s:
|
|
962
|
+
tmp_group, s = _split_syntactical_unit(s)
|
|
963
|
+
out.append(tmp_group)
|
|
964
|
+
s = s.strip()
|
|
965
|
+
if tmp_group == stop:
|
|
966
|
+
return ''.join(out), s
|
|
967
|
+
elif s.startswith(stop):
|
|
968
|
+
out.append(stop)
|
|
969
|
+
return ''.join(out), s[1:].strip()
|
|
970
|
+
raise SyntaxError("Syntactical group starting with %s did not end with %s" % (repr(start), repr(stop)))
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def _sage_getargspec_from_ast(source):
|
|
974
|
+
r"""
|
|
975
|
+
Return an argspec for a Python function or method by compiling its
|
|
976
|
+
source to an abstract-syntax tree (AST) and walking its ``args``
|
|
977
|
+
subtrees with :class:`SageArgSpecVisitor`. We use this in
|
|
978
|
+
:func:`_sage_getargspec_cython`.
|
|
979
|
+
|
|
980
|
+
INPUT:
|
|
981
|
+
|
|
982
|
+
- ``source`` -- string; the function's (or method's) source code
|
|
983
|
+
definition. The function's body is ignored.
|
|
984
|
+
|
|
985
|
+
OUTPUT: an instance of :obj:`inspect.ArgSpec`, i.e., a named tuple
|
|
986
|
+
|
|
987
|
+
EXAMPLES::
|
|
988
|
+
|
|
989
|
+
sage: import inspect, sage.misc.sageinspect as sms
|
|
990
|
+
sage: from_ast = sms._sage_getargspec_from_ast
|
|
991
|
+
sage: s = "def f(a, b=2, c={'a': [4, 5.5, False]}, d=(None, True)):\n return"
|
|
992
|
+
sage: from_ast(s)
|
|
993
|
+
FullArgSpec(args=['a', 'b', 'c', 'd'], varargs=None, varkw=None, defaults=(2, {'a': [4, 5.5, False]}, (None, True)), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
994
|
+
sage: context = {}
|
|
995
|
+
sage: exec(compile(s, '<string>', 'single'), context)
|
|
996
|
+
sage: inspect.getfullargspec(context['f'])
|
|
997
|
+
FullArgSpec(args=['a', 'b', 'c', 'd'], varargs=None, varkw=None, defaults=(2, {'a': [4, 5.5, False]}, (None, True)), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
998
|
+
sage: from_ast(s) == inspect.getfullargspec(context['f'])
|
|
999
|
+
True
|
|
1000
|
+
sage: set(from_ast(sms.sage_getsource(x)) == inspect.getfullargspec(x) for x in [factor, identity_matrix, Graph.__init__]) # needs sage.graphs sage.modules
|
|
1001
|
+
{True}
|
|
1002
|
+
"""
|
|
1003
|
+
ast_args = ast.parse(source.lstrip()).body[0].args
|
|
1004
|
+
|
|
1005
|
+
visitor = SageArgSpecVisitor()
|
|
1006
|
+
args = [visitor.visit(a) for a in ast_args.args]
|
|
1007
|
+
defaults = [visitor.visit(d) for d in ast_args.defaults]
|
|
1008
|
+
|
|
1009
|
+
# vararg and kwarg may be None
|
|
1010
|
+
vararg = getattr(ast_args.vararg, 'arg', None)
|
|
1011
|
+
kwarg = getattr(ast_args.kwarg, 'arg', None)
|
|
1012
|
+
|
|
1013
|
+
return inspect.FullArgSpec(args, vararg, kwarg,
|
|
1014
|
+
tuple(defaults) if defaults else None,
|
|
1015
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def _sage_getargspec_cython(source):
|
|
1019
|
+
r"""
|
|
1020
|
+
inspect.getargspec from source code. That is, get the names and
|
|
1021
|
+
default values of a function's arguments.
|
|
1022
|
+
|
|
1023
|
+
INPUT:
|
|
1024
|
+
|
|
1025
|
+
- ``source`` -- string; the function's (or method's) source code
|
|
1026
|
+
definition. The function's body is ignored. The definition may
|
|
1027
|
+
contain type definitions for the function arguments.
|
|
1028
|
+
|
|
1029
|
+
OUTPUT: an instance of :class:`inspect.FullArgSpec`, i.e., a named tuple
|
|
1030
|
+
|
|
1031
|
+
EXAMPLES::
|
|
1032
|
+
|
|
1033
|
+
sage: from sage.misc.sageinspect import _sage_getargspec_cython as sgc
|
|
1034
|
+
sage: sgc("cpdef double abc(self, Element x=None, Parent base=0):")
|
|
1035
|
+
FullArgSpec(args=['self', 'x', 'base'], varargs=None, varkw=None, defaults=(None, 0), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1036
|
+
sage: sgc("def __init__(self, x=None, unsigned int base=0):")
|
|
1037
|
+
FullArgSpec(args=['self', 'x', 'base'], varargs=None, varkw=None, defaults=(None, 0), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1038
|
+
sage: sgc('def o(p, r={}, *q, **s) except? -1:')
|
|
1039
|
+
FullArgSpec(args=['p', 'r'], varargs='q', varkw='s', defaults=({},), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1040
|
+
sage: sgc('cpdef how(r=(None, "u:doing?")):')
|
|
1041
|
+
FullArgSpec(args=['r'], varargs=None, varkw=None, defaults=((None, 'u:doing?'),), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1042
|
+
sage: sgc('def _(x="):"):')
|
|
1043
|
+
FullArgSpec(args=['x'], varargs=None, varkw=None, defaults=('):',), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1044
|
+
sage: sgc('def f(z = {(1, 2, 3): True}):\n return z')
|
|
1045
|
+
FullArgSpec(args=['z'], varargs=None, varkw=None, defaults=({(1, 2, 3): True},), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1046
|
+
sage: sgc('def f(double x, z = {(1, 2, 3): True}):\n return z')
|
|
1047
|
+
FullArgSpec(args=['x', 'z'], varargs=None, varkw=None, defaults=({(1, 2, 3): True},), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1048
|
+
sage: sgc('def f(*args): pass')
|
|
1049
|
+
FullArgSpec(args=[], varargs='args', varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1050
|
+
sage: sgc('def f(**args): pass')
|
|
1051
|
+
FullArgSpec(args=[], varargs=None, varkw='args', defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1052
|
+
|
|
1053
|
+
Some malformed input is detected::
|
|
1054
|
+
|
|
1055
|
+
sage: sgc('def f(x, y')
|
|
1056
|
+
Traceback (most recent call last):
|
|
1057
|
+
...
|
|
1058
|
+
SyntaxError: Unexpected EOF while parsing argument list
|
|
1059
|
+
sage: sgc('def f(*x = 5, z = {(1,2,3): True}): pass')
|
|
1060
|
+
Traceback (most recent call last):
|
|
1061
|
+
...
|
|
1062
|
+
SyntaxError: invalid ...
|
|
1063
|
+
sage: sgc('def f(int *x = 5, z = {(1,2,3): True}): pass')
|
|
1064
|
+
Traceback (most recent call last):
|
|
1065
|
+
...
|
|
1066
|
+
SyntaxError: Pointer types not allowed in def or cpdef functions
|
|
1067
|
+
sage: sgc('def f(x = , z = {(1,2,3): True}): pass')
|
|
1068
|
+
Traceback (most recent call last):
|
|
1069
|
+
...
|
|
1070
|
+
SyntaxError: Definition of a default argument expected
|
|
1071
|
+
sage: sgc('def f(int x = 5, , z = {(1,2,3): True}): pass')
|
|
1072
|
+
Traceback (most recent call last):
|
|
1073
|
+
...
|
|
1074
|
+
SyntaxError: invalid ...
|
|
1075
|
+
|
|
1076
|
+
TESTS:
|
|
1077
|
+
|
|
1078
|
+
Some input that is malformed in Python 2 but well formed in Cython or
|
|
1079
|
+
Python 3 is correctly parsed::
|
|
1080
|
+
|
|
1081
|
+
sage: def dummy_python(self, *args, x=1): pass
|
|
1082
|
+
sage: sgc("def dummy_python(self, *args, x=1): pass")
|
|
1083
|
+
FullArgSpec(args=['self', 'x'], varargs='args', varkw=None, defaults=(1,),
|
|
1084
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1085
|
+
sage: cython("def dummy_cython(self, *args, x=1): pass") # needs sage.misc.cython
|
|
1086
|
+
sage: sgc("def dummy_cython(self, *args, x=1): pass")
|
|
1087
|
+
FullArgSpec(args=['self', 'x'], varargs='args', varkw=None, defaults=(1,),
|
|
1088
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1089
|
+
|
|
1090
|
+
In some examples above, a syntax error was raised when a type
|
|
1091
|
+
definition contains a pointer. An exception is made for ``char*``,
|
|
1092
|
+
since C strings are acceptable input in public Cython functions::
|
|
1093
|
+
|
|
1094
|
+
sage: sgc('def f(char *x = "a string", z = {(1,2,3): True}): pass')
|
|
1095
|
+
FullArgSpec(args=['x', 'z'], varargs=None, varkw=None,
|
|
1096
|
+
defaults=('a string', {(1, 2, 3): True}),
|
|
1097
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1098
|
+
"""
|
|
1099
|
+
assert isinstance(source, str)
|
|
1100
|
+
# the caller ought to ensure this, but if it forgets (e.g. passing None),
|
|
1101
|
+
# we avoid raising AttributeError to avoid confusing error message
|
|
1102
|
+
# and possible further hard-to-debug errors, see :issue:`39735`
|
|
1103
|
+
defpos = source.find('def ')
|
|
1104
|
+
assert defpos > -1, "The given source does not contain 'def'"
|
|
1105
|
+
s = source[defpos:].strip()
|
|
1106
|
+
while s:
|
|
1107
|
+
if s.startswith('('):
|
|
1108
|
+
break
|
|
1109
|
+
_, s = _split_syntactical_unit(s)
|
|
1110
|
+
s = s[1:].strip()
|
|
1111
|
+
if not s:
|
|
1112
|
+
raise SyntaxError("Function definition must contain an argument list")
|
|
1113
|
+
|
|
1114
|
+
# We remove the type declarations, build a dummy Python function, and
|
|
1115
|
+
# then call _get_argspec_from_ast. This should be
|
|
1116
|
+
# better than creating a complete parser for Cython syntax,
|
|
1117
|
+
# even though _split_syntactical_unit does part of the parsing work anyway.
|
|
1118
|
+
|
|
1119
|
+
cy_units = []
|
|
1120
|
+
while not s.startswith(')'):
|
|
1121
|
+
if not s:
|
|
1122
|
+
raise SyntaxError("Unexpected EOF while parsing argument list")
|
|
1123
|
+
u, s = _split_syntactical_unit(s)
|
|
1124
|
+
cy_units.append(u)
|
|
1125
|
+
|
|
1126
|
+
py_units = []
|
|
1127
|
+
name = None
|
|
1128
|
+
i = 0
|
|
1129
|
+
l = len(cy_units)
|
|
1130
|
+
expect_default = False
|
|
1131
|
+
nb_stars = 0
|
|
1132
|
+
varargs = None
|
|
1133
|
+
keywords = None
|
|
1134
|
+
while (i < l):
|
|
1135
|
+
unit = cy_units[i]
|
|
1136
|
+
if expect_default:
|
|
1137
|
+
if unit in ('=', '*', ','):
|
|
1138
|
+
raise SyntaxError("Definition of a default argument expected")
|
|
1139
|
+
while unit != ',':
|
|
1140
|
+
py_units.append(unit)
|
|
1141
|
+
i += 1
|
|
1142
|
+
if i == l:
|
|
1143
|
+
break
|
|
1144
|
+
unit = cy_units[i]
|
|
1145
|
+
expect_default = False
|
|
1146
|
+
name = None
|
|
1147
|
+
if nb_stars:
|
|
1148
|
+
raise SyntaxError("The %s argument has no default" % ('varargs' if nb_stars == 1 else 'keywords'))
|
|
1149
|
+
continue
|
|
1150
|
+
i += 1
|
|
1151
|
+
if unit == '*':
|
|
1152
|
+
if name:
|
|
1153
|
+
if name != 'char':
|
|
1154
|
+
raise SyntaxError("Pointer types not allowed in def or cpdef functions")
|
|
1155
|
+
else:
|
|
1156
|
+
continue
|
|
1157
|
+
else:
|
|
1158
|
+
nb_stars += 1
|
|
1159
|
+
continue
|
|
1160
|
+
elif unit == ',':
|
|
1161
|
+
if expect_default:
|
|
1162
|
+
raise SyntaxError("Unexpected EOF while parsing argument list")
|
|
1163
|
+
name = None
|
|
1164
|
+
if nb_stars:
|
|
1165
|
+
nb_stars = 0
|
|
1166
|
+
continue
|
|
1167
|
+
elif unit == '=':
|
|
1168
|
+
expect_default = True
|
|
1169
|
+
name = None
|
|
1170
|
+
if nb_stars:
|
|
1171
|
+
raise SyntaxError("The %s argument has no default" % ('varargs' if nb_stars == 1 else 'keywords'))
|
|
1172
|
+
else:
|
|
1173
|
+
name = unit
|
|
1174
|
+
if name is not None:
|
|
1175
|
+
# Is "name" part of a type definition?
|
|
1176
|
+
# If it is the last identifier before '=' or ',',
|
|
1177
|
+
# then it *is* a variable name,
|
|
1178
|
+
if i == l or cy_units[i] in ('=', ','):
|
|
1179
|
+
if nb_stars == 0:
|
|
1180
|
+
py_units.append(name)
|
|
1181
|
+
elif nb_stars == 1:
|
|
1182
|
+
if varargs is None:
|
|
1183
|
+
varargs = name
|
|
1184
|
+
# skip the "=" or ",", since varargs
|
|
1185
|
+
# is treated separately
|
|
1186
|
+
i += 1
|
|
1187
|
+
name = None
|
|
1188
|
+
nb_stars = 0
|
|
1189
|
+
else:
|
|
1190
|
+
raise SyntaxError("varargs cannot be defined twice")
|
|
1191
|
+
elif nb_stars == 2:
|
|
1192
|
+
if keywords is None:
|
|
1193
|
+
keywords = name
|
|
1194
|
+
# skip the "=" or ",", since varargs
|
|
1195
|
+
# is treated separately
|
|
1196
|
+
i += 1
|
|
1197
|
+
name = None
|
|
1198
|
+
nb_stars = 0
|
|
1199
|
+
else:
|
|
1200
|
+
raise SyntaxError("varargs cannot be defined twice")
|
|
1201
|
+
else:
|
|
1202
|
+
raise SyntaxError("variable declaration comprises at most two '*'")
|
|
1203
|
+
else:
|
|
1204
|
+
py_units.append(unit)
|
|
1205
|
+
if varargs is None:
|
|
1206
|
+
varargs = ''
|
|
1207
|
+
elif not py_units or py_units[-1] == ',':
|
|
1208
|
+
varargs = '*' + varargs
|
|
1209
|
+
else:
|
|
1210
|
+
varargs = ',*' + varargs
|
|
1211
|
+
if keywords is None:
|
|
1212
|
+
keywords = ''
|
|
1213
|
+
elif varargs or (py_units and py_units[-1] != ','):
|
|
1214
|
+
keywords = ',**' + keywords
|
|
1215
|
+
else:
|
|
1216
|
+
keywords = '**' + keywords
|
|
1217
|
+
return _sage_getargspec_from_ast('def dummy(' + ''.join(py_units) +
|
|
1218
|
+
varargs + keywords + '): pass')
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
def sage_getfile(obj):
|
|
1222
|
+
r"""
|
|
1223
|
+
Get the full file name associated to ``obj`` as a string.
|
|
1224
|
+
|
|
1225
|
+
INPUT:
|
|
1226
|
+
|
|
1227
|
+
- ``obj`` -- a Sage object, module, etc.
|
|
1228
|
+
|
|
1229
|
+
EXAMPLES::
|
|
1230
|
+
|
|
1231
|
+
sage: from sage.misc.sageinspect import sage_getfile
|
|
1232
|
+
sage: sage_getfile(sage.rings.rational)
|
|
1233
|
+
'...sage/rings/rational.pyx'
|
|
1234
|
+
sage: from sage.algebras.steenrod.steenrod_algebra import Sq # needs sage.combinat sage.modules
|
|
1235
|
+
sage: sage_getfile(Sq) # needs sage.combinat sage.modules
|
|
1236
|
+
'...sage/algebras/steenrod/steenrod_algebra.py'
|
|
1237
|
+
sage: sage_getfile(x) # needs sage.symbolic
|
|
1238
|
+
'...sage/symbolic/expression.pyx'
|
|
1239
|
+
|
|
1240
|
+
The following tests against some bugs fixed in :issue:`9976`::
|
|
1241
|
+
|
|
1242
|
+
sage: from sage.combinat.partition_algebra import SetPartitionsAk # needs sage.combinat sage.modules
|
|
1243
|
+
sage: obj = SetPartitionsAk # needs sage.combinat sage.modules
|
|
1244
|
+
sage: sage_getfile(obj) # needs sage.combinat sage.modules
|
|
1245
|
+
'...sage/combinat/partition_algebra.py'
|
|
1246
|
+
|
|
1247
|
+
And here is another bug, fixed in :issue:`11298`::
|
|
1248
|
+
|
|
1249
|
+
sage: P.<x,y> = QQ[]
|
|
1250
|
+
sage: sage_getfile(P) # needs sage.libs.singular
|
|
1251
|
+
'...sage/rings/polynomial/multi_polynomial_libsingular...'
|
|
1252
|
+
|
|
1253
|
+
Another bug with editable meson install::
|
|
1254
|
+
|
|
1255
|
+
sage: P.<x,y> = QQ[]
|
|
1256
|
+
sage: I = P * [x,y]
|
|
1257
|
+
sage: path = sage_getfile(I.groebner_basis); path
|
|
1258
|
+
'.../sage/rings/qqbar_decorators.py'
|
|
1259
|
+
sage: path == sage_getfile(sage.rings.qqbar_decorators)
|
|
1260
|
+
True
|
|
1261
|
+
|
|
1262
|
+
A problem fixed in :issue:`16309`::
|
|
1263
|
+
|
|
1264
|
+
sage: cython( # needs sage.misc.cython
|
|
1265
|
+
....: '''
|
|
1266
|
+
....: class Bar: pass
|
|
1267
|
+
....: cdef class Foo: pass
|
|
1268
|
+
....: ''')
|
|
1269
|
+
sage: sage_getfile(Bar) # needs sage.misc.cython
|
|
1270
|
+
'...pyx'
|
|
1271
|
+
sage: sage_getfile(Foo) # needs sage.misc.cython
|
|
1272
|
+
'...pyx'
|
|
1273
|
+
|
|
1274
|
+
By :issue:`18249`, we return an empty string for Python builtins. In that
|
|
1275
|
+
way, there is no error when the user types, for example, ``range?``::
|
|
1276
|
+
|
|
1277
|
+
sage: sage_getfile(range)
|
|
1278
|
+
''
|
|
1279
|
+
"""
|
|
1280
|
+
# We try to extract from docstrings, but not using Python's inspect
|
|
1281
|
+
# because _sage_getdoc_unformatted is more robust.
|
|
1282
|
+
d = _sage_getdoc_unformatted(obj)
|
|
1283
|
+
pos = _extract_embedded_position(d)
|
|
1284
|
+
if pos is not None:
|
|
1285
|
+
(_, filename, _) = pos
|
|
1286
|
+
return filename
|
|
1287
|
+
|
|
1288
|
+
# The instance case
|
|
1289
|
+
if isclassinstance(obj):
|
|
1290
|
+
if isinstance(obj, functools.partial):
|
|
1291
|
+
return sage_getfile(obj.func)
|
|
1292
|
+
return sage_getfile(obj.__class__) # inspect.getabsfile(obj.__class__)
|
|
1293
|
+
else:
|
|
1294
|
+
if hasattr(obj, '__init__'):
|
|
1295
|
+
pos = _extract_embedded_position(_sage_getdoc_unformatted(obj.__init__))
|
|
1296
|
+
if pos is not None:
|
|
1297
|
+
(_, filename, _) = pos
|
|
1298
|
+
return filename
|
|
1299
|
+
|
|
1300
|
+
# No go? fall back to inspect.
|
|
1301
|
+
try:
|
|
1302
|
+
sourcefile = inspect.getabsfile(obj)
|
|
1303
|
+
except TypeError: # this happens for Python builtins
|
|
1304
|
+
return ''
|
|
1305
|
+
for suffix in import_machinery.EXTENSION_SUFFIXES:
|
|
1306
|
+
if sourcefile.endswith(suffix):
|
|
1307
|
+
# TODO: the following is incorrect in meson editable install
|
|
1308
|
+
# because the build is out-of-tree,
|
|
1309
|
+
# but as long as either the class or its __init__ method has a
|
|
1310
|
+
# docstring, _sage_getdoc_unformatted should return correct result
|
|
1311
|
+
# see https://github.com/mesonbuild/meson-python/issues/723
|
|
1312
|
+
return sourcefile.removesuffix(suffix)+os.path.extsep+'pyx'
|
|
1313
|
+
return sourcefile
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
def sage_getfile_relative(obj):
|
|
1317
|
+
r"""
|
|
1318
|
+
Get the file name associated to ``obj`` as a string.
|
|
1319
|
+
|
|
1320
|
+
This is the same as :func:`sage_getfile`, but
|
|
1321
|
+
if the source file is part of the ``sage.*`` namespace, it
|
|
1322
|
+
makes the file name relative so that it starts with ``sage/``.
|
|
1323
|
+
|
|
1324
|
+
INPUT:
|
|
1325
|
+
|
|
1326
|
+
- ``obj`` -- a Sage object, module, etc.
|
|
1327
|
+
|
|
1328
|
+
EXAMPLES::
|
|
1329
|
+
|
|
1330
|
+
sage: from sage.misc.sageinspect import sage_getfile_relative
|
|
1331
|
+
sage: sage_getfile_relative(sage.rings.rational)
|
|
1332
|
+
'sage/rings/rational.pyx'
|
|
1333
|
+
sage: from sage.algebras.steenrod.steenrod_algebra import Sq # needs sage.combinat sage.modules
|
|
1334
|
+
sage: sage_getfile_relative(Sq) # needs sage.combinat sage.modules
|
|
1335
|
+
'sage/algebras/steenrod/steenrod_algebra.py'
|
|
1336
|
+
sage: sage_getfile_relative(x) # needs sage.symbolic
|
|
1337
|
+
'sage/symbolic/expression.pyx'
|
|
1338
|
+
sage: sage_getfile_relative(range)
|
|
1339
|
+
''
|
|
1340
|
+
"""
|
|
1341
|
+
filename = sage_getfile(obj)
|
|
1342
|
+
if not filename:
|
|
1343
|
+
return filename
|
|
1344
|
+
|
|
1345
|
+
from os.path import relpath, normpath, commonprefix
|
|
1346
|
+
|
|
1347
|
+
def directories():
|
|
1348
|
+
try:
|
|
1349
|
+
from sage.env import SAGE_SRC
|
|
1350
|
+
except ImportError:
|
|
1351
|
+
pass
|
|
1352
|
+
else:
|
|
1353
|
+
if SAGE_SRC:
|
|
1354
|
+
yield normpath(os.path.join(SAGE_SRC, 'sage'))
|
|
1355
|
+
import sage
|
|
1356
|
+
yield from sage.__path__
|
|
1357
|
+
|
|
1358
|
+
for directory in directories():
|
|
1359
|
+
if commonprefix([filename, directory]) == directory:
|
|
1360
|
+
return os.path.join('sage', relpath(filename, directory))
|
|
1361
|
+
|
|
1362
|
+
return filename
|
|
1363
|
+
|
|
1364
|
+
|
|
1365
|
+
def sage_getargspec(obj):
|
|
1366
|
+
r"""
|
|
1367
|
+
Return the names and default values of a function's arguments.
|
|
1368
|
+
|
|
1369
|
+
INPUT:
|
|
1370
|
+
|
|
1371
|
+
- ``obj`` -- any callable object
|
|
1372
|
+
|
|
1373
|
+
OUTPUT:
|
|
1374
|
+
|
|
1375
|
+
A named tuple :class:`FullArgSpec` is returned, as specified by the
|
|
1376
|
+
Python library function :func:`inspect.getfullargspec`.
|
|
1377
|
+
|
|
1378
|
+
NOTE:
|
|
1379
|
+
|
|
1380
|
+
If the object has a method ``_sage_argspec_``, then the output of
|
|
1381
|
+
that method is transformed into a named tuple and then returned.
|
|
1382
|
+
|
|
1383
|
+
If a class instance has a method ``_sage_src_``, then its output
|
|
1384
|
+
is studied to determine the argspec. This is because currently
|
|
1385
|
+
the :class:`~sage.misc.cachefunc.CachedMethod` decorator has
|
|
1386
|
+
no ``_sage_argspec_`` method.
|
|
1387
|
+
|
|
1388
|
+
EXAMPLES::
|
|
1389
|
+
|
|
1390
|
+
sage: from sage.misc.sageinspect import sage_getargspec
|
|
1391
|
+
sage: def f(x, y, z=1, t=2, *args, **keywords):
|
|
1392
|
+
....: pass
|
|
1393
|
+
sage: sage_getargspec(f)
|
|
1394
|
+
FullArgSpec(args=['x', 'y', 'z', 't'], varargs='args', varkw='keywords',
|
|
1395
|
+
defaults=(1, 2), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1396
|
+
|
|
1397
|
+
We now run :func:`sage_getargspec` on some functions from the Sage library::
|
|
1398
|
+
|
|
1399
|
+
sage: sage_getargspec(identity_matrix) # needs sage.modules
|
|
1400
|
+
FullArgSpec(args=['ring', 'n', 'sparse'], varargs=None, varkw=None,
|
|
1401
|
+
defaults=(0, False), kwonlyargs=[], kwonlydefaults=None,
|
|
1402
|
+
annotations={})
|
|
1403
|
+
sage: sage_getargspec(factor)
|
|
1404
|
+
FullArgSpec(args=['n', 'proof', 'int_', 'algorithm', 'verbose'],
|
|
1405
|
+
varargs=None, varkw='kwds', defaults=(None, False, 'pari', 0),
|
|
1406
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1407
|
+
|
|
1408
|
+
In the case of a class or a class instance, the :class:`FullArgSpec` of the
|
|
1409
|
+
``__new__``, ``__init__`` or ``__call__`` method is returned::
|
|
1410
|
+
|
|
1411
|
+
sage: P.<x,y> = QQ[]
|
|
1412
|
+
sage: sage_getargspec(P) # needs sage.libs.singular
|
|
1413
|
+
FullArgSpec(args=['base_ring', 'n', 'names', 'order'],
|
|
1414
|
+
varargs=None, varkw=None, defaults=('degrevlex',),
|
|
1415
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1416
|
+
sage: sage_getargspec(P.__class__) # needs sage.libs.singular
|
|
1417
|
+
FullArgSpec(args=['self', 'x'], varargs='args', varkw='kwds', defaults=(0,),
|
|
1418
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1419
|
+
|
|
1420
|
+
The following tests against various bugs that were fixed in
|
|
1421
|
+
:issue:`9976`::
|
|
1422
|
+
|
|
1423
|
+
sage: from sage.rings.polynomial.real_roots import bernstein_polynomial_factory_ratlist # needs sage.libs.linbox sage.modules
|
|
1424
|
+
sage: sage_getargspec(bernstein_polynomial_factory_ratlist.coeffs_bitsize) # needs sage.libs.linbox sage.modules
|
|
1425
|
+
FullArgSpec(args=['self'], varargs=None, varkw=None, defaults=None,
|
|
1426
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1427
|
+
sage: from sage.rings.polynomial.pbori.pbori import BooleanMonomialMonoid # needs sage.rings.polynomial.pbori
|
|
1428
|
+
sage: sage_getargspec(BooleanMonomialMonoid.gen) # needs sage.rings.polynomial.pbori
|
|
1429
|
+
FullArgSpec(args=['self', 'i'], varargs=None, varkw=None, defaults=(0,),
|
|
1430
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1431
|
+
sage: I = P*[x,y]
|
|
1432
|
+
sage: sage_getargspec(I.groebner_basis) # needs sage.libs.singular
|
|
1433
|
+
FullArgSpec(args=['self', 'algorithm', 'deg_bound', 'mult_bound', 'prot'],
|
|
1434
|
+
varargs='args', varkw='kwds', defaults=('', None, None, False),
|
|
1435
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1436
|
+
sage: cython("cpdef int foo(x,y) except -1: return 1") # needs sage.misc.cython
|
|
1437
|
+
sage: sage_getargspec(foo) # needs sage.misc.cython
|
|
1438
|
+
FullArgSpec(args=['x', 'y'], varargs=None, varkw=None, defaults=None,
|
|
1439
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1440
|
+
|
|
1441
|
+
If a :func:`functools.partial` instance is involved, we see no other meaningful solution
|
|
1442
|
+
than to return the argspec of the underlying function::
|
|
1443
|
+
|
|
1444
|
+
sage: def f(a, b, c, d=1):
|
|
1445
|
+
....: return a + b + c + d
|
|
1446
|
+
sage: import functools
|
|
1447
|
+
sage: f1 = functools.partial(f, 1, c=2)
|
|
1448
|
+
sage: sage_getargspec(f1)
|
|
1449
|
+
FullArgSpec(args=['a', 'b', 'c', 'd'], varargs=None, varkw=None, defaults=(1,),
|
|
1450
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1451
|
+
|
|
1452
|
+
TESTS:
|
|
1453
|
+
|
|
1454
|
+
By :issue:`9976`, rather complicated cases work. In the
|
|
1455
|
+
following example, we dynamically create an extension class
|
|
1456
|
+
that returns some source code, and the example shows that
|
|
1457
|
+
the source code is taken for granted, i.e., the argspec of
|
|
1458
|
+
an instance of that class does not coincide with the argspec
|
|
1459
|
+
of its call method. That behaviour is intended, since a
|
|
1460
|
+
decorated method appears to have the generic signature
|
|
1461
|
+
``*args, **kwds``, but in fact it is only supposed to be called
|
|
1462
|
+
with the arguments requested by the underlying undecorated
|
|
1463
|
+
method. We saw an easy example above, namely ``I.groebner_basis``.
|
|
1464
|
+
Here is a more difficult one::
|
|
1465
|
+
|
|
1466
|
+
sage: # needs sage.misc.cython
|
|
1467
|
+
sage: cython_code = [
|
|
1468
|
+
....: 'cdef class MyClass:',
|
|
1469
|
+
....: ' def _sage_src_(self):',
|
|
1470
|
+
....: ' return "def foo(x, a=\\\')\\\"\\\', b={(2+1):\\\'bar\\\', not 1:3, 3<<4:5}): return\\n"',
|
|
1471
|
+
....: ' def __call__(self, m, n): return "something"']
|
|
1472
|
+
sage: cython('\n'.join(cython_code))
|
|
1473
|
+
sage: O = MyClass()
|
|
1474
|
+
sage: print(sage.misc.sageinspect.sage_getsource(O))
|
|
1475
|
+
def foo(x, a=')"', b={(2+1):'bar', not 1:3, 3<<4:5}): return
|
|
1476
|
+
sage: spec = sage.misc.sageinspect.sage_getargspec(O)
|
|
1477
|
+
sage: spec.args, spec.varargs, spec.varkw
|
|
1478
|
+
(['x', 'a', 'b'], None, None)
|
|
1479
|
+
sage: spec.defaults[0]
|
|
1480
|
+
')"'
|
|
1481
|
+
sage: sorted(spec.defaults[1].items(), key=lambda x: str(x))
|
|
1482
|
+
[(3, 'bar'), (48, 5), (False, 3)]
|
|
1483
|
+
sage: sage.misc.sageinspect.sage_getargspec(O.__call__)
|
|
1484
|
+
FullArgSpec(args=['self', 'm', 'n'], varargs=None, varkw=None, defaults=None,
|
|
1485
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1486
|
+
|
|
1487
|
+
::
|
|
1488
|
+
|
|
1489
|
+
sage: cython('def foo(x, a=\'\\\')"\', b={not (2+1==3):\'bar\'}): return') # needs sage.misc.cython
|
|
1490
|
+
sage: print(sage.misc.sageinspect.sage_getsource(foo)) # needs sage.misc.cython
|
|
1491
|
+
def foo(x, a='\')"', b={not (2+1==3):'bar'}): return
|
|
1492
|
+
<BLANKLINE>
|
|
1493
|
+
sage: sage.misc.sageinspect.sage_getargspec(foo) # needs sage.misc.cython
|
|
1494
|
+
FullArgSpec(args=['x', 'a', 'b'], varargs=None, varkw=None,
|
|
1495
|
+
defaults=('\')"', {False: 'bar'}),
|
|
1496
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1497
|
+
|
|
1498
|
+
The following produced a syntax error before the patch at :issue:`11913`,
|
|
1499
|
+
see also :issue:`26906`::
|
|
1500
|
+
|
|
1501
|
+
sage: sage.misc.sageinspect.sage_getargspec(r.lm) # optional - rpy2
|
|
1502
|
+
FullArgSpec(args=['self'], varargs='args', varkw='kwds', defaults=None,
|
|
1503
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1504
|
+
|
|
1505
|
+
The following was fixed in :issue:`16309`::
|
|
1506
|
+
|
|
1507
|
+
sage: # needs sage.misc.cython
|
|
1508
|
+
sage: cython(
|
|
1509
|
+
....: '''
|
|
1510
|
+
....: class Foo:
|
|
1511
|
+
....: @staticmethod
|
|
1512
|
+
....: def join(categories, bint as_list=False, tuple ignore_axioms=(), tuple axioms=()): pass
|
|
1513
|
+
....: cdef class Bar:
|
|
1514
|
+
....: @staticmethod
|
|
1515
|
+
....: def join(categories, bint as_list=False, tuple ignore_axioms=(), tuple axioms=()): pass
|
|
1516
|
+
....: cpdef meet(categories, bint as_list=False, tuple ignore_axioms=(), tuple axioms=()): pass
|
|
1517
|
+
....: ''')
|
|
1518
|
+
sage: sage_getargspec(Foo.join)
|
|
1519
|
+
FullArgSpec(args=['categories', 'as_list', 'ignore_axioms', 'axioms'], varargs=None, varkw=None,
|
|
1520
|
+
defaults=(False, (), ()), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1521
|
+
sage: sage_getargspec(Bar.join)
|
|
1522
|
+
FullArgSpec(args=['categories', 'as_list', 'ignore_axioms', 'axioms'], varargs=None, varkw=None,
|
|
1523
|
+
defaults=(False, (), ()), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1524
|
+
sage: sage_getargspec(Bar.meet)
|
|
1525
|
+
FullArgSpec(args=['categories', 'as_list', 'ignore_axioms', 'axioms'], varargs=None, varkw=None,
|
|
1526
|
+
defaults=(False, (), ()), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1527
|
+
|
|
1528
|
+
Test that :issue:`17009` is fixed::
|
|
1529
|
+
|
|
1530
|
+
sage: sage_getargspec(gap) # needs sage.libs.gap
|
|
1531
|
+
FullArgSpec(args=['self', 'x', 'name'], varargs=None, varkw=None,
|
|
1532
|
+
defaults=(None,), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1533
|
+
|
|
1534
|
+
By :issue:`17814`, the following gives the correct answer (previously, the
|
|
1535
|
+
defaults would have been found ``None``)::
|
|
1536
|
+
|
|
1537
|
+
sage: from sage.misc.nested_class import MainClass
|
|
1538
|
+
sage: sage_getargspec(MainClass.NestedClass.NestedSubClass.dummy)
|
|
1539
|
+
FullArgSpec(args=['self', 'x', 'r'], varargs='args', varkw='kwds',
|
|
1540
|
+
defaults=((1, 2, 3.4),), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1541
|
+
|
|
1542
|
+
In :issue:`18249` was decided to return a generic signature for Python
|
|
1543
|
+
builtin functions, rather than to raise an error (which is what Python's
|
|
1544
|
+
inspect module does)::
|
|
1545
|
+
|
|
1546
|
+
sage: import inspect
|
|
1547
|
+
sage: sage_getargspec(range)
|
|
1548
|
+
FullArgSpec(args=[], varargs='args', varkw='kwds', defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1549
|
+
|
|
1550
|
+
Test that :issue:`28524` is fixed::
|
|
1551
|
+
|
|
1552
|
+
sage: from sage.repl.interpreter import get_test_shell
|
|
1553
|
+
sage: shell = get_test_shell()
|
|
1554
|
+
sage: shell.run_cell(
|
|
1555
|
+
....: 'class Foo:\n'
|
|
1556
|
+
....: ' def __call__(self):\n'
|
|
1557
|
+
....: ' return None\n'
|
|
1558
|
+
....: ' def __module__(self):\n'
|
|
1559
|
+
....: ' return "sage.misc.sageinspect"\n'
|
|
1560
|
+
....: ' def _sage_src_(self):\n'
|
|
1561
|
+
....: ' return "the source code string"')
|
|
1562
|
+
sage: shell.run_cell('f = Foo()')
|
|
1563
|
+
sage: shell.run_cell('f??')
|
|
1564
|
+
...the source code string...
|
|
1565
|
+
"""
|
|
1566
|
+
from sage.misc.lazy_attribute import lazy_attribute
|
|
1567
|
+
from sage.misc.abstract_method import AbstractMethod
|
|
1568
|
+
if inspect.isclass(obj):
|
|
1569
|
+
return sage_getargspec(obj.__call__)
|
|
1570
|
+
if isinstance(obj, (lazy_attribute, AbstractMethod)):
|
|
1571
|
+
source = sage_getsource(obj)
|
|
1572
|
+
return inspect.FullArgSpec(*_sage_getargspec_cython(source))
|
|
1573
|
+
if not callable(obj):
|
|
1574
|
+
raise TypeError("obj is not a code object")
|
|
1575
|
+
try:
|
|
1576
|
+
return inspect.FullArgSpec(*obj._sage_argspec_())
|
|
1577
|
+
except (AttributeError, TypeError):
|
|
1578
|
+
pass
|
|
1579
|
+
# If we are lucky, the function signature is embedded in the docstring.
|
|
1580
|
+
docstring = _sage_getdoc_unformatted(obj)
|
|
1581
|
+
try:
|
|
1582
|
+
name = obj.__name__
|
|
1583
|
+
except AttributeError:
|
|
1584
|
+
name = type(obj).__name__
|
|
1585
|
+
argspec = _extract_embedded_signature(docstring, name)[1]
|
|
1586
|
+
if argspec is not None:
|
|
1587
|
+
return argspec
|
|
1588
|
+
if hasattr(obj, '__code__'):
|
|
1589
|
+
# Note that this may give a wrong result for the constants!
|
|
1590
|
+
try:
|
|
1591
|
+
args, varargs, varkw = inspect.getargs(obj.__code__)
|
|
1592
|
+
return inspect.FullArgSpec(args, varargs, varkw, obj.__defaults__,
|
|
1593
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1594
|
+
except (TypeError, AttributeError):
|
|
1595
|
+
pass
|
|
1596
|
+
if isclassinstance(obj):
|
|
1597
|
+
if hasattr(obj, '_sage_src_'): # it may be a decorator!
|
|
1598
|
+
source = sage_getsource(obj)
|
|
1599
|
+
try:
|
|
1600
|
+
# we try to find the definition and parse it by
|
|
1601
|
+
# _sage_getargspec_ast
|
|
1602
|
+
proxy = 'def dummy' + _grep_first_pair_of_parentheses(source) \
|
|
1603
|
+
+ ':\n return'
|
|
1604
|
+
return _sage_getargspec_from_ast(proxy)
|
|
1605
|
+
except SyntaxError:
|
|
1606
|
+
# To fix trac #10860. See #11913 for more information.
|
|
1607
|
+
# See also #26906 and #28524.
|
|
1608
|
+
pass
|
|
1609
|
+
if isinstance(obj, functools.partial):
|
|
1610
|
+
base_spec = sage_getargspec(obj.func)
|
|
1611
|
+
return base_spec
|
|
1612
|
+
return sage_getargspec(obj.__class__.__call__)
|
|
1613
|
+
elif (hasattr(obj, '__objclass__') and hasattr(obj, '__name__') and
|
|
1614
|
+
obj.__name__ == 'next'):
|
|
1615
|
+
# Handle sage.rings.ring.FiniteFieldIterator.next and similar
|
|
1616
|
+
# slot wrappers. This is mainly to suppress Sphinx warnings.
|
|
1617
|
+
return ['self'], None, None, None
|
|
1618
|
+
else:
|
|
1619
|
+
# We try to get the argspec by reading the source, which may be
|
|
1620
|
+
# expensive, but should only be needed for functions defined outside
|
|
1621
|
+
# of the Sage library (since otherwise the signature should be
|
|
1622
|
+
# embedded in the docstring)
|
|
1623
|
+
try:
|
|
1624
|
+
source = sage_getsource(obj)
|
|
1625
|
+
except TypeError: # happens for Python builtins
|
|
1626
|
+
source = ''
|
|
1627
|
+
if source:
|
|
1628
|
+
return inspect.FullArgSpec(*_sage_getargspec_cython(source))
|
|
1629
|
+
else:
|
|
1630
|
+
func_obj = obj
|
|
1631
|
+
|
|
1632
|
+
# Otherwise we're (hopefully!) plain Python, so use inspect
|
|
1633
|
+
try:
|
|
1634
|
+
args, varargs, varkw = inspect.getargs(func_obj.__code__)
|
|
1635
|
+
except AttributeError:
|
|
1636
|
+
try:
|
|
1637
|
+
args, varargs, varkw = inspect.getargs(func_obj)
|
|
1638
|
+
except TypeError: # arg is not a code object
|
|
1639
|
+
# The above "hopefully" was wishful thinking:
|
|
1640
|
+
try:
|
|
1641
|
+
source = sage_getsource(obj)
|
|
1642
|
+
if source is not None:
|
|
1643
|
+
return inspect.FullArgSpec(*_sage_getargspec_cython(source))
|
|
1644
|
+
except TypeError: # This happens for Python builtins
|
|
1645
|
+
pass
|
|
1646
|
+
# The best we can do is to return a generic argspec
|
|
1647
|
+
args = []
|
|
1648
|
+
varargs = 'args'
|
|
1649
|
+
varkw = 'kwds'
|
|
1650
|
+
try:
|
|
1651
|
+
defaults = func_obj.__defaults__
|
|
1652
|
+
except AttributeError:
|
|
1653
|
+
defaults = None
|
|
1654
|
+
return inspect.FullArgSpec(args, varargs, varkw, defaults,
|
|
1655
|
+
kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1656
|
+
|
|
1657
|
+
|
|
1658
|
+
def _fullargspec_to_signature(fullargspec):
|
|
1659
|
+
"""
|
|
1660
|
+
Converts a :class:`FullArgSpec` instance to a :class:`Signature` instance by best effort.
|
|
1661
|
+
The opposite conversion is implemented in the source code of :func:`inspect.getfullargspec`.
|
|
1662
|
+
|
|
1663
|
+
EXAMPLES::
|
|
1664
|
+
|
|
1665
|
+
sage: from sage.misc.sageinspect import _fullargspec_to_signature
|
|
1666
|
+
sage: from inspect import FullArgSpec
|
|
1667
|
+
sage: fullargspec = FullArgSpec(args=['self', 'x', 'base'], varargs=None, varkw=None, defaults=(None, 0), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1668
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1669
|
+
<Signature (self, x=None, base=0)>
|
|
1670
|
+
|
|
1671
|
+
TESTS::
|
|
1672
|
+
|
|
1673
|
+
sage: fullargspec = FullArgSpec(args=['p', 'r'], varargs='q', varkw='s', defaults=({},), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1674
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1675
|
+
<Signature (p, r={}, *q, **s)>
|
|
1676
|
+
sage: fullargspec = FullArgSpec(args=['r'], varargs=None, varkw=None, defaults=((None, 'u:doing?'),), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1677
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1678
|
+
<Signature (r=(None, 'u:doing?'))>
|
|
1679
|
+
sage: fullargspec = FullArgSpec(args=['x'], varargs=None, varkw=None, defaults=('):',), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1680
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1681
|
+
<Signature (x='):')>
|
|
1682
|
+
sage: fullargspec = FullArgSpec(args=['z'], varargs=None, varkw=None, defaults=({(1, 2, 3): True},), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1683
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1684
|
+
<Signature (z={(1, 2, 3): True})>
|
|
1685
|
+
sage: fullargspec = FullArgSpec(args=['x', 'z'], varargs=None, varkw=None, defaults=({(1, 2, 3): True},), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1686
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1687
|
+
<Signature (x, z={(1, 2, 3): True})>
|
|
1688
|
+
sage: fullargspec = FullArgSpec(args=[], varargs='args', varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1689
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1690
|
+
<Signature (*args)>
|
|
1691
|
+
sage: fullargspec = FullArgSpec(args=[], varargs=None, varkw='args', defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1692
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1693
|
+
<Signature (**args)>
|
|
1694
|
+
sage: fullargspec = FullArgSpec(args=['self', 'x'], varargs='args', varkw=None, defaults=(1,), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1695
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1696
|
+
<Signature (self, x=1, *args)>
|
|
1697
|
+
sage: fullargspec = FullArgSpec(args=['self', 'x'], varargs='args', varkw=None, defaults=(1,), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1698
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1699
|
+
<Signature (self, x=1, *args)>
|
|
1700
|
+
sage: fullargspec = FullArgSpec(args=['x', 'z'], varargs=None, varkw=None, defaults=('a string', {(1, 2, 3): True}), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
1701
|
+
sage: _fullargspec_to_signature(fullargspec)
|
|
1702
|
+
<Signature (x='a string', z={(1, 2, 3): True})>
|
|
1703
|
+
sage: _fullargspec_to_signature(FullArgSpec(args=['a'], varargs=None, varkw=None, defaults=None, kwonlyargs=['b', 'c'], kwonlydefaults={'b': 1}, annotations={}))
|
|
1704
|
+
<Signature (a, *, b=1, c)>
|
|
1705
|
+
sage: _fullargspec_to_signature(FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(1,), kwonlyargs=['c'], kwonlydefaults=None, annotations={}))
|
|
1706
|
+
<Signature (a, b=1, *, c)>
|
|
1707
|
+
"""
|
|
1708
|
+
parameters = []
|
|
1709
|
+
defaults_start = len(fullargspec.args) - len(fullargspec.defaults) if fullargspec.defaults else None
|
|
1710
|
+
|
|
1711
|
+
for i, arg in enumerate(fullargspec.args):
|
|
1712
|
+
default = fullargspec.defaults[i - defaults_start] if defaults_start is not None and i >= defaults_start else Parameter.empty
|
|
1713
|
+
param = Parameter(arg, Parameter.POSITIONAL_OR_KEYWORD, default=default)
|
|
1714
|
+
parameters.append(param)
|
|
1715
|
+
|
|
1716
|
+
if fullargspec.varargs:
|
|
1717
|
+
param = Parameter(fullargspec.varargs, Parameter.VAR_POSITIONAL)
|
|
1718
|
+
parameters.append(param)
|
|
1719
|
+
|
|
1720
|
+
if fullargspec.varkw:
|
|
1721
|
+
param = Parameter(fullargspec.varkw, Parameter.VAR_KEYWORD)
|
|
1722
|
+
parameters.append(param)
|
|
1723
|
+
|
|
1724
|
+
for arg in fullargspec.kwonlyargs:
|
|
1725
|
+
param = Parameter(arg, Parameter.KEYWORD_ONLY, default=Parameter.empty if fullargspec.kwonlydefaults is None else
|
|
1726
|
+
fullargspec.kwonlydefaults.get(arg, Parameter.empty))
|
|
1727
|
+
parameters.append(param)
|
|
1728
|
+
|
|
1729
|
+
return Signature(parameters)
|
|
1730
|
+
|
|
1731
|
+
|
|
1732
|
+
def sage_signature(obj):
|
|
1733
|
+
r"""
|
|
1734
|
+
Return the names and default values of a function's arguments.
|
|
1735
|
+
|
|
1736
|
+
INPUT:
|
|
1737
|
+
|
|
1738
|
+
- ``obj`` -- any callable object
|
|
1739
|
+
|
|
1740
|
+
OUTPUT:
|
|
1741
|
+
|
|
1742
|
+
A :class:`Signature` is returned, as specified by the
|
|
1743
|
+
Python library function :func:`inspect.signature`.
|
|
1744
|
+
|
|
1745
|
+
|
|
1746
|
+
.. NOTE::
|
|
1747
|
+
|
|
1748
|
+
Currently the type information is not returned, because the output
|
|
1749
|
+
is converted from the return value of :func:`sage_getargspec`.
|
|
1750
|
+
This should be changed in the future.
|
|
1751
|
+
|
|
1752
|
+
EXAMPLES::
|
|
1753
|
+
|
|
1754
|
+
sage: from sage.misc.sageinspect import sage_signature
|
|
1755
|
+
sage: def f(x, y, z=1, t=2, *args, **keywords):
|
|
1756
|
+
....: pass
|
|
1757
|
+
sage: sage_signature(f)
|
|
1758
|
+
<Signature (x, y, z=1, t=2, *args, **keywords)>
|
|
1759
|
+
|
|
1760
|
+
We now run :func:`sage_signature` on some functions from the Sage library::
|
|
1761
|
+
|
|
1762
|
+
sage: sage_signature(identity_matrix) # needs sage.modules
|
|
1763
|
+
<Signature (ring, n=0, sparse=False)>
|
|
1764
|
+
sage: sage_signature(factor)
|
|
1765
|
+
<Signature (n, proof=None, int_=False, algorithm='pari', verbose=0, **kwds)>
|
|
1766
|
+
|
|
1767
|
+
In the case of a class or a class instance, the :class:`Signature` of the
|
|
1768
|
+
``__new__``, ``__init__`` or ``__call__`` method is returned::
|
|
1769
|
+
|
|
1770
|
+
sage: P.<x,y> = QQ[]
|
|
1771
|
+
sage: sage_signature(P) # needs sage.libs.singular
|
|
1772
|
+
<Signature (base_ring, n, names, order='degrevlex')>
|
|
1773
|
+
sage: sage_signature(P.__class__) # needs sage.libs.singular
|
|
1774
|
+
<Signature (self, x=0, *args, **kwds)>
|
|
1775
|
+
|
|
1776
|
+
The following tests against various bugs that were fixed in
|
|
1777
|
+
:issue:`9976`::
|
|
1778
|
+
|
|
1779
|
+
sage: from sage.rings.polynomial.real_roots import bernstein_polynomial_factory_ratlist # needs sage.libs.linbox sage.modules
|
|
1780
|
+
sage: sage_signature(bernstein_polynomial_factory_ratlist.coeffs_bitsize) # needs sage.libs.linbox sage.modules
|
|
1781
|
+
<Signature (self)>
|
|
1782
|
+
sage: from sage.rings.polynomial.pbori.pbori import BooleanMonomialMonoid # needs sage.rings.polynomial.pbori
|
|
1783
|
+
sage: sage_signature(BooleanMonomialMonoid.gen) # needs sage.rings.polynomial.pbori
|
|
1784
|
+
<Signature (self, i=0)>
|
|
1785
|
+
sage: I = P*[x,y]
|
|
1786
|
+
sage: sage_signature(I.groebner_basis) # needs sage.libs.singular
|
|
1787
|
+
<Signature (self, algorithm='', deg_bound=None, mult_bound=None, prot=False, *args, **kwds)>
|
|
1788
|
+
sage: cython("cpdef int foo(x,y) except -1: return 1") # needs sage.misc.cython
|
|
1789
|
+
sage: sage_signature(foo) # needs sage.misc.cython
|
|
1790
|
+
<Signature (x, y)>
|
|
1791
|
+
|
|
1792
|
+
If a :func:`functools.partial` instance is involved, we see no other meaningful solution
|
|
1793
|
+
than to return the signature of the underlying function::
|
|
1794
|
+
|
|
1795
|
+
sage: def f(a, b, c, d=1):
|
|
1796
|
+
....: return a + b + c + d
|
|
1797
|
+
sage: import functools
|
|
1798
|
+
sage: f1 = functools.partial(f, 1, c=2)
|
|
1799
|
+
sage: sage_signature(f1)
|
|
1800
|
+
<Signature (a, b, c, d=1)>
|
|
1801
|
+
"""
|
|
1802
|
+
return _fullargspec_to_signature(sage_getargspec(obj))
|
|
1803
|
+
|
|
1804
|
+
|
|
1805
|
+
def formatannotation(annotation, base_module=None):
|
|
1806
|
+
"""
|
|
1807
|
+
This is taken from Python 3.7's inspect.py; the only change is to
|
|
1808
|
+
add documentation.
|
|
1809
|
+
|
|
1810
|
+
INPUT:
|
|
1811
|
+
|
|
1812
|
+
- ``annotation`` -- annotation for a function
|
|
1813
|
+
- ``base_module`` -- (default: ``None``)
|
|
1814
|
+
|
|
1815
|
+
This is only relevant with Python 3, so the doctests are marked
|
|
1816
|
+
accordingly.
|
|
1817
|
+
|
|
1818
|
+
EXAMPLES::
|
|
1819
|
+
|
|
1820
|
+
sage: from sage.misc.sageinspect import formatannotation
|
|
1821
|
+
sage: import inspect
|
|
1822
|
+
sage: def foo(a, *, b:int, **kwargs):
|
|
1823
|
+
....: pass
|
|
1824
|
+
sage: s = inspect.signature(foo)
|
|
1825
|
+
|
|
1826
|
+
sage: a = s.parameters['a'].annotation
|
|
1827
|
+
sage: a
|
|
1828
|
+
<class 'inspect._empty'>
|
|
1829
|
+
sage: formatannotation(a)
|
|
1830
|
+
'inspect._empty'
|
|
1831
|
+
|
|
1832
|
+
sage: b = s.parameters['b'].annotation
|
|
1833
|
+
sage: b
|
|
1834
|
+
<class 'int'>
|
|
1835
|
+
sage: formatannotation(b)
|
|
1836
|
+
'int'
|
|
1837
|
+
"""
|
|
1838
|
+
if getattr(annotation, '__module__', None) == 'typing':
|
|
1839
|
+
return repr(annotation).replace('typing.', '')
|
|
1840
|
+
if isinstance(annotation, type):
|
|
1841
|
+
if annotation.__module__ in ('builtins', base_module):
|
|
1842
|
+
return annotation.__qualname__
|
|
1843
|
+
return annotation.__module__ + '.' + annotation.__qualname__
|
|
1844
|
+
return repr(annotation)
|
|
1845
|
+
|
|
1846
|
+
|
|
1847
|
+
_formatannotation = formatannotation
|
|
1848
|
+
|
|
1849
|
+
|
|
1850
|
+
def sage_formatargspec(args, varargs=None, varkw=None, defaults=None,
|
|
1851
|
+
kwonlyargs=(), kwonlydefaults=None, annotations={},
|
|
1852
|
+
formatarg=str,
|
|
1853
|
+
formatvarargs=None,
|
|
1854
|
+
formatvarkw=None,
|
|
1855
|
+
formatvalue=None,
|
|
1856
|
+
formatreturns=None,
|
|
1857
|
+
formatannotation=None):
|
|
1858
|
+
"""
|
|
1859
|
+
Format an argument spec from the values returned by getfullargspec.
|
|
1860
|
+
|
|
1861
|
+
The first seven arguments are (args, varargs, varkw, defaults,
|
|
1862
|
+
kwonlyargs, kwonlydefaults, annotations). The other five arguments
|
|
1863
|
+
are the corresponding optional formatting functions that are called to
|
|
1864
|
+
turn names and values into strings. The last argument is an optional
|
|
1865
|
+
function to format the sequence of arguments.
|
|
1866
|
+
|
|
1867
|
+
This is taken from Python 3.7's inspect.py, where it is
|
|
1868
|
+
deprecated. The only change, aside from documentation (this
|
|
1869
|
+
paragraph and the next, plus doctests), is to remove the
|
|
1870
|
+
deprecation warning.
|
|
1871
|
+
|
|
1872
|
+
Sage uses this function to format arguments, as obtained by
|
|
1873
|
+
:func:`sage_getargspec`. Since :func:`sage_getargspec` works for
|
|
1874
|
+
Cython functions while Python's inspect module does not, it makes
|
|
1875
|
+
sense to keep this function for formatting instances of
|
|
1876
|
+
``inspect.FullArgSpec``.
|
|
1877
|
+
|
|
1878
|
+
EXAMPLES::
|
|
1879
|
+
|
|
1880
|
+
sage: from sage.misc.sageinspect import sage_formatargspec
|
|
1881
|
+
sage: args = ['a', 'b', 'c']
|
|
1882
|
+
sage: defaults = [3]
|
|
1883
|
+
sage: sage_formatargspec(args, defaults=defaults)
|
|
1884
|
+
'(a, b, c=3)'
|
|
1885
|
+
"""
|
|
1886
|
+
if formatvarargs is None:
|
|
1887
|
+
formatvarargs = lambda name: '*' + name
|
|
1888
|
+
if formatvarkw is None:
|
|
1889
|
+
formatvarkw = lambda name: '**' + name
|
|
1890
|
+
if formatvalue is None:
|
|
1891
|
+
formatvalue = lambda value: '=' + repr(value)
|
|
1892
|
+
if formatreturns is None:
|
|
1893
|
+
formatreturns = lambda text: ' -> ' + text
|
|
1894
|
+
if formatannotation is None:
|
|
1895
|
+
formatannotation = _formatannotation
|
|
1896
|
+
|
|
1897
|
+
def formatargandannotation(arg):
|
|
1898
|
+
result = formatarg(arg)
|
|
1899
|
+
if arg in annotations:
|
|
1900
|
+
result += ': ' + formatannotation(annotations[arg])
|
|
1901
|
+
return result
|
|
1902
|
+
specs = []
|
|
1903
|
+
if defaults:
|
|
1904
|
+
firstdefault = len(args) - len(defaults)
|
|
1905
|
+
for i, arg in enumerate(args):
|
|
1906
|
+
spec = formatargandannotation(arg)
|
|
1907
|
+
if defaults and i >= firstdefault:
|
|
1908
|
+
spec = spec + formatvalue(defaults[i - firstdefault])
|
|
1909
|
+
specs.append(spec)
|
|
1910
|
+
if varargs is not None:
|
|
1911
|
+
specs.append(formatvarargs(formatargandannotation(varargs)))
|
|
1912
|
+
else:
|
|
1913
|
+
if kwonlyargs:
|
|
1914
|
+
specs.append('*')
|
|
1915
|
+
if kwonlyargs:
|
|
1916
|
+
for kwonlyarg in kwonlyargs:
|
|
1917
|
+
spec = formatargandannotation(kwonlyarg)
|
|
1918
|
+
if kwonlydefaults and kwonlyarg in kwonlydefaults:
|
|
1919
|
+
spec += formatvalue(kwonlydefaults[kwonlyarg])
|
|
1920
|
+
specs.append(spec)
|
|
1921
|
+
if varkw is not None:
|
|
1922
|
+
specs.append(formatvarkw(formatargandannotation(varkw)))
|
|
1923
|
+
result = '(' + ', '.join(specs) + ')'
|
|
1924
|
+
if 'return' in annotations:
|
|
1925
|
+
result += formatreturns(formatannotation(annotations['return']))
|
|
1926
|
+
return result
|
|
1927
|
+
|
|
1928
|
+
|
|
1929
|
+
def sage_getdef(obj, obj_name=''):
|
|
1930
|
+
r"""
|
|
1931
|
+
Return the definition header for any callable object.
|
|
1932
|
+
|
|
1933
|
+
INPUT:
|
|
1934
|
+
|
|
1935
|
+
- ``obj`` -- function
|
|
1936
|
+
- ``obj_name`` -- string (default: ``''``); prepended to the output
|
|
1937
|
+
|
|
1938
|
+
EXAMPLES::
|
|
1939
|
+
|
|
1940
|
+
sage: from sage.misc.sageinspect import sage_getdef
|
|
1941
|
+
sage: sage_getdef(identity_matrix) # needs sage.modules
|
|
1942
|
+
'(ring, n=0, sparse=False)'
|
|
1943
|
+
sage: sage_getdef(identity_matrix, 'identity_matrix') # needs sage.modules
|
|
1944
|
+
'identity_matrix(ring, n=0, sparse=False)'
|
|
1945
|
+
|
|
1946
|
+
Check that :issue:`6848` has been fixed::
|
|
1947
|
+
|
|
1948
|
+
sage: sage_getdef(RDF.random_element)
|
|
1949
|
+
'(min=-1, max=1)'
|
|
1950
|
+
|
|
1951
|
+
If an exception is generated, None is returned instead and the
|
|
1952
|
+
exception is suppressed.
|
|
1953
|
+
"""
|
|
1954
|
+
try:
|
|
1955
|
+
spec = sage_getargspec(obj)
|
|
1956
|
+
s = str(sage_formatargspec(*spec))
|
|
1957
|
+
s = s.strip('(').strip(')').strip()
|
|
1958
|
+
if s[:4] == 'self':
|
|
1959
|
+
s = s[4:]
|
|
1960
|
+
s = s.lstrip(',').strip()
|
|
1961
|
+
# for use with typesetting the definition with the notebook:
|
|
1962
|
+
# sometimes s contains "*args" or "**keywds", and the
|
|
1963
|
+
# asterisks confuse ReST/sphinx/docutils, so escape them:
|
|
1964
|
+
# change * to \*, and change ** to \**.
|
|
1965
|
+
return obj_name + '(' + s + ')'
|
|
1966
|
+
except (AttributeError, TypeError, ValueError):
|
|
1967
|
+
return '%s( [noargspec] )' % obj_name
|
|
1968
|
+
|
|
1969
|
+
|
|
1970
|
+
def _sage_getdoc_unformatted(obj):
|
|
1971
|
+
r"""
|
|
1972
|
+
Return the unformatted docstring associated to ``obj`` as a
|
|
1973
|
+
string.
|
|
1974
|
+
|
|
1975
|
+
If ``obj`` is a Cython object with an embedded position in its
|
|
1976
|
+
docstring, the embedded position is **not** stripped.
|
|
1977
|
+
|
|
1978
|
+
INPUT:
|
|
1979
|
+
|
|
1980
|
+
- ``obj`` -- a function, module, etc.: something with a docstring
|
|
1981
|
+
|
|
1982
|
+
EXAMPLES::
|
|
1983
|
+
|
|
1984
|
+
sage: from sage.misc.sageinspect import _sage_getdoc_unformatted
|
|
1985
|
+
sage: print(_sage_getdoc_unformatted(sage.rings.integer.Integer))
|
|
1986
|
+
Integer(x=None, base=0)
|
|
1987
|
+
File: ...sage/rings/integer.pyx (starting at line ...)
|
|
1988
|
+
<BLANKLINE>
|
|
1989
|
+
The :class:`Integer` class represents arbitrary precision
|
|
1990
|
+
integers. It derives from the :class:`Element` class, so
|
|
1991
|
+
integers can be used as ring elements anywhere in Sage.
|
|
1992
|
+
...
|
|
1993
|
+
|
|
1994
|
+
TESTS:
|
|
1995
|
+
|
|
1996
|
+
Test that we suppress useless built-in output (:issue:`3342`)::
|
|
1997
|
+
|
|
1998
|
+
sage: from sage.misc.sageinspect import _sage_getdoc_unformatted
|
|
1999
|
+
sage: _sage_getdoc_unformatted(isinstance.__class__)
|
|
2000
|
+
''
|
|
2001
|
+
|
|
2002
|
+
"""
|
|
2003
|
+
if obj is None:
|
|
2004
|
+
return ''
|
|
2005
|
+
r = obj.__doc__
|
|
2006
|
+
|
|
2007
|
+
# Check if the __doc__ attribute was actually a string, and
|
|
2008
|
+
# not a 'getset_descriptor' or similar.
|
|
2009
|
+
if isinstance(r, str):
|
|
2010
|
+
return r
|
|
2011
|
+
else:
|
|
2012
|
+
# Not a string of any kind
|
|
2013
|
+
return ''
|
|
2014
|
+
|
|
2015
|
+
|
|
2016
|
+
def sage_getdoc_original(obj):
|
|
2017
|
+
r"""
|
|
2018
|
+
Return the unformatted docstring associated to ``obj`` as a
|
|
2019
|
+
string.
|
|
2020
|
+
|
|
2021
|
+
If ``obj`` is a Cython object with an embedded position or signature in
|
|
2022
|
+
its docstring, the embedded information is stripped. If the stripped
|
|
2023
|
+
docstring is empty, then the stripped docstring of ``obj.__init__`` is
|
|
2024
|
+
returned instead.
|
|
2025
|
+
|
|
2026
|
+
Feed the results from this into the function
|
|
2027
|
+
:func:`sage.misc.sagedoc.format` for printing to the screen.
|
|
2028
|
+
|
|
2029
|
+
INPUT:
|
|
2030
|
+
|
|
2031
|
+
- ``obj`` -- a function, module, etc.: something with a docstring
|
|
2032
|
+
|
|
2033
|
+
EXAMPLES::
|
|
2034
|
+
|
|
2035
|
+
sage: from sage.misc.sageinspect import sage_getdoc_original
|
|
2036
|
+
|
|
2037
|
+
Here is a class that has its own docstring::
|
|
2038
|
+
|
|
2039
|
+
sage: print(sage_getdoc_original(sage.rings.integer.Integer))
|
|
2040
|
+
<BLANKLINE>
|
|
2041
|
+
The :class:`Integer` class represents arbitrary precision
|
|
2042
|
+
integers. It derives from the :class:`Element` class, so
|
|
2043
|
+
integers can be used as ring elements anywhere in Sage.
|
|
2044
|
+
...
|
|
2045
|
+
|
|
2046
|
+
If the class does not have a docstring, the docstring of the
|
|
2047
|
+
``__init__`` method is used, but not the ``__init__`` method
|
|
2048
|
+
of the base class (this was fixed in :issue:`24936`)::
|
|
2049
|
+
|
|
2050
|
+
sage: from sage.categories.category import Category
|
|
2051
|
+
sage: class A(Category):
|
|
2052
|
+
....: def __init__(self):
|
|
2053
|
+
....: '''The __init__ docstring'''
|
|
2054
|
+
sage: sage_getdoc_original(A)
|
|
2055
|
+
'The __init__ docstring'
|
|
2056
|
+
sage: class B(Category):
|
|
2057
|
+
....: pass
|
|
2058
|
+
sage: sage_getdoc_original(B)
|
|
2059
|
+
''
|
|
2060
|
+
|
|
2061
|
+
Old-style classes are supported::
|
|
2062
|
+
|
|
2063
|
+
sage: class OldStyleClass:
|
|
2064
|
+
....: def __init__(self):
|
|
2065
|
+
....: '''The __init__ docstring'''
|
|
2066
|
+
....: pass
|
|
2067
|
+
sage: print(sage_getdoc_original(OldStyleClass))
|
|
2068
|
+
The __init__ docstring
|
|
2069
|
+
|
|
2070
|
+
When there is no ``__init__`` method, we just get an empty string::
|
|
2071
|
+
|
|
2072
|
+
sage: class OldStyleClass:
|
|
2073
|
+
....: pass
|
|
2074
|
+
sage: sage_getdoc_original(OldStyleClass)
|
|
2075
|
+
''
|
|
2076
|
+
|
|
2077
|
+
If an instance of a class does not have its own docstring, the docstring
|
|
2078
|
+
of its class results::
|
|
2079
|
+
|
|
2080
|
+
sage: sage_getdoc_original(sage.plot.colors.aliceblue) == sage_getdoc_original(sage.plot.colors.Color) # needs sage.plot
|
|
2081
|
+
True
|
|
2082
|
+
"""
|
|
2083
|
+
# typ is the type corresponding to obj, which is obj itself if
|
|
2084
|
+
# that was a type or old-style class
|
|
2085
|
+
if isinstance(obj, type):
|
|
2086
|
+
typ = obj
|
|
2087
|
+
else:
|
|
2088
|
+
typ = type(obj)
|
|
2089
|
+
|
|
2090
|
+
s, argspec = _extract_embedded_signature(_sage_getdoc_unformatted(obj), typ.__name__)
|
|
2091
|
+
if s:
|
|
2092
|
+
pos = _extract_embedded_position(s)
|
|
2093
|
+
if pos is not None:
|
|
2094
|
+
s = pos[0]
|
|
2095
|
+
if not s:
|
|
2096
|
+
# The docstring of obj is empty. To get something, we want to use
|
|
2097
|
+
# the documentation of the __init__ method, but only if it belongs
|
|
2098
|
+
# to (the type of) obj.
|
|
2099
|
+
init = typ.__dict__.get("__init__")
|
|
2100
|
+
if init:
|
|
2101
|
+
return sage_getdoc_original(init)
|
|
2102
|
+
return s
|
|
2103
|
+
|
|
2104
|
+
|
|
2105
|
+
def sage_getdoc(obj, obj_name='', embedded=False):
|
|
2106
|
+
r"""
|
|
2107
|
+
Return the docstring associated to ``obj`` as a string.
|
|
2108
|
+
|
|
2109
|
+
If ``obj`` is a Cython object with an embedded position in its
|
|
2110
|
+
docstring, the embedded position is stripped.
|
|
2111
|
+
|
|
2112
|
+
The optional boolean argument ``embedded`` controls the
|
|
2113
|
+
string formatting. It is False by default.
|
|
2114
|
+
|
|
2115
|
+
INPUT:
|
|
2116
|
+
|
|
2117
|
+
- ``obj`` -- a function, module, etc.: something with a docstring
|
|
2118
|
+
|
|
2119
|
+
EXAMPLES::
|
|
2120
|
+
|
|
2121
|
+
sage: from sage.misc.sageinspect import sage_getdoc
|
|
2122
|
+
sage: sage_getdoc(identity_matrix)[87:124] # needs sage.modules
|
|
2123
|
+
'...the n x n identity matrix...'
|
|
2124
|
+
sage: def f(a, b, c, d=1): return a+b+c+d
|
|
2125
|
+
...
|
|
2126
|
+
sage: import functools
|
|
2127
|
+
sage: f1 = functools.partial(f, 1,c=2)
|
|
2128
|
+
sage: f.__doc__ = "original documentation"
|
|
2129
|
+
sage: f1.__doc__ = "specialised documentation"
|
|
2130
|
+
sage: sage_getdoc(f)
|
|
2131
|
+
'original documentation\n'
|
|
2132
|
+
sage: sage_getdoc(f1)
|
|
2133
|
+
'specialised documentation\n'
|
|
2134
|
+
"""
|
|
2135
|
+
import sage.misc.sagedoc
|
|
2136
|
+
if obj is None:
|
|
2137
|
+
return ''
|
|
2138
|
+
r = sage_getdoc_original(obj)
|
|
2139
|
+
s = sage.misc.sagedoc.format(r, embedded=embedded)
|
|
2140
|
+
f = sage_getfile(obj)
|
|
2141
|
+
if f and os.path.exists(f):
|
|
2142
|
+
from sage.doctest.control import skipfile
|
|
2143
|
+
skip = skipfile(f)
|
|
2144
|
+
if isinstance(skip, str):
|
|
2145
|
+
warn = """WARNING: the enclosing module is marked '{}',
|
|
2146
|
+
so doctests may not pass.""".format(skip)
|
|
2147
|
+
s = warn + "\n\n" + s
|
|
2148
|
+
|
|
2149
|
+
# Fix object naming
|
|
2150
|
+
if obj_name != '':
|
|
2151
|
+
i = obj_name.find('.')
|
|
2152
|
+
if i != -1:
|
|
2153
|
+
obj_name = obj_name[:i]
|
|
2154
|
+
s = s.replace('self.', '%s.' % obj_name)
|
|
2155
|
+
|
|
2156
|
+
return s
|
|
2157
|
+
|
|
2158
|
+
|
|
2159
|
+
def sage_getsource(obj):
|
|
2160
|
+
r"""
|
|
2161
|
+
Return the source code associated to obj as a string, or None.
|
|
2162
|
+
|
|
2163
|
+
INPUT:
|
|
2164
|
+
|
|
2165
|
+
- ``obj`` -- function, etc.
|
|
2166
|
+
|
|
2167
|
+
EXAMPLES::
|
|
2168
|
+
|
|
2169
|
+
sage: from sage.misc.sageinspect import sage_getsource
|
|
2170
|
+
sage: sage_getsource(identity_matrix)[19:60] # needs sage.modules
|
|
2171
|
+
'identity_matrix(ring, n=0, sparse=False):'
|
|
2172
|
+
sage: sage_getsource(identity_matrix)[19:60] # needs sage.modules
|
|
2173
|
+
'identity_matrix(ring, n=0, sparse=False):'
|
|
2174
|
+
"""
|
|
2175
|
+
# First we should check if the object has a _sage_src_
|
|
2176
|
+
# method. If it does, we just return the output from
|
|
2177
|
+
# that. This is useful for getting pexpect interface
|
|
2178
|
+
# elements to behave similar to regular Python objects
|
|
2179
|
+
# with respect to introspection.
|
|
2180
|
+
try:
|
|
2181
|
+
return obj._sage_src_()
|
|
2182
|
+
except (AttributeError, TypeError):
|
|
2183
|
+
pass
|
|
2184
|
+
|
|
2185
|
+
t = sage_getsourcelines(obj)
|
|
2186
|
+
if not t:
|
|
2187
|
+
return None
|
|
2188
|
+
(source_lines, lineno) = t
|
|
2189
|
+
return ''.join(source_lines)
|
|
2190
|
+
|
|
2191
|
+
|
|
2192
|
+
def _sage_getsourcelines_name_with_dot(obj):
|
|
2193
|
+
r"""
|
|
2194
|
+
Get the source lines of an object whose name
|
|
2195
|
+
contains a dot and whose source lines can not
|
|
2196
|
+
be obtained by different methods.
|
|
2197
|
+
|
|
2198
|
+
EXAMPLES::
|
|
2199
|
+
|
|
2200
|
+
sage: C = Rings()
|
|
2201
|
+
sage: from sage.misc.sageinspect import sage_getsource
|
|
2202
|
+
sage: print(sage_getsource(C.parent_class)) #indirect doctest
|
|
2203
|
+
class ParentMethods:
|
|
2204
|
+
...
|
|
2205
|
+
Return the Lie bracket `[x, y] = x y - y x` of `x` and `y`.
|
|
2206
|
+
...
|
|
2207
|
+
|
|
2208
|
+
TESTS:
|
|
2209
|
+
|
|
2210
|
+
The following was fixed in :issue:`16309`::
|
|
2211
|
+
|
|
2212
|
+
sage: # needs sage.misc.cython
|
|
2213
|
+
sage: cython(
|
|
2214
|
+
....: '''
|
|
2215
|
+
....: class A:
|
|
2216
|
+
....: def __init__(self):
|
|
2217
|
+
....: "some init doc"
|
|
2218
|
+
....: pass
|
|
2219
|
+
....: class B:
|
|
2220
|
+
....: "some class doc"
|
|
2221
|
+
....: class A(A):
|
|
2222
|
+
....: pass
|
|
2223
|
+
....: ''')
|
|
2224
|
+
sage: B.A.__name__
|
|
2225
|
+
'A'
|
|
2226
|
+
sage: B.A.__qualname__
|
|
2227
|
+
'B.A'
|
|
2228
|
+
sage: sage_getsource(B.A)
|
|
2229
|
+
' class A(A):\n pass\n\n'
|
|
2230
|
+
|
|
2231
|
+
Note that for this example to work, it is essential that the class ``B``
|
|
2232
|
+
has a docstring. Otherwise, the code of ``B`` could not be found (Cython
|
|
2233
|
+
inserts embedding information into the docstring) and thus the code of
|
|
2234
|
+
``B.A`` couldn't be found either.
|
|
2235
|
+
"""
|
|
2236
|
+
# First, split the name:
|
|
2237
|
+
if '.' in obj.__name__:
|
|
2238
|
+
splitted_name = obj.__name__.split('.')
|
|
2239
|
+
elif hasattr(obj, '__qualname__'):
|
|
2240
|
+
splitted_name = obj.__qualname__.split('.')
|
|
2241
|
+
else:
|
|
2242
|
+
splitted_name = obj.__name__
|
|
2243
|
+
path = obj.__module__.split('.')+splitted_name[:-1]
|
|
2244
|
+
name = splitted_name[-1]
|
|
2245
|
+
try:
|
|
2246
|
+
M = __import__(path.pop(0))
|
|
2247
|
+
except ImportError:
|
|
2248
|
+
try:
|
|
2249
|
+
B = obj.__base__
|
|
2250
|
+
if B is None:
|
|
2251
|
+
raise AttributeError
|
|
2252
|
+
except AttributeError:
|
|
2253
|
+
raise OSError("could not get source code")
|
|
2254
|
+
return sage_getsourcelines(B)
|
|
2255
|
+
# M should just be the top-most module.
|
|
2256
|
+
# Hence, normally it is just 'sage'
|
|
2257
|
+
try:
|
|
2258
|
+
while path:
|
|
2259
|
+
M = getattr(M, path.pop(0))
|
|
2260
|
+
except AttributeError:
|
|
2261
|
+
try:
|
|
2262
|
+
B = obj.__base__
|
|
2263
|
+
if B is None:
|
|
2264
|
+
raise AttributeError
|
|
2265
|
+
except AttributeError:
|
|
2266
|
+
raise OSError("could not get source code")
|
|
2267
|
+
return sage_getsourcelines(B)
|
|
2268
|
+
|
|
2269
|
+
lines, base_lineno = sage_getsourcelines(M)
|
|
2270
|
+
# the rest of the function is copied from
|
|
2271
|
+
# inspect.findsource
|
|
2272
|
+
if not lines:
|
|
2273
|
+
raise OSError('could not get source code')
|
|
2274
|
+
|
|
2275
|
+
if inspect.ismodule(obj):
|
|
2276
|
+
return lines, base_lineno
|
|
2277
|
+
|
|
2278
|
+
if inspect.isclass(obj):
|
|
2279
|
+
pat = re.compile(r'^(\s*)class\s*' + name + r'\b')
|
|
2280
|
+
# make some effort to find the best matching class definition:
|
|
2281
|
+
# use the one with the least indentation, which is the one
|
|
2282
|
+
# that's most probably not inside a function definition.
|
|
2283
|
+
candidates = []
|
|
2284
|
+
for i in range(len(lines)):
|
|
2285
|
+
match = pat.match(lines[i])
|
|
2286
|
+
if match:
|
|
2287
|
+
# if it's at toplevel, it's already the best one
|
|
2288
|
+
if lines[i][0] == 'c':
|
|
2289
|
+
return inspect.getblock(lines[i:]), i+base_lineno
|
|
2290
|
+
# else add whitespace to candidate list
|
|
2291
|
+
candidates.append((match.group(1), i))
|
|
2292
|
+
if candidates:
|
|
2293
|
+
# this will sort by whitespace, and by line number,
|
|
2294
|
+
# less whitespace first
|
|
2295
|
+
candidates.sort()
|
|
2296
|
+
return inspect.getblock(lines[candidates[0][1]:]), candidates[0][1]+base_lineno
|
|
2297
|
+
else:
|
|
2298
|
+
raise OSError('could not find class definition')
|
|
2299
|
+
|
|
2300
|
+
if inspect.ismethod(obj):
|
|
2301
|
+
obj = obj.__func__
|
|
2302
|
+
if is_function_or_cython_function(obj):
|
|
2303
|
+
obj = obj.__code__
|
|
2304
|
+
if inspect.istraceback(obj):
|
|
2305
|
+
obj = obj.tb_frame
|
|
2306
|
+
if inspect.isframe(obj):
|
|
2307
|
+
obj = obj.f_code
|
|
2308
|
+
if inspect.iscode(obj):
|
|
2309
|
+
if not hasattr(obj, 'co_firstlineno'):
|
|
2310
|
+
raise OSError('could not find function definition')
|
|
2311
|
+
pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
|
|
2312
|
+
pmatch = pat.match
|
|
2313
|
+
# fperez - fix: sometimes, co_firstlineno can give a number larger than
|
|
2314
|
+
# the length of lines, which causes an error. Safeguard against that.
|
|
2315
|
+
lnum = min(obj.co_firstlineno, len(lines)) - 1
|
|
2316
|
+
while lnum > 0:
|
|
2317
|
+
if pmatch(lines[lnum]):
|
|
2318
|
+
break
|
|
2319
|
+
lnum -= 1
|
|
2320
|
+
|
|
2321
|
+
return inspect.getblock(lines[lnum:]), lnum+base_lineno
|
|
2322
|
+
raise OSError('could not find code object')
|
|
2323
|
+
|
|
2324
|
+
|
|
2325
|
+
def sage_getsourcelines(obj):
|
|
2326
|
+
r"""
|
|
2327
|
+
Return a pair ([source_lines], starting line number) of the source
|
|
2328
|
+
code associated to obj, or None.
|
|
2329
|
+
|
|
2330
|
+
INPUT:
|
|
2331
|
+
|
|
2332
|
+
- ``obj`` -- function, etc.
|
|
2333
|
+
|
|
2334
|
+
OUTPUT:
|
|
2335
|
+
|
|
2336
|
+
(source_lines, lineno) or None: ``source_lines`` is a list of
|
|
2337
|
+
strings, and ``lineno`` is an integer.
|
|
2338
|
+
|
|
2339
|
+
EXAMPLES::
|
|
2340
|
+
|
|
2341
|
+
sage: from sage.misc.sageinspect import sage_getsourcelines
|
|
2342
|
+
|
|
2343
|
+
sage: # needs sage.modules
|
|
2344
|
+
sage: from sage.matrix.constructor import matrix
|
|
2345
|
+
sage: sage_getsourcelines(matrix)[1]
|
|
2346
|
+
22
|
|
2347
|
+
sage: sage_getsourcelines(matrix)[0][0]
|
|
2348
|
+
'def matrix(*args, **kwds):\n'
|
|
2349
|
+
|
|
2350
|
+
Some classes customize this using a ``_sage_src_lines_`` method,
|
|
2351
|
+
which gives the source lines of a class instance, but not the class
|
|
2352
|
+
itself. We demonstrate this for :class:`CachedFunction`::
|
|
2353
|
+
|
|
2354
|
+
sage: # needs sage.combinat
|
|
2355
|
+
sage: cachedfib = cached_function(fibonacci)
|
|
2356
|
+
sage: sage_getsourcelines(cachedfib)[0][0]
|
|
2357
|
+
"def fibonacci(n, algorithm='pari') -> Integer:\n"
|
|
2358
|
+
sage: sage_getsourcelines(type(cachedfib))[0][0]
|
|
2359
|
+
'cdef class CachedFunction():\n'
|
|
2360
|
+
|
|
2361
|
+
TESTS::
|
|
2362
|
+
|
|
2363
|
+
sage: # needs sage.misc.cython
|
|
2364
|
+
sage: cython('''cpdef test_funct(x, y): return''')
|
|
2365
|
+
sage: sage_getsourcelines(test_funct)
|
|
2366
|
+
(['cpdef test_funct(x, y): return\n'], 1)
|
|
2367
|
+
|
|
2368
|
+
The following tests that an instance of ``functools.partial`` is correctly
|
|
2369
|
+
dealt with (see :issue:`9976`)::
|
|
2370
|
+
|
|
2371
|
+
sage: from sage.tests.functools_partial_src import test_func
|
|
2372
|
+
sage: sage_getsourcelines(test_func)
|
|
2373
|
+
(['def base(x):\n',
|
|
2374
|
+
...
|
|
2375
|
+
' return x\n'], 9)
|
|
2376
|
+
|
|
2377
|
+
Here are some cases that were covered in :issue:`11298`;
|
|
2378
|
+
note that line numbers may easily change, and therefore we do
|
|
2379
|
+
not test them::
|
|
2380
|
+
|
|
2381
|
+
sage: P.<x,y> = QQ[]
|
|
2382
|
+
sage: I = P*[x,y]
|
|
2383
|
+
sage: sage_getsourcelines(P) # needs sage.libs.singular
|
|
2384
|
+
(['cdef class MPolynomialRing_libsingular(MPolynomialRing_base):\n',
|
|
2385
|
+
'\n',
|
|
2386
|
+
' def __cinit__(self):\n',
|
|
2387
|
+
...)
|
|
2388
|
+
sage: sage_getsourcelines(I) # needs sage.libs.singular
|
|
2389
|
+
([...'class MPolynomialIdeal(MPolynomialIdeal_singular_repr,\n',
|
|
2390
|
+
...)
|
|
2391
|
+
sage: x = var('x') # needs sage.symbolic
|
|
2392
|
+
sage: lines, lineno = sage_getsourcelines(x); lines[0:5] # needs sage.symbolic
|
|
2393
|
+
['cdef class Expression(...):\n',
|
|
2394
|
+
'\n',
|
|
2395
|
+
' cdef GEx _gobj\n',
|
|
2396
|
+
'\n',
|
|
2397
|
+
' cpdef object pyobject(self):\n']
|
|
2398
|
+
sage: lines[-1] # last line # needs sage.symbolic
|
|
2399
|
+
' return S\n'
|
|
2400
|
+
|
|
2401
|
+
We show some enhancements provided by :issue:`11768`. First, we
|
|
2402
|
+
use a dummy parent class that has defined an element class by a
|
|
2403
|
+
nested class definition::
|
|
2404
|
+
|
|
2405
|
+
sage: from sage.misc.test_nested_class import TestNestedParent
|
|
2406
|
+
sage: from sage.misc.sageinspect import sage_getsource
|
|
2407
|
+
sage: P = TestNestedParent()
|
|
2408
|
+
sage: E = P.element_class
|
|
2409
|
+
sage: E.__bases__
|
|
2410
|
+
(<class 'sage.misc.test_nested_class.TestNestedParent.Element'>,
|
|
2411
|
+
<class 'sage.categories.sets_cat.Sets.element_class'>)
|
|
2412
|
+
sage: print(sage_getsource(E))
|
|
2413
|
+
class Element:
|
|
2414
|
+
"This is a dummy element class"
|
|
2415
|
+
pass
|
|
2416
|
+
sage: print(sage_getsource(P))
|
|
2417
|
+
class TestNestedParent(UniqueRepresentation, Parent):
|
|
2418
|
+
...
|
|
2419
|
+
class Element:
|
|
2420
|
+
"This is a dummy element class"
|
|
2421
|
+
pass
|
|
2422
|
+
|
|
2423
|
+
Here is another example that relies on a nested class definition
|
|
2424
|
+
in the background::
|
|
2425
|
+
|
|
2426
|
+
sage: C = AdditiveMagmas()
|
|
2427
|
+
sage: HC = C.Homsets()
|
|
2428
|
+
sage: sage_getsourcelines(HC)
|
|
2429
|
+
([' class Homsets(HomsetsCategory):\n', ...], ...)
|
|
2430
|
+
|
|
2431
|
+
Testing against a bug that has occurred during work on :issue:`11768`::
|
|
2432
|
+
|
|
2433
|
+
sage: P.<x,y> = QQ[]
|
|
2434
|
+
sage: I = P*[x,y]
|
|
2435
|
+
sage: sage_getsourcelines(I) # needs sage.libs.singular
|
|
2436
|
+
([...'class MPolynomialIdeal(MPolynomialIdeal_singular_repr,\n',
|
|
2437
|
+
' MPolynomialIdeal_macaulay2_repr,\n',
|
|
2438
|
+
' MPolynomialIdeal_magma_repr,\n',
|
|
2439
|
+
' Ideal_generic):\n',
|
|
2440
|
+
' def __init__(self, ring, gens, coerce=True...):\n',
|
|
2441
|
+
...)
|
|
2442
|
+
"""
|
|
2443
|
+
# First try the method _sage_src_lines_(), which is meant to give
|
|
2444
|
+
# the source lines of an object (not of its type!).
|
|
2445
|
+
try:
|
|
2446
|
+
sage_src_lines = obj._sage_src_lines_
|
|
2447
|
+
except AttributeError:
|
|
2448
|
+
pass
|
|
2449
|
+
else:
|
|
2450
|
+
try:
|
|
2451
|
+
return sage_src_lines()
|
|
2452
|
+
except (NotImplementedError, TypeError):
|
|
2453
|
+
# NotImplementedError can be raised by _sage_src_lines_()
|
|
2454
|
+
# to indicate that it didn't find the source lines.
|
|
2455
|
+
#
|
|
2456
|
+
# TypeError can happen when obj is a type and
|
|
2457
|
+
# obj._sage_src_lines_ is an unbound method. In this case,
|
|
2458
|
+
# we don't want to use _sage_src_lines_(), we just want to
|
|
2459
|
+
# get the source of the type itself.
|
|
2460
|
+
pass
|
|
2461
|
+
|
|
2462
|
+
# Check if we deal with an instance
|
|
2463
|
+
if isclassinstance(obj):
|
|
2464
|
+
if isinstance(obj, functools.partial):
|
|
2465
|
+
return sage_getsourcelines(obj.func)
|
|
2466
|
+
else:
|
|
2467
|
+
return sage_getsourcelines(obj.__class__)
|
|
2468
|
+
|
|
2469
|
+
# First, we deal with nested classes. Their name contains a dot, and we
|
|
2470
|
+
# have a special function for that purpose.
|
|
2471
|
+
# This is the case for ParentMethods of categories, for example.
|
|
2472
|
+
if (inspect.isclass(obj) and
|
|
2473
|
+
('.' in obj.__name__ or '.' in getattr(obj, '__qualname__', ''))):
|
|
2474
|
+
return _sage_getsourcelines_name_with_dot(obj)
|
|
2475
|
+
|
|
2476
|
+
# Next, we try _sage_getdoc_unformatted()
|
|
2477
|
+
d = _sage_getdoc_unformatted(obj)
|
|
2478
|
+
pos = _extract_embedded_position(d)
|
|
2479
|
+
if pos is None:
|
|
2480
|
+
try:
|
|
2481
|
+
return inspect.getsourcelines(obj)
|
|
2482
|
+
except (OSError, TypeError) as err:
|
|
2483
|
+
if hasattr(obj, '__init__'):
|
|
2484
|
+
d = _sage_getdoc_unformatted(obj.__init__)
|
|
2485
|
+
pos = _extract_embedded_position(d)
|
|
2486
|
+
if pos is None:
|
|
2487
|
+
if inspect.isclass(obj):
|
|
2488
|
+
try:
|
|
2489
|
+
B = obj.__base__
|
|
2490
|
+
except AttributeError:
|
|
2491
|
+
B = None
|
|
2492
|
+
if B is not None and B is not obj:
|
|
2493
|
+
return sage_getsourcelines(B)
|
|
2494
|
+
if obj.__class__ != type:
|
|
2495
|
+
return sage_getsourcelines(obj.__class__)
|
|
2496
|
+
raise err
|
|
2497
|
+
|
|
2498
|
+
(orig, filename, lineno) = pos
|
|
2499
|
+
try:
|
|
2500
|
+
with open(filename) as f:
|
|
2501
|
+
source_lines = f.readlines()
|
|
2502
|
+
except OSError:
|
|
2503
|
+
try:
|
|
2504
|
+
from sage.misc.temporary_file import spyx_tmp
|
|
2505
|
+
raw_name = filename.split('/')[-1]
|
|
2506
|
+
newname = os.path.join(spyx_tmp(), '_'.join(raw_name.split('_')[:-1]), raw_name)
|
|
2507
|
+
with open(newname) as f:
|
|
2508
|
+
source_lines = f.readlines()
|
|
2509
|
+
except OSError:
|
|
2510
|
+
return None
|
|
2511
|
+
|
|
2512
|
+
# It is possible that the source lines belong to the __init__ method,
|
|
2513
|
+
# rather than to the class. So, we try to look back and find the class
|
|
2514
|
+
# definition.
|
|
2515
|
+
first_line = source_lines[lineno-1]
|
|
2516
|
+
leading_blanks = len(first_line)-len(first_line.lstrip())
|
|
2517
|
+
if first_line.lstrip().startswith('def ') and "__init__" in first_line and obj.__name__ != '__init__':
|
|
2518
|
+
ignore = False
|
|
2519
|
+
double_quote = None
|
|
2520
|
+
for lnb in range(lineno, 0, -1):
|
|
2521
|
+
new_first_line = source_lines[lnb-1]
|
|
2522
|
+
nfl_strip = new_first_line.lstrip()
|
|
2523
|
+
if nfl_strip.startswith('"""'):
|
|
2524
|
+
if double_quote is None:
|
|
2525
|
+
double_quote = True
|
|
2526
|
+
if double_quote:
|
|
2527
|
+
ignore = not ignore
|
|
2528
|
+
elif nfl_strip.startswith("'''"):
|
|
2529
|
+
if double_quote is None:
|
|
2530
|
+
double_quote = False
|
|
2531
|
+
if double_quote is False:
|
|
2532
|
+
ignore = not ignore
|
|
2533
|
+
if ignore:
|
|
2534
|
+
continue
|
|
2535
|
+
if len(new_first_line)-len(nfl_strip) < leading_blanks and nfl_strip:
|
|
2536
|
+
# We are not inside a doc string. So, if the indentation
|
|
2537
|
+
# is less than the indentation of the __init__ method
|
|
2538
|
+
# then we must be at the class definition!
|
|
2539
|
+
lineno = lnb
|
|
2540
|
+
break
|
|
2541
|
+
return _extract_source(source_lines, lineno), lineno
|
|
2542
|
+
|
|
2543
|
+
|
|
2544
|
+
def sage_getvariablename(self, omit_underscore_names=True):
|
|
2545
|
+
"""
|
|
2546
|
+
Attempt to get the name of a Sage object.
|
|
2547
|
+
|
|
2548
|
+
INPUT:
|
|
2549
|
+
|
|
2550
|
+
- ``self`` -- any object
|
|
2551
|
+
|
|
2552
|
+
- ``omit_underscore_names`` -- boolean (default: ``True``)
|
|
2553
|
+
|
|
2554
|
+
OUTPUT:
|
|
2555
|
+
|
|
2556
|
+
If the user has assigned an object ``obj`` to a variable name,
|
|
2557
|
+
then return that variable name. If several variables point to
|
|
2558
|
+
``obj``, return a sorted list of those names. If
|
|
2559
|
+
``omit_underscore_names`` is ``True`` (the default) then omit names
|
|
2560
|
+
starting with an underscore "_".
|
|
2561
|
+
|
|
2562
|
+
EXAMPLES::
|
|
2563
|
+
|
|
2564
|
+
sage: # needs sage.modules
|
|
2565
|
+
sage: from sage.misc.sageinspect import sage_getvariablename
|
|
2566
|
+
sage: A = random_matrix(ZZ, 100)
|
|
2567
|
+
sage: sage_getvariablename(A)
|
|
2568
|
+
'A'
|
|
2569
|
+
sage: B = A
|
|
2570
|
+
sage: sage_getvariablename(A)
|
|
2571
|
+
['A', 'B']
|
|
2572
|
+
|
|
2573
|
+
If an object is not assigned to a variable, an empty list is returned::
|
|
2574
|
+
|
|
2575
|
+
sage: sage_getvariablename(random_matrix(ZZ, 60)) # needs sage.modules
|
|
2576
|
+
[]
|
|
2577
|
+
"""
|
|
2578
|
+
# This is a modified version of code taken from
|
|
2579
|
+
# https://web.archive.org/web/20100416095847/http://pythonic.pocoo.org/2009/5/30/finding-objects-names
|
|
2580
|
+
# written by Georg Brandl.
|
|
2581
|
+
result = []
|
|
2582
|
+
for frame in inspect.stack():
|
|
2583
|
+
for name, obj in frame[0].f_globals.items():
|
|
2584
|
+
if obj is self:
|
|
2585
|
+
result.append(name)
|
|
2586
|
+
if len(result) == 1:
|
|
2587
|
+
return result[0]
|
|
2588
|
+
else:
|
|
2589
|
+
return sorted(result)
|
|
2590
|
+
|
|
2591
|
+
|
|
2592
|
+
__internal_teststring = '''
|
|
2593
|
+
import os # 1
|
|
2594
|
+
# preceding comment not include # 2
|
|
2595
|
+
def test1(a, b=2): # 3
|
|
2596
|
+
if a: # 4
|
|
2597
|
+
return 1 # 5
|
|
2598
|
+
return b # 6
|
|
2599
|
+
# intervening comment not included # 7
|
|
2600
|
+
class test2(): # 8
|
|
2601
|
+
pass # 9
|
|
2602
|
+
# indented comment not included # 10
|
|
2603
|
+
# trailing comment not included # 11
|
|
2604
|
+
def test3(b, # 12
|
|
2605
|
+
a=2): # 13
|
|
2606
|
+
pass # EOF # 14'''
|
|
2607
|
+
|
|
2608
|
+
|
|
2609
|
+
def __internal_tests():
|
|
2610
|
+
r"""
|
|
2611
|
+
Test internals of the sageinspect module.
|
|
2612
|
+
|
|
2613
|
+
EXAMPLES::
|
|
2614
|
+
|
|
2615
|
+
sage: from sage.misc.sageinspect import *
|
|
2616
|
+
sage: from sage.misc.sageinspect import _extract_source, _extract_embedded_position, _sage_getargspec_cython, __internal_teststring
|
|
2617
|
+
|
|
2618
|
+
If docstring is None, nothing bad happens::
|
|
2619
|
+
|
|
2620
|
+
sage: sage_getdoc(None)
|
|
2621
|
+
''
|
|
2622
|
+
|
|
2623
|
+
sage: import sage.all__sagemath_objects
|
|
2624
|
+
sage: sage_getsource(sage.all__sagemath_objects)
|
|
2625
|
+
"...all..."
|
|
2626
|
+
|
|
2627
|
+
A cython function with default arguments (one of which is a string)::
|
|
2628
|
+
|
|
2629
|
+
sage: sage_getdef(sage.rings.integer.Integer.factor, obj_name='factor')
|
|
2630
|
+
"factor(algorithm='pari', proof=None, limit=None, int_=False, verbose=0)"
|
|
2631
|
+
|
|
2632
|
+
This used to be problematic, but was fixed in :issue:`10094`::
|
|
2633
|
+
|
|
2634
|
+
sage: sage_getsource(sage.rings.integer.Integer.__init__)
|
|
2635
|
+
' def __init__(self, x=None, base=0):\n...'
|
|
2636
|
+
sage: sage_getdef(sage.rings.integer.Integer.__init__, obj_name='__init__')
|
|
2637
|
+
'__init__(x=None, base=0)'
|
|
2638
|
+
|
|
2639
|
+
Test _extract_source with some likely configurations, including no trailing
|
|
2640
|
+
newline at the end of the file::
|
|
2641
|
+
|
|
2642
|
+
sage: s = __internal_teststring.strip()
|
|
2643
|
+
sage: es = lambda ls, l: ''.join(_extract_source(ls, l)).rstrip()
|
|
2644
|
+
|
|
2645
|
+
sage: print(es(s, 3))
|
|
2646
|
+
def test1(a, b=2): # 3
|
|
2647
|
+
if a: # 4
|
|
2648
|
+
return 1 # 5
|
|
2649
|
+
return b # 6
|
|
2650
|
+
|
|
2651
|
+
sage: print(es(s, 8))
|
|
2652
|
+
class test2(): # 8
|
|
2653
|
+
pass # 9
|
|
2654
|
+
|
|
2655
|
+
sage: print(es(s, 12))
|
|
2656
|
+
def test3(b, # 12
|
|
2657
|
+
a=2): # 13
|
|
2658
|
+
pass # EOF # 14
|
|
2659
|
+
|
|
2660
|
+
Test _sage_getargspec_cython with multiple default arguments and a type::
|
|
2661
|
+
|
|
2662
|
+
sage: _sage_getargspec_cython("def init(self, x=None, base=0):")
|
|
2663
|
+
FullArgSpec(args=['self', 'x', 'base'], varargs=None, varkw=None, defaults=(None, 0), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
2664
|
+
sage: _sage_getargspec_cython("def __init__(self, x=None, base=0):")
|
|
2665
|
+
FullArgSpec(args=['self', 'x', 'base'], varargs=None, varkw=None, defaults=(None, 0), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
2666
|
+
sage: _sage_getargspec_cython("def __init__(self, x=None, unsigned int base=0, **keys):")
|
|
2667
|
+
FullArgSpec(args=['self', 'x', 'base'], varargs=None, varkw='keys', defaults=(None, 0), kwonlyargs=[], kwonlydefaults=None, annotations={})
|
|
2668
|
+
|
|
2669
|
+
Test _extract_embedded_position:
|
|
2670
|
+
|
|
2671
|
+
We cannot test the filename since it depends on the installation location.
|
|
2672
|
+
|
|
2673
|
+
Make sure things work with no trailing newline::
|
|
2674
|
+
|
|
2675
|
+
sage: _extract_embedded_position('File: sage/rings/rational.pyx (starting at line 1080)')
|
|
2676
|
+
('', '.../rational.pyx', 1080)
|
|
2677
|
+
|
|
2678
|
+
And with a trailing newline::
|
|
2679
|
+
|
|
2680
|
+
sage: s = 'File: sage/rings/rational.pyx (starting at line 1080)\n'
|
|
2681
|
+
sage: _extract_embedded_position(s)
|
|
2682
|
+
('', '.../rational.pyx', 1080)
|
|
2683
|
+
|
|
2684
|
+
And with an original docstring::
|
|
2685
|
+
|
|
2686
|
+
sage: s = 'File: sage/rings/rational.pyx (starting at line 1080)\noriginal'
|
|
2687
|
+
sage: _extract_embedded_position(s)
|
|
2688
|
+
('original', '.../rational.pyx', 1080)
|
|
2689
|
+
|
|
2690
|
+
And with a complicated original docstring::
|
|
2691
|
+
|
|
2692
|
+
sage: s = 'File: sage/rings/rational.pyx (starting at line 1080)\n\n\noriginal test\noriginal'
|
|
2693
|
+
sage: _extract_embedded_position(s)
|
|
2694
|
+
('\n\noriginal test\noriginal', ..., 1080)
|
|
2695
|
+
|
|
2696
|
+
sage: s = 'no embedded position'
|
|
2697
|
+
sage: _extract_embedded_position(s) is None
|
|
2698
|
+
True
|
|
2699
|
+
"""
|
|
2700
|
+
|
|
2701
|
+
|
|
2702
|
+
def find_object_modules(obj):
|
|
2703
|
+
r"""
|
|
2704
|
+
Return a dictionary whose keys are the names of the modules where ``obj``
|
|
2705
|
+
appear and the value at a given module name is the list of names that
|
|
2706
|
+
``obj`` have in that module.
|
|
2707
|
+
|
|
2708
|
+
It is very unlikely that the output dictionary has several keys except when
|
|
2709
|
+
``obj`` is an instance of a class.
|
|
2710
|
+
|
|
2711
|
+
EXAMPLES::
|
|
2712
|
+
|
|
2713
|
+
sage: from sage.misc.sageinspect import find_object_modules
|
|
2714
|
+
sage: find_object_modules(RR) # needs sage.rings.real_mpfr
|
|
2715
|
+
{'sage.rings.real_mpfr': ['RR']}
|
|
2716
|
+
sage: find_object_modules(ZZ)
|
|
2717
|
+
{'sage.rings.integer_ring': ['Z', 'ZZ']}
|
|
2718
|
+
"""
|
|
2719
|
+
# see if the object is defined in its own module
|
|
2720
|
+
# might be wrong for class instances as the instantiation might appear
|
|
2721
|
+
# outside of the module !!
|
|
2722
|
+
module_name = None
|
|
2723
|
+
if isclassinstance(obj):
|
|
2724
|
+
module_name = obj.__class__.__module__
|
|
2725
|
+
elif hasattr(obj, '__module__') and obj.__module__:
|
|
2726
|
+
module_name = obj.__module__
|
|
2727
|
+
|
|
2728
|
+
if module_name:
|
|
2729
|
+
if module_name not in sys.modules:
|
|
2730
|
+
raise ValueError("this should never happen")
|
|
2731
|
+
d = sys.modules[module_name].__dict__
|
|
2732
|
+
matching = sorted(key for key in d if d[key] is obj)
|
|
2733
|
+
if matching:
|
|
2734
|
+
return {module_name: matching}
|
|
2735
|
+
|
|
2736
|
+
# otherwise, we parse all (already loaded) modules and hope to find
|
|
2737
|
+
# something
|
|
2738
|
+
module_to_obj = {}
|
|
2739
|
+
for module_name, module in sys.modules.items():
|
|
2740
|
+
if module_name != '__main__' and hasattr(module, '__dict__'):
|
|
2741
|
+
d = module.__dict__
|
|
2742
|
+
names = [key for key in d if d[key] is obj]
|
|
2743
|
+
if names:
|
|
2744
|
+
module_to_obj[module_name] = names
|
|
2745
|
+
|
|
2746
|
+
# if the object is an instance, we try to guess where it is defined
|
|
2747
|
+
if isclassinstance(obj):
|
|
2748
|
+
dec_pattern = re.compile(r"^(\w[\w0-9\_]*)\s*=", re.MULTILINE)
|
|
2749
|
+
module_to_obj2 = {}
|
|
2750
|
+
for module_name, obj_names in module_to_obj.items():
|
|
2751
|
+
module_to_obj2[module_name] = []
|
|
2752
|
+
try:
|
|
2753
|
+
src = sage_getsource(sys.modules[module_name])
|
|
2754
|
+
except TypeError:
|
|
2755
|
+
pass
|
|
2756
|
+
else:
|
|
2757
|
+
m = dec_pattern.search(src)
|
|
2758
|
+
while m:
|
|
2759
|
+
if m.group(1) in obj_names:
|
|
2760
|
+
module_to_obj2[module_name].append(m.group(1))
|
|
2761
|
+
m = dec_pattern.search(src, m.end())
|
|
2762
|
+
if not module_to_obj2[module_name]:
|
|
2763
|
+
del module_to_obj2[module_name]
|
|
2764
|
+
|
|
2765
|
+
if module_to_obj2:
|
|
2766
|
+
return module_to_obj2
|
|
2767
|
+
|
|
2768
|
+
return module_to_obj
|