Ticket #11768: trac11768_source_of_dynamic_class.patch

File trac11768_source_of_dynamic_class.patch, 11.7 KB (added by SimonKing, 8 years ago)

Get source lines of dynamic classes (e.g., parent_class of a category). Combined patch

  • sage/misc/nested_class_test.py

    # HG changeset patch
    # User Simon King <simon.king@uni-jena.de>
    # Date 1314878571 -7200
    # Node ID 30b9311c472448fa941edec22668f70f711889b1
    # Parent  bc54438bd5ca5a6923764a245c5fe79032f23990
    #11768: Find source lines of dynamic classes (e.g., parent_class of a category)
            Tests are independent of #12876
    
    diff --git a/sage/misc/nested_class_test.py b/sage/misc/nested_class_test.py
    a b  
    188188        pass
    189189
    190190CMeta = ALBMeta.CMeta
     191
     192class TestNestedParent(UniqueRepresentation, Parent):
     193    """
     194    This is a dummy for testing source inspection of nested classes.
     195
     196    EXAMPLES::
     197
     198        sage: from sage.misc.nested_class_test import TestNestedParent
     199        sage: from sage.misc.sageinspect import sage_getsource
     200        sage: P = TestNestedParent()
     201        sage: E = P.element_class
     202        sage: E.__bases__
     203        (<class sage.misc.nested_class_test.TestNestedParent.Element at ...>,
     204         <class 'sage.categories.sets_cat.Sets.element_class'>)
     205        sage: print sage_getsource(E)
     206            class Element:
     207                "This is a dummy element class"
     208                pass
     209
     210    """
     211    class Element:
     212        "This is a dummy element class"
     213        pass
     214
  • sage/misc/sageinspect.py

    diff --git a/sage/misc/sageinspect.py b/sage/misc/sageinspect.py
    a b  
    112112    sage: sage_getdef(str.find, 'find')
    113113    'find( [noargspec] )'
    114114
    115 By trac ticket #9976, introspection also works for interactively
     115By :trac:`9976`, introspection also works for interactively
    116116defined Cython code, and with rather tricky argument lines::
    117117
    118118    sage: cython('def foo(x, a=\')"\', b={not (2+1==3):\'bar\'}): return')
     
    879879        sage: sage_getfile(Sq)[-42:]
    880880        'sage/algebras/steenrod/steenrod_algebra.py'
    881881
    882     The following tests against some bugs fixed in trac ticket #9976::
     882    The following tests against some bugs fixed in :trac:`9976`::
    883883
    884884        sage: obj = sage.combinat.partition_algebra.SetPartitionsAk
    885885        sage: obj = sage.combinat.partition_algebra.SetPartitionsAk
    886886        sage: sage_getfile(obj)
    887887        '...sage/combinat/partition_algebra.py'
    888888
    889     And here is another bug, fixed in trac ticket #11298::
     889    And here is another bug, fixed in :trac:`11298`::
    890890
    891891        sage: P.<x,y> = QQ[]
    892892        sage: sage_getfile(P)
     
    970970        ArgSpec(args=['self', 'element'], varargs=None, keywords=None, defaults=None)
    971971
    972972    The following tests against various bugs that were fixed in
    973     trac ticket #9976::
     973    :trac:`9976`::
    974974
    975975        sage: from sage.rings.polynomial.real_roots import bernstein_polynomial_factory_ratlist
    976976        sage: sage_getargspec(bernstein_polynomial_factory_ratlist.coeffs_bitsize)
     
    998998
    999999    TESTS:
    10001000
    1001     By trac ticket #9976, rather complicated cases work. In the
     1001    By :trac:`9976`, rather complicated cases work. In the
    10021002    following example, we dynamically create an extension class
    10031003    that returns some source code, and the example shows that
    10041004    the source code is taken for granted, i.e., the argspec of
     
    11231123        sage: sage_getdef(identity_matrix, 'identity_matrix')
    11241124        'identity_matrix(ring, n=0, sparse=False)'
    11251125
    1126     Check that trac ticket #6848 has been fixed::
     1126    Check that :trac:`6848` has been fixed::
    11271127   
    11281128        sage: sage_getdef(RDF.random_element)
    11291129        '(min=-1, max=1)'
     
    13241324    (source_lines, lineno) = t
    13251325    return ''.join(source_lines)   
    13261326
     1327def _sage_getsourcelines_name_with_dot(object):
     1328    r"""
     1329    Get the source lines of an object whose name
     1330    contains a dot and whose source lines can not
     1331    be obtained by different methods.
     1332
     1333    EXAMPLES::
     1334
     1335        sage: C = Rings()
     1336        sage: from sage.misc.sageinspect import sage_getsource
     1337        sage: print sage_getsource(C.parent_class)  #indirect doctest
     1338        class ParentMethods:
     1339        ...
     1340                Returns the Lie bracket `[x, y] = x y - y x` of `x` and `y`.
     1341        ...
     1342
     1343    AUTHOR:
     1344
     1345    - Simon King (2011-09)
     1346    """
     1347    # First, split the name:
     1348    splitted_name = object.__name__.split('.')
     1349    path = object.__module__.split('.')+splitted_name[:-1]
     1350    name = splitted_name[-1]
     1351    try:
     1352        M = __import__(path.pop(0))
     1353    except ImportError:
     1354        try:
     1355            B = object.__base__
     1356            if B is None:
     1357                raise AttributeError
     1358        except AttributeError:
     1359            raise IOError, "could not get source code"
     1360        return sage_getsourcelines(B)
     1361    # M should just be the top-most module.
     1362    # Hence, normally it is just 'sage'
     1363    try:
     1364        while path:
     1365            M = getattr(M, path.pop(0))
     1366    except AttributeError:
     1367        try:
     1368            B = object.__base__
     1369            if B is None:
     1370                raise AttributeError
     1371        except AttributeError:
     1372            raise IOError, "could not get source code"
     1373        return sage_getsourcelines(B)
     1374
     1375    lines, base_lineno = sage_getsourcelines(M)
     1376    # the rest of the function is copied from
     1377    # inspect.findsource
     1378    if not lines:
     1379        raise IOError('could not get source code')
     1380
     1381    if inspect.ismodule(object):
     1382        return lines, base_lineno
     1383
     1384    if inspect.isclass(object):
     1385        pat = re.compile(r'^(\s*)class\s*' + name + r'\b')
     1386        # make some effort to find the best matching class definition:
     1387        # use the one with the least indentation, which is the one
     1388        # that's most probably not inside a function definition.
     1389        candidates = []
     1390        for i in range(len(lines)):
     1391            match = pat.match(lines[i])
     1392            if match:
     1393                # if it's at toplevel, it's already the best one
     1394                if lines[i][0] == 'c':
     1395                    return inspect.getblock(lines[i:]), i+base_lineno
     1396                # else add whitespace to candidate list
     1397                candidates.append((match.group(1), i))
     1398        if candidates:
     1399            # this will sort by whitespace, and by line number,
     1400            # less whitespace first
     1401            candidates.sort()
     1402            return inspect.getblock(lines[candidates[0][1]:]), candidates[0][1]+base_lineno
     1403        else:
     1404            raise IOError('could not find class definition')
     1405
     1406    if inspect.ismethod(object):
     1407        object = object.im_func
     1408    if inspect.isfunction(object):
     1409        object = object.func_code
     1410    if inspect.istraceback(object):
     1411        object = object.tb_frame
     1412    if inspect.isframe(object):
     1413        object = object.f_code
     1414    if inspect.iscode(object):
     1415        if not hasattr(object, 'co_firstlineno'):
     1416            raise IOError('could not find function definition')
     1417        pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
     1418        pmatch = pat.match
     1419        # fperez - fix: sometimes, co_firstlineno can give a number larger than
     1420        # the length of lines, which causes an error.  Safeguard against that.
     1421        lnum = min(object.co_firstlineno,len(lines))-1
     1422        while lnum > 0:
     1423            if pmatch(lines[lnum]): break
     1424            lnum -= 1
     1425
     1426        return inspect.getblock(lines[lnum:]), lnum+base_lineno
     1427    raise IOError('could not find code object')
     1428
     1429
    13271430def sage_getsourcelines(obj, is_binary=False):
    13281431    r"""
    13291432    Return a pair ([source_lines], starting line number) of the source
     
    13541457        (['cpdef test_funct(x,y): return\n'], 6)
    13551458
    13561459    The following tests that an instance of ``functools.partial`` is correctly
    1357     dealt with (see trac ticket #9976)::
     1460    dealt with (see :trac:`9976`)::
    13581461
    13591462        sage: obj = sage.combinat.partition_algebra.SetPartitionsAk
    13601463        sage: sage_getsourcelines(obj)
     
    13621465        ...
    13631466        '    raise ValueError, "k must be an integer or an integer + 1/2"\n'], 31)
    13641467
    1365     Here are some cases that were covered in trac ticket #11298;
     1468    Here are some cases that were covered in :trac`11298`;
    13661469    note that line numbers may easily change, and therefore we do
    13671470    not test them::
    13681471
     
    13851488          '    cpdef object pyobject(self):\n',
    13861489        ...
    13871490          '        return self / x\n'], ...)
    1388        
     1491
     1492    We show some enhancements provided by :trac:`11768`. First, we
     1493    use a dummy parent class that has defined an element class by a
     1494    nested class definition::
     1495
     1496        sage: from sage.misc.nested_class_test import TestNestedParent
     1497        sage: from sage.misc.sageinspect import sage_getsource
     1498        sage: P = TestNestedParent()
     1499        sage: E = P.element_class
     1500        sage: E.__bases__
     1501        (<class sage.misc.nested_class_test.TestNestedParent.Element at ...>,
     1502         <class 'sage.categories.sets_cat.Sets.element_class'>)
     1503        sage: print sage_getsource(E)
     1504            class Element:
     1505                "This is a dummy element class"
     1506                pass
     1507        sage: print sage_getsource(P)
     1508        class TestNestedParent(UniqueRepresentation, Parent):
     1509            ...
     1510            class Element:
     1511                "This is a dummy element class"
     1512                pass
     1513
     1514    Here is another example that relies on a nested class definition
     1515    in the background::
     1516
     1517        sage: C = Rings()
     1518        sage: HC = C.hom_category()
     1519        sage: sage_getsourcelines(HC)
     1520        (['    class HomCategory(HomCategory):\n', ...], ...)
     1521
     1522
     1523    Testing against a bug that has occured during work on #11768::
     1524
     1525        sage: P.<x,y> = QQ[]
     1526        sage: I = P*[x,y]
     1527        sage: sage_getsourcelines(I)
     1528        (['class MPolynomialIdeal( MPolynomialIdeal_singular_repr, \\\n',
     1529          '                        MPolynomialIdeal_macaulay2_repr, \\\n',
     1530          '                        MPolynomialIdeal_magma_repr, \\\n',
     1531          '                        Ideal_generic ):\n',
     1532          '    def __init__(self, ring, gens, coerce=True):\n',
     1533          ...
     1534          '        return result_ring.ideal(result)\n'], ...)
    13891535
    13901536    AUTHORS:
    13911537   
     
    13941540    - Extension to interactive Cython code by Simon King
    13951541    - Simon King: If a class has no docstring then let the class
    13961542      definition be found starting from the ``__init__`` method.
     1543    - Simon King: Get source lines for dynamic classes.
     1544
    13971545    """
    13981546
    13991547    try:
    14001548        return obj._sage_src_lines_()
    1401     except (AttributeError, TypeError):
     1549    except AttributeError:
    14021550        pass
     1551    except TypeError:
     1552        # That happes for instances of dynamic classes
     1553        return sage_getsourcelines(obj.__class__)
    14031554
    14041555    # Check if we deal with instance
    14051556    if isclassinstance(obj):
    14061557        if isinstance(obj,functools.partial):
    1407             obj = obj.func
     1558            return sage_getsourcelines(obj.func)
    14081559        else:
    1409             obj=obj.__class__
     1560            #obj=obj.__class__
     1561            return sage_getsourcelines(obj.__class__)
    14101562       
    14111563    # If we can handle it, we do.  We first try Python's inspect, and
    14121564    # if that fails then we try _sage_getdoc_unformatted. We can not use
     
    14211573            try:
    14221574                return inspect.getsourcelines(obj)
    14231575            except IOError:
     1576                if (not hasattr(obj, '__class__')) or hasattr(obj,'__metaclass__'):
     1577                    # That hapens for ParentMethods
     1578                    # of categories
     1579                    if '.' in obj.__name__:
     1580                        return _sage_getsourcelines_name_with_dot(obj)
     1581                if inspect.isclass(obj):
     1582                    try:
     1583                        B = obj.__base__
     1584                    except AttributeError:
     1585                        B = None
     1586                    if B is not None and B is not obj:
     1587                        return sage_getsourcelines(B)
    14241588                if obj.__class__ != type:
    14251589                    return sage_getsourcelines(obj.__class__)
    14261590                raise