Ticket #11791: trac11791_dynamic_metaclass_introspection.patch

File trac11791_dynamic_metaclass_introspection.patch, 11.6 KB (added by SimonKing, 8 years ago)

Introspection for classes with dynamic metaclass

  • sage/misc/sageinspect.py

    # HG changeset patch
    # User Simon King <simon.king@uni-jena.de>
    # Date 1315840400 -7200
    # Node ID 7c549232263753f43e626194e2e90de5a794d1a3
    # Parent  a1187778c16cbf1981c9e9e53d4c661d5ec4c036
    #11791: Introspection for interactive class definitions with dynamic metaclass, rel #9107
    
    diff --git a/sage/misc/sageinspect.py b/sage/misc/sageinspect.py
    a b  
    99- William Stein (extensive modifications)
    1010- Nick Alexander (extensions)
    1111- Nick Alexander (testing)
    12 - Simon King (some extension for Cython, generalisation of SageArgSpecVisitor)
     12- Simon King (some extension for Cython, generalisation
     13  of SageArgSpecVisitor, improved handling of nested
     14  classes)
    1315
    1416EXAMPLES::
    1517
     
    879881        sage: sage_getfile(Sq)[-42:]
    880882        'sage/algebras/steenrod/steenrod_algebra.py'
    881883
    882     The following tests against some bugs fixed in trac ticket #9976::
     884    The following tests against some bugs fixed in :trac:`9976`::
    883885
    884886        sage: obj = sage.combinat.partition_algebra.SetPartitionsAk
    885887        sage: obj = sage.combinat.partition_algebra.SetPartitionsAk
    886888        sage: sage_getfile(obj)
    887889        '...sage/combinat/partition_algebra.py'
    888890
    889     And here is another bug, fixed in trac ticket #11298::
     891    And here is another bug, fixed in :trac:`11298`::
    890892
    891893        sage: P.<x,y> = QQ[]
    892894        sage: sage_getfile(P)
    893895        '...sage/rings/polynomial/multi_polynomial_libsingular.pyx'
    894896
     897    The following has been enabled in :trac:`11791`::
     898
     899        sage: cython_code = [
     900        ... 'from sage.structure.dynamic_class import dynamic_class',
     901        ... 'from sage.misc.classcall_metaclass import ClasscallMetaclass',
     902        ... 'from sage.all import cached_function',
     903        ... 'MyMetaclass = dynamic_class("MyMetaclass",(ClasscallMetaclass,))',
     904        ... 'class Bar2:',
     905        ... '    __metaclass__ = MyMetaclass',
     906        ... '    @cached_function',
     907        ... '    def __classcall__(cls, R):',
     908        ... '        return type.__call__(cls, R)',
     909        ... '    def __init__(self,R):',
     910        ... '        self.R = R',
     911        ... '    def __repr__(self):',
     912        ... '        return "[%s]"%self.R',
     913        ... '    def __hash__(self):',
     914        ... '        print "computing the hash"',
     915        ... '        return int(12345)']
     916        sage: cython('\n'.join(cython_code))
     917        sage: print ''.join(open(sage_getfile(Bar2)).readlines())
     918        <BLANKLINE>
     919        include "interrupt.pxi"  # ctrl-c interrupt block support
     920        include "stdsage.pxi"  # ctrl-c interrupt block support
     921        <BLANKLINE>
     922        include "cdefs.pxi"
     923        from sage.structure.dynamic_class import dynamic_class
     924        from sage.misc.classcall_metaclass import ClasscallMetaclass
     925        from sage.all import cached_function
     926        MyMetaclass = dynamic_class("MyMetaclass",(ClasscallMetaclass,))
     927        class Bar2:
     928            __metaclass__ = MyMetaclass
     929            @cached_function
     930            def __classcall__(cls, R):
     931                return type.__call__(cls, R)
     932            def __init__(self,R):
     933                self.R = R
     934            def __repr__(self):
     935                return "[%s]"%self.R
     936            def __hash__(self):
     937                print "computing the hash"
     938                return int(12345)
     939
     940    The following was fixed in :trac:`9107` and :trac:`11791`::
     941
     942        sage: cython_code = [
     943        ... "from sage.structure.unique_representation import UniqueRepresentation",
     944        ... "class A1(UniqueRepresentation):",
     945        ... "    class B1(UniqueRepresentation):",
     946        ... "        class C1: pass",
     947        ... "    class B2:",
     948        ... "        class C2: pass"]
     949        sage: import os
     950        sage: cython(os.linesep.join(cython_code))
     951        sage: from sage.misc.sageinspect import sage_getsource
     952        sage: print sage_getsource(A1.B1)
     953            class B1(UniqueRepresentation):
     954                class C1: pass
     955
    895956    AUTHORS:
    896957
    897958    - Nick Alexander
     
    903964    pos = _extract_embedded_position(d)
    904965    if pos is not None:
    905966        (_, filename, _) = pos
    906         return filename
     967        # Verify that it is the correct file. Move on to a temporary file,
     968        # if it turns out that it can not be found in the sage library:
     969        try:
     970            source_lines = open(filename).readlines()
     971            return filename
     972        except IOError:
     973            try:
     974                from sage.all import SAGE_TMP
     975                raw_name = filename.split(os.sep)[-1]
     976                newname = os.path.join(SAGE_TMP,'spyx','_'.join(raw_name.split('_')[:-1]),raw_name)
     977                source_lines = open(newname).readlines()
     978                return newname
     979            except IOError:
     980                pass
    907981
    908982    # The instance case
    909983    if isclassinstance(obj):
     
    912986        return sage_getfile(obj.__class__) #inspect.getabsfile(obj.__class__)
    913987
    914988    # No go? fall back to inspect.
    915     return inspect.getabsfile(obj)
     989    filename = inspect.getabsfile(obj)
     990    filesplit = filename.split(os.extsep)
     991    if filesplit[-1]=='so': # an extension, probably we'll find the file under the name ".pyx"
     992        return os.extsep.join(filesplit[:-1])+os.extsep+'pyx'
     993    elif filesplit[-1]=='pyc': # This occurs with interactive defs in IPython
     994        return os.extsep.join(filesplit[:-1])+os.extsep+'py'
     995    return filename
    916996
    917997def sage_getargspec(obj):
    918998    r"""
     
    15071587          ...
    15081588          '        return result_ring.ideal(result)\n'], ...)
    15091589
     1590    The following has been enabled in trac ticket #11791::
     1591
     1592        sage: cython_code = [
     1593        ... 'from sage.structure.dynamic_class import dynamic_class',
     1594        ... 'from sage.all import cached_function',
     1595        ... 'from sage.misc.classcall_metaclass import ClasscallMetaclass',
     1596        ... 'MyMetaclass = dynamic_class("MyMetaclass",(ClasscallMetaclass,))',
     1597        ... 'class Bar2:',
     1598        ... '    __metaclass__ = MyMetaclass',
     1599        ... '    @cached_function',
     1600        ... '    def __classcall__(cls, R):',
     1601        ... '        return type.__call__(cls, R)',
     1602        ... '    def __init__(self,R):',
     1603        ... '        self.R = R',
     1604        ... '    def __repr__(self):',
     1605        ... '        return "[%s]"%self.R',
     1606        ... '    def __hash__(self):',
     1607        ... '        print "computing the hash"',
     1608        ... '        return int(12345)']
     1609        sage: cython('\n'.join(cython_code))
     1610        sage: print ''.join(sage_getsourcelines(Bar2)[0])
     1611        class Bar2:
     1612            __metaclass__ = MyMetaclass
     1613            @cached_function
     1614            def __classcall__(cls, R):
     1615                return type.__call__(cls, R)
     1616            def __init__(self,R):
     1617                self.R = R
     1618            def __repr__(self):
     1619                return "[%s]"%self.R
     1620            def __hash__(self):
     1621                print "computing the hash"
     1622                return int(12345)
     1623
    15101624    AUTHORS:
    15111625   
    15121626    - William Stein
     
    15171631    - Simon King: Get source lines for dynamic classes.
    15181632
    15191633    """
     1634    # If there is a custom _sage_src_lines_ that belongs to obj (and not to obj.__class__),
     1635    # then we use it
     1636    if getattr(obj,'_sage_src_lines_',None) is not getattr(type(obj),'_sage_src_lines_',None):
     1637        # Rationale:
     1638        # 1. If obj._sage_src_lines_ does not exist, then both are None.
     1639        # 2. If obj.__class__._sage_src_lines_ exists and is the same as obj._sage_src_lines_, then
     1640        #    obj._sage_src_lines_() would return the source of obj.__class__
     1641        # In both cases, obj._sage_src_lines_() would not give the result that we want.
     1642        try:
     1643            return obj._sage_src_lines_()
     1644        except TypeError:
     1645            # In some cases (e.g., QQ['x','y']), obj._sage_src_lines_() dispatches to a function
     1646            # that takes no arguments.
     1647            pass
    15201648
    1521     try:
    1522         return obj._sage_src_lines_()
    1523     except AttributeError:
    1524         pass
    1525     except TypeError:
    1526         # That happes for instances of dynamic classes
    1527         return sage_getsourcelines(obj.__class__)
    1528 
    1529     # Check if we deal with instance
     1649    # Check if we deal with instances - functools and "usual" instances.
    15301650    if isclassinstance(obj):
    15311651        if isinstance(obj,functools.partial):
    15321652            return sage_getsourcelines(obj.func)
    15331653        else:
    1534             #obj=obj.__class__
    15351654            return sage_getsourcelines(obj.__class__)
    15361655       
    1537     # If we can handle it, we do.  We first try Python's inspect, and
    1538     # if that fails then we try _sage_getdoc_unformatted. We can not use
    1539     # the latter right away, since otherwise there is an import problem
    1540     # at sage startup, believe it or not.
     1656    # If the above attempts fail then we test whether we face nested classes:
     1657    from sage.misc.nested_class import NestedClassMetaclass
     1658    if (isinstance(type(obj), NestedClassMetaclass) or issubclass(type(obj), NestedClassMetaclass) or not hasattr(obj,'__class__')): # and '.' in obj.__name__:
     1659        return _sage_getsourcelines_name_with_dot(obj)
     1660
     1661    # We try to find the position of the object's definition in its source file.
     1662    # First, we try with the information provided by inspect.getdoc.
    15411663    d = inspect.getdoc(obj)
    15421664    pos = _extract_embedded_position(d)
    15431665    if pos is None:
     1666        # If that fails, we try again, with _sage_getdoc_unformatted
    15441667        d = _sage_getdoc_unformatted(obj)
    15451668        pos = _extract_embedded_position(d)
    15461669        if pos is None:
     1670            # We try Python's inspect, as a last resort to get the source properly
    15471671            try:
    15481672                return inspect.getsourcelines(obj)
    1549             except IOError:
    1550                 if (not hasattr(obj, '__class__')) or hasattr(obj,'__metaclass__'):
    1551                     # That hapens for ParentMethods
    1552                     # of categories
    1553                     if '.' in obj.__name__:
    1554                         return _sage_getsourcelines_name_with_dot(obj)
    1555                 if inspect.isclass(obj):
    1556                     try:
    1557                         B = obj.__base__
    1558                     except AttributeError:
    1559                         B = None
    1560                     if B is not None and B is not obj:
    1561                         return sage_getsourcelines(B)
    1562                 if obj.__class__ != type:
    1563                     return sage_getsourcelines(obj.__class__)
    1564                 raise
     1673            except (IOError, TypeError):
     1674                pass
    15651675
    1566     (orig, filename, lineno) = pos
     1676            # Every attempt has failed. We thus resort
     1677            # to the base, if obj is a class, or to
     1678            # the object's class otherwise.
     1679            if isinstance(obj, type):
     1680                try:
     1681                    B = obj.__base__
     1682                except AttributeError:
     1683                    B = None
     1684                if B is not None and B is not obj:
     1685                    return sage_getsourcelines(B)
     1686            if obj.__class__ != type:
     1687                return sage_getsourcelines(obj.__class__)
     1688            raise
     1689
     1690    # We know the position, but it could be that the file is not in
     1691    # the sage library. sage_getfile knows how to cope with that case
     1692    (orig, _, lineno) = pos
    15671693    try:
    1568         source_lines = open(filename).readlines()
     1694        source_lines = open(sage_getfile(obj)).readlines()
    15691695    except IOError:
    1570         try:
    1571             from sage.all import SAGE_TMP
    1572             raw_name = filename.split('/')[-1]
    1573             newname = SAGE_TMP+'/spyx/'+'_'.join(raw_name.split('_')[:-1])+'/'+raw_name
    1574             source_lines = open(newname).readlines()
    1575         except IOError:
    1576             return None
     1696        return None
    15771697   
    15781698    # It is possible that the source lines belong to the __init__ method,
    15791699    # rather than to the class. So, we try to look back and find the class