Ticket #6097: abstract_method-6097-nt.patch

File abstract_method-6097-nt.patch, 8.6 KB (added by nthiery, 9 years ago)
  • doc/en/reference/misc.rst

    # HG changeset patch
    # User Nicolas M. Thiery <nthiery@users.sf.net>
    # Date 1242949450 25200
    # Node ID fdf5d0664386aaf859a49d214d852a81112f2273
    # Parent  3b59d94857e44281a890acf15adea81b47d33fd8
    #6097: Implements a mantra for declaring abstract methods
    
    This patch implements a decorator tha can be used to declare a method
    that should be implemented by derived classes. This declaration should
    typically include documentation for the specification for this method.
    
    The purpose is to enforce a consistent and visual mantra for such
    declarations. This is also used by #5891 for automated tests.
    
    diff --git a/doc/en/reference/misc.rst b/doc/en/reference/misc.rst
    a b Miscellaneous 
    66.. toctree::
    77   :maxdepth: 2
    88
     9   sage/misc/abstract_method
    910   sage/misc/misc
    1011   sage/misc/package
    1112   sage/misc/getusage
  • new file sage/misc/abstract_method.py

    diff --git a/sage/misc/abstract_method.py b/sage/misc/abstract_method.py
    new file mode 100644
    - +  
     1"""
     2Abstract methods
     3"""
     4
     5#*****************************************************************************
     6#       Copyright (C) 2008 Nicolas M. Thiery <nthiery at users.sf.net>
     7#
     8#  Distributed under the terms of the GNU General Public License (GPL)
     9#
     10#    This code is distributed in the hope that it will be useful,
     11#    but WITHOUT ANY WARRANTY; without even the implied warranty of
     12#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13#    General Public License for more details.
     14#
     15#  The full text of the GPL is available at:
     16#
     17#                  http://www.gnu.org/licenses/
     18#*****************************************************************************
     19
     20import types
     21
     22def abstract_method(f = None, optional = False):
     23    r"""
     24    Abstract methods
     25
     26    INPUT::
     27
     28     - ``f``: a function
     29     - ``optional``: a boolean; defaults to False
     30
     31    The decorator :obj:`abstract_method` can be used to declare
     32    methods that should be implemented by all concrete derived
     33    classes. This declaration should typically include documentation
     34    for the specification for this method.
     35
     36    The purpose is to enforce a consistent and visual syntax for such
     37    declarations. It is used by the Sage categories for automated
     38    tests (see ``Sets.Parent.test_not_implemented``).
     39
     40    EXAMPLES::
     41
     42    We create a class with an abstract method::
     43
     44        sage: class A(object):
     45        ...
     46        ...       @abstract_method
     47        ...       def my_method(self):
     48        ...           '''
     49        ...           The method :meth:`my_method` computes my_method
     50        ...
     51        ...           EXAMPLES::
     52        ...
     53        ...           '''
     54        ...           pass
     55        ...
     56        sage: A.my_method
     57        <abstract method my_method at ...>
     58
     59    The current policy is that a ``NotImplementedError`` is raised
     60    when accessing the method through an instance, even before the
     61    method is called::
     62
     63        sage: x = A()
     64        sage: x.my_method
     65        Traceback (most recent call last):
     66        ...
     67        NotImplementedError: <abstract method my_method at ...>
     68
     69    It is also possible to mark abstract methods as optional::
     70
     71        sage: class A(object):
     72        ...
     73        ...       @abstract_method(optional = True)
     74        ...       def my_method(self):
     75        ...           '''
     76        ...           The method :meth:`my_method` computes my_method
     77        ...
     78        ...           EXAMPLES::
     79        ...
     80        ...           '''
     81        ...           pass
     82        ...
     83
     84        sage: A.my_method
     85        <optional abstract method my_method at ...>
     86
     87        sage: x = A()
     88        sage: x.my_method
     89        NotImplemented
     90
     91    The official mantra for testing whether an optional abstract
     92    method is implemented is::
     93
     94    # Fixme: sage -t complains about indentation below
     95    #    sage: if x.my_method is not NotImplemented:
     96    #    ...       x.my_method()
     97    #    ...   else:
     98    #    ...       print "x.my_method not available. Let's use some other trick."
     99    #    ...
     100    #    x.my_method not available. Let's use some other trick.
     101
     102    ..topic: Discussion
     103
     104    The policy details are not yet fixed. The purpose of this fist
     105    implementation is to let developpers experiment with it and give
     106    feedback on what's most practical.
     107
     108    The advantage of the current policy is that attempts at using a
     109    non implemented methods are caught as early as possible. On the
     110    other hand, one cannot use introspection directly to fetch the
     111    documentation::
     112
     113        sage: x.my_method?      # todo: not implemented
     114
     115    Instead one needs to do::
     116
     117        sage: A._my_method?     # todo: not implemented
     118
     119    This could probably be fixed in :mod:`sage.misc.sageinspect`.
     120
     121    TODO: what should be the recommended mantra for existence testing from the class?
     122
     123    TODO: should extra information appear in the output? The name of
     124    the class? That of the super class where the abstract method is defined?
     125
     126    TODO: look for similar decorators on the web, and merge
     127
     128    ..topic: Implementation details
     129
     130    Technically, an abstract_method is a non-data descriptor (see
     131    Invoking Descriptors in the Python reference manual).
     132
     133    The syntax ``@abstract_method`` w.r.t. @abstract_method(optional = True)
     134    is achieved by a little trick which we test here::
     135
     136        sage: abstract_method(optional = True)
     137        <function <lambda> at ...>
     138        sage: abstract_method(optional = True)(banner)
     139        <optional abstract method banner at ...>
     140        sage: abstract_method(banner, optional = True)
     141        <optional abstract method banner at ...>
     142
     143    """
     144    if f is None:
     145        return lambda f: AbstractMethod(f, optional = optional)
     146    else:
     147        return AbstractMethod(f, optional)
     148
     149class AbstractMethod(object):
     150    def __init__(self, f, optional = False):
     151        """
     152        Constructor for abstract methods
     153
     154        EXAMPLES::
     155
     156            sage: def f(x):
     157            ...       "doc of f"
     158            ...       return 1
     159            ...
     160            sage: x = abstract_method(f); x
     161            <abstract method f at ...>
     162            sage: x.__doc__
     163            'doc of f'
     164            sage: x.__name__
     165            'f'
     166            sage: x.__module__
     167            '__main__'
     168        """
     169        assert isinstance(f, types.FunctionType) # only plain functions are supported yet
     170        assert isinstance(optional, bool)
     171        self._f = f
     172        self._optional = optional
     173        if hasattr(f, "func_doc"):
     174            self.__doc__ = f.func_doc
     175        if hasattr(f, "func_name"):
     176            self.__name__ = f.func_name
     177        else:
     178            self.__name__ = "..."
     179        if hasattr(f, "__module__"):
     180            self.__module__ = f.__module__
     181
     182    def __repr__(self):
     183        """
     184        EXAMPLES::
     185
     186            sage: abstract_method(banner)
     187            <abstract method banner at ...>
     188       
     189            sage: abstract_method(banner, optional = True)
     190            <optional abstract method banner at ...>
     191        """
     192        return "<" + ("optional " if self._optional else "") + "abstract method %s"%repr(self._f)[10:]
     193
     194    def _sage_src_lines_(self):
     195        """
     196        Returns the source code location for the wrapped function.
     197
     198        EXAMPLES::
     199
     200            sage: from sage.misc.sageinspect import sage_getsourcelines
     201            sage: g = abstract_method(banner)
     202            sage: (src, lines) = sage_getsourcelines(g)
     203            sage: src[0]
     204            'def banner():\n'
     205            sage: lines
     206            72
     207
     208        """
     209        from sage.misc.sageinspect import sage_getsourcelines
     210        return sage_getsourcelines(self._f)
     211
     212       
     213    def __get__(self, instance, cls):
     214        """
     215        Implements the attribute access protocol.
     216
     217        EXAMPLES::
     218
     219            sage: class A: pass
     220            sage: def f(x): return 1
     221            ...
     222            sage: f = abstract_method(f)
     223            sage: f.__get__(A(), A)
     224            Traceback (most recent call last):
     225            ...
     226            NotImplementedError: <abstract method f at ...>
     227        """
     228        if instance is None:
     229            return self
     230        elif self._optional:
     231            return NotImplemented
     232        else:
     233            raise NotImplementedError(repr(self))
  • sage/misc/all.py

    diff --git a/sage/misc/all.py b/sage/misc/all.py
    a b from cachefunc import CachedFunction, ca 
    157157
    158158from lazy_attribute import lazy_attribute
    159159
     160from abstract_method import abstract_method
     161
    160162from sagex_ds import BinaryTree
    161163
    162164from randstate import seed, set_random_seed, initial_seed, current_randstate