Ticket #12518: trac_12518-enumerated_set_from_iterator-final.patch

File trac_12518-enumerated_set_from_iterator-final.patch, 35.1 KB (added by Vincent Delecroix, 10 years ago)
  • doc/en/reference/structure.rst

    # HG changeset patch
    # User Vincent Delecroix <20100.delecroix at gmail.com>
    # Date 1354256463 -3600
    # Node ID 6fbe9ef61e0eb9096451cf425a7f56fdf4280e73
    # Parent  30f1e2d0f20f854d425c773644a1d8718f67e34d
    Added a decorator which will automatically return a combinatorial class from a function which provides an interator.
    * * *
    trac 12518
    Implementation of enumerated set from a function that returns an iterator. This patch improved
    the previous implementation of CombinatorialClassFromIterator in sage.combinat.combinat and fit
    with the category framework.
    * * *
    Trac #12518: Review patch
    
    diff --git a/doc/en/reference/structure.rst b/doc/en/reference/structure.rst
    a b  
    2626   sage/sets/set
    2727   sage/sets/disjoint_set
    2828   sage/sets/disjoint_union_enumerated_sets
     29   sage/sets/set_from_iterator
    2930   sage/sets/finite_enumerated_set
    3031   sage/sets/finite_set_maps
    3132   sage/sets/finite_set_map_cy
  • new file sage/sets/set_from_iterator.py

    diff --git a/sage/sets/set_from_iterator.py b/sage/sets/set_from_iterator.py
    new file mode 100644
    - +  
     1r"""
     2Enumerated set from iterator
     3
     4EXAMPLES:
     5
     6We build a set from the iterator ``graphs`` that returns a canonical
     7representative for each isomorphism class of graphs::
     8
     9    sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     10    sage: E = EnumeratedSetFromIterator(
     11    ...     graphs,
     12    ...     name = "Graphs",
     13    ...     category = InfiniteEnumeratedSets(),
     14    ...     cache = True)
     15    sage: E
     16    Graphs
     17    sage: E.unrank(0)
     18    Graph on 0 vertices
     19    sage: E.unrank(4)
     20    Graph on 3 vertices
     21    sage: E.cardinality()
     22    +Infinity
     23    sage: E.category()
     24    Category of facade infinite enumerated sets
     25
     26The module also provides decorator for functions and methods::
     27
     28    sage: from sage.sets.set_from_iterator import set_from_function
     29    sage: @set_from_function
     30    ... def f(n): return xsrange(n)
     31    sage: f(3)
     32    {0, 1, 2}
     33    sage: f(5)
     34    {0, 1, 2, 3, 4}
     35    sage: f(100)
     36    {0, 1, 2, 3, 4, ...}
     37
     38    sage: from sage.sets.set_from_iterator import set_from_method
     39    sage: class A:
     40    ...    @set_from_method
     41    ...    def f(self,n):
     42    ...        return xsrange(n)
     43    sage: a = A()
     44    sage: a.f(3)
     45    {0, 1, 2}
     46    sage: f(100)
     47    {0, 1, 2, 3, 4, ...}
     48"""
     49#*****************************************************************************
     50#  Copyright (C) 2012 Vincent Delecroix <vincent.delecroix@gmail.com>
     51#
     52#  Distributed under the terms of the GNU General Public License (GPL)
     53#
     54#    This code is distributed in the hope that it will be useful,
     55#    but WITHOUT ANY WARRANTY; without even the implied warranty of
     56#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     57#    General Public License for more details.
     58#
     59#  The full text of the GPL is available at:
     60#
     61#                  http://www.gnu.org/licenses/
     62#******************************************************************************
     63
     64from sage.structure.parent import Parent
     65from sage.categories.enumerated_sets import EnumeratedSets
     66from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets
     67from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets
     68from sage.categories.sets_cat import EmptySetError
     69from itertools import izip_longest
     70import os
     71from sage.misc.function_mangling import ArgumentFixer
     72from sage.misc.lazy_list import lazy_list
     73
     74class EnumeratedSetFromIterator(Parent):
     75    """
     76    A class for enumerated set built from an iterator.
     77
     78    INPUT:
     79
     80    - ``f`` -- a function that returns an iterable from which the set is built from
     81
     82    - ``args`` -- tuple -- arguments to be sent to the function ``f``
     83
     84    - ``kwds`` -- dictionnary -- keywords to be sent to the function ``f``
     85
     86    - ``name`` -- an optional name for the set
     87
     88    - ``category`` -- (default: ``None``) an optional category for that
     89      enumerated set. If you know that your iterator will stop after a finite
     90      number of steps you should set it as :class:`FiniteEnumeratedSets`, conversly if
     91      you know that your iterator will run over and over you should set it as
     92      :class:`InfiniteEnumeratedSets`.
     93
     94    - ``cache`` -- boolean (default: ``False``) -- Whether or not use a cache
     95      mechanism for the iterator. If ``True``, then the function ``f`` is called
     96      only once.
     97
     98
     99    EXAMPLES::
     100
     101        sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     102        sage: E = EnumeratedSetFromIterator(graphs, args = (7,))
     103        sage: E
     104        {Graph on 7 vertices, Graph on 7 vertices, Graph on 7 vertices, Graph on 7 vertices, Graph on 7 vertices, ...}
     105        sage: E.category()
     106        Category of facade enumerated sets
     107
     108    The same example with a cache and a custom name::
     109
     110        sage: E = EnumeratedSetFromIterator(
     111        ...      graphs,
     112        ...      args = (8,),
     113        ...      category = FiniteEnumeratedSets(),
     114        ...      name = "Graphs with 8 vertices",
     115        ...      cache = True)
     116        sage: E
     117        Graphs with 8 vertices
     118        sage: E.unrank(3)
     119        Graph on 8 vertices
     120        sage: E.category()
     121        Category of facade finite enumerated sets
     122
     123    TESTS:
     124
     125    The cache is compatible with multiple call to ``__iter__``::
     126
     127        sage: from itertools import count
     128        sage: E = EnumeratedSetFromIterator(count, args=(0,), category=InfiniteEnumeratedSets(), cache=True)
     129        sage: e1 = iter(E)
     130        sage: e2 = iter(E)
     131        sage: e1.next(), e1.next()
     132        (0, 1)
     133        sage: e2.next(), e2.next(), e2.next()
     134        (0, 1, 2)
     135        sage: e1.next(), e1.next()
     136        (2, 3)
     137        sage: e2.next()
     138        3
     139        sage: TestSuite(E).run()
     140
     141        sage: E = EnumeratedSetFromIterator(xsrange, args=(10,), category=FiniteEnumeratedSets(), cache=True)
     142        sage: TestSuite(E).run()
     143
     144    .. NOTE::
     145
     146        In order to make the ``TestSuite`` works, the elements of the set
     147        should have parents.
     148    """
     149    def __init__(self, f, args=None, kwds=None, name=None, category=None, cache=False):
     150        """
     151        TESTS::
     152
     153            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     154            sage: S = EnumeratedSetFromIterator(xsrange, (1,200,-1), category=FiniteEnumeratedSets())
     155            sage: TestSuite(S).run()
     156        """
     157        if category is not None:
     158            Parent.__init__(self, facade = True, category = category)
     159        else:
     160            Parent.__init__(self, facade = True, category = EnumeratedSets())
     161
     162
     163        if name is not None:
     164            self.rename(name)
     165
     166        self._func = f
     167
     168        if args is not None:
     169            self._args = args
     170        if kwds is not None:
     171            self._kwds = kwds
     172
     173        if cache:
     174            self._cache = lazy_list(iter(self._func(
     175                                         *getattr(self, '_args', ()),
     176                                        **getattr(self, '_kwds', {}))))
     177
     178    def __reduce__(self):
     179        r"""
     180        Support for pickle.
     181
     182        TESTS::
     183
     184            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     185            sage: from sage.graphs.graph_generators import graphs
     186            sage: E = EnumeratedSetFromIterator(graphs,
     187            ...      args=(3,),
     188            ...      category=FiniteEnumeratedSets(),
     189            ...      name="Graphs on 3 vertices")
     190            sage: E
     191            Graphs on 3 vertices
     192            sage: F = loads(dumps(E)); F
     193            Graphs on 3 vertices
     194            sage: E == F
     195            True
     196        """
     197        return (EnumeratedSetFromIterator,
     198                (self._func,                           # func
     199                 getattr(self, '_args', None),         # args
     200                 getattr(self, '_kwds', None),         # kwds
     201                 getattr(self, '__custom_name', None), # name
     202                 self.category(),                      # category
     203                 hasattr(self, '_cache'))              # cache
     204                )
     205
     206    def _repr_(self):
     207        r"""
     208        Return a string representation of ``self``.
     209
     210        TESTS::
     211
     212            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     213            sage: E = EnumeratedSetFromIterator(Partitions(7,min_part=2).__iter__)
     214            sage: repr(E)    # indirect doctest
     215            '{[7], [5, 2], [4, 3], [3, 2, 2]}'
     216            sage: E = EnumeratedSetFromIterator(Partitions(9,min_part=2).__iter__)
     217            sage: repr(E)    # indirect doctest
     218            '{[9], [7, 2], [6, 3], [5, 4], [5, 2, 2], ...}'
     219            sage: E = EnumeratedSetFromIterator(Partitions(9,min_part=2).__iter__, name="Some partitions")
     220            sage: repr(E)    # indirect doctest
     221            'Some partitions'
     222        """
     223        l = []
     224        i = iter(self)
     225        for _ in xrange(6):
     226            try:
     227                l.append(i.next())
     228            except StopIteration:
     229                break
     230        if len(l) < 6:
     231            return '{' + ', '.join(repr(x) for x in l) + '}'
     232        l.pop(-1)
     233        return '{' + ', '.join(repr(x) for x in l) + ', ...}'
     234
     235    def __contains__(self, x):
     236        r"""
     237        Test whether ``x`` is in ``self``.
     238
     239        If the set is infinite, only the answer ``True`` should be expected in
     240        finite time.
     241
     242        EXAMPLES::
     243
     244            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     245            sage: P = Partitions(12,min_part=2,max_part=5)
     246            sage: E = EnumeratedSetFromIterator(P.__iter__)
     247            sage: P([5,5,2]) in E
     248            True
     249        """
     250        return any(x == y for y in self)
     251
     252    is_parent_of = __contains__
     253
     254    #TODO: what should we do for comparisons of infinite sets
     255    def __eq__(self, other):
     256        r"""
     257        Equality test.
     258
     259        The function returns ``True`` if and only if other is an enumerated
     260        set and has the same element as ``self``.
     261
     262        TESTS::
     263
     264            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     265            sage: E4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
     266            sage: F4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
     267            sage: E5 = EnumeratedSetFromIterator(graphs, args=(5,), category=FiniteEnumeratedSets())
     268            sage: E4 == E4
     269            True
     270            sage: E4 == F4
     271            True
     272            sage: E4 == E5
     273            False
     274            sage: E5 == E4
     275            False
     276            sage: E5 == E5
     277            True
     278        """
     279        if isinstance(other, EnumeratedSetFromIterator):
     280            # trick to allow equality between infinite sets
     281            # this assume that the function does not return randomized data!
     282            if (self._func == other._func and
     283                getattr(self, '_args', None) == getattr(other, '_args', None) and
     284                getattr(self, '_kwds', None) == getattr(other, '_kwds', None)):
     285                return True
     286
     287        if other in EnumeratedSets():
     288            #TODO: think about what should be done at that point
     289            if self not in FiniteEnumeratedSets() and other not in FiniteEnumeratedSets():
     290                import warnings
     291                warnings.warn("Testing equality of infinite sets which will not end in case of equality")
     292
     293            i1 = iter(self)
     294            i2 = iter(other)
     295            while True:
     296                try:
     297                    x = i1.next()
     298                except StopIteration:
     299                    try:
     300                        i2.next()
     301                        return False
     302                    except StopIteration:
     303                        return True
     304                try:
     305                    y = i2.next()
     306                except StopIteration:
     307                    return False
     308                if x != y:
     309                    return False
     310
     311    def __ne__(self,other):
     312        r"""
     313        Difference test.
     314
     315        The function calls the ``__eq__`` test.
     316
     317        TESTS::
     318
     319            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     320            sage: E4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
     321            sage: F4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
     322            sage: E5 = EnumeratedSetFromIterator(graphs, args=(5,), category=FiniteEnumeratedSets())
     323            sage: E4 != E4
     324            False
     325            sage: E4 != F4
     326            False
     327            sage: E4 != E5
     328            True
     329            sage: E5 != E4
     330            True
     331            sage: E5 != E5
     332            False
     333        """
     334        return not self.__eq__(other)
     335
     336    def __iter__(self):
     337        r"""
     338        Returns an iterator over the element of ``self``.
     339
     340        EXAMPLES::
     341
     342            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     343            sage: E = EnumeratedSetFromIterator(graphs, args=(8,))
     344            sage: g1 = iter(E).next(); g1
     345            Graph on 8 vertices
     346            sage: E = EnumeratedSetFromIterator(graphs, args=(8,), cache=True)
     347            sage: g2 = iter(E).next(); g2
     348            Graph on 8 vertices
     349            sage: g1 == g2
     350            True
     351        """
     352        if hasattr(self, '_cache'):
     353            return iter(self._cache)
     354        return iter(self._func(*getattr(self, '_args', ()), **getattr(self, '_kwds', {})))
     355
     356    def unrank(self, i):
     357        r"""
     358        Returns the element at position ``i``.
     359
     360        EXAMPLES::
     361
     362            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     363            sage: E = EnumeratedSetFromIterator(graphs, args=(8,), cache=True)
     364            sage: F = EnumeratedSetFromIterator(graphs, args=(8,), cache=False)
     365            sage: E.unrank(2)
     366            Graph on 8 vertices
     367            sage: E.unrank(2) == F.unrank(2)
     368            True
     369        """
     370        if hasattr(self, '_cache'):
     371            return self._cache[i]
     372        return super(EnumeratedSetFromIterator,self).unrank(i)
     373
     374    def _element_constructor_(self, el):
     375        """
     376        Construct an element from ``el``.
     377
     378        TESTS::
     379
     380            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     381            sage: S = EnumeratedSetFromIterator(xrange, args=(1,4))
     382            sage: S(1)  # indirect doctest
     383            1
     384            sage: S(0)  # indirect doctest
     385            Traceback (most recent call last):
     386            ...
     387            ValueError: 0 not in {1, 2, 3}
     388        """
     389        if el in self:
     390            return el
     391        else:
     392            raise ValueError("%s not in %s"%(el, self))
     393
     394    def clear_cache(self):
     395        r"""
     396        Clear the cache.
     397
     398        EXAMPLES::
     399
     400            sage: from itertools import count
     401            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     402            sage: E = EnumeratedSetFromIterator(count, args=(1,), cache=True)
     403            sage: e1 = E._cache
     404            sage: e1
     405            lazy list [1, 2, 3, ...]
     406            sage: E.clear_cache()
     407            sage: E._cache
     408            lazy list [1, 2, 3, ...]
     409            sage: e1 is E._cache
     410            False
     411        """
     412        if hasattr(self, '_cache'):
     413            self._cache = lazy_list(iter(self._func(
     414                                         *getattr(self, '_args', ()),
     415                                        **getattr(self, '_kwds', {}))))
     416
     417#
     418# Decorators
     419#
     420
     421#TODO: move it in sage.misc ?
     422class Decorator:
     423    r"""
     424    Abstract class that manage documentation and sources of the wrapped object.
     425
     426    The method needs to be stored in the attribute ``self.f``
     427    """
     428    def _sage_doc_(self):
     429        """
     430        Provide documentation for the wrapped function.
     431
     432        TESTS::
     433
     434            sage: from sage.misc.sageinspect import sage_getdoc
     435            sage: from sage.sets.set_from_iterator import Decorator
     436            sage: d = Decorator()
     437            sage: d.f = Integer.is_prime
     438            sage: print sage_getdoc(d)   # indirect doctest
     439               Returns "True" if "self" is prime.
     440            ...
     441               IMPLEMENTATION: Calls the PARI "isprime" function.
     442            <BLANKLINE>
     443        """
     444        from sage.misc.sageinspect import sage_getsourcelines, sage_getfile
     445        f = self.f
     446        if hasattr(f, "func_doc"):
     447            try:
     448                sourcelines = sage_getsourcelines(f)
     449            except IOError:
     450                sourcelines = None
     451            if sourcelines is not None:
     452                import os
     453                SAGE_ROOT = os.environ['SAGE_ROOT']
     454                filename = sage_getfile(f)
     455                # The following is a heuristics to get
     456                # the file name of the cached function
     457                # or method
     458                if filename.startswith(SAGE_ROOT+'/devel/sage/'):
     459                    filename = filename[len(SAGE_ROOT+'/devel/sage/'):]
     460                elif 'site-packages/' in filename:
     461                    filename = filename.split('site-packages/',1)[1]
     462                file_info = "File: %s (starting at line %d)\n"%(filename,sourcelines[1])
     463                doc = file_info+(f.func_doc or '')
     464            else:
     465                doc = f.func_doc
     466        else:
     467            doc = f.__doc__
     468        return doc
     469
     470    def _sage_src_(self):
     471        r"""
     472        Returns the source code for the wrapped function.
     473
     474        TESTS::
     475
     476            sage: from sage.misc.sageinspect import sage_getsource
     477            sage: from sage.sets.set_from_iterator import Decorator
     478            sage: d = Decorator()
     479            sage: d.f = Rational.is_square
     480            sage: print sage_getsource(d.f)   # indirect doctest
     481            def is_square(self):
     482            ...
     483                return mpq_sgn(self.value) >= 0 and mpz_perfect_square_p(mpq_numref(self.value)) and mpz_perfect_square_p(mpq_denref(self.value))
     484        """
     485        from sage.misc.sageinspect import sage_getsource
     486        return sage_getsource(self.f)
     487
     488    def _sage_src_lines_(self):
     489        r"""
     490        Returns the list of source lines and the first line number
     491        of the wrapped function.
     492
     493        TESTS::
     494
     495            sage: from sage.misc.sageinspect import sage_getsourcelines
     496            sage: from sage.sets.set_from_iterator import Decorator
     497            sage: d = Decorator()
     498            sage: d.f = MathieuGroup.order
     499            sage: S = sage_getsourcelines(d)   # indirect doctest
     500            sage: S[0][2]
     501            '        Return the number of elements of this group.\n'
     502            sage: S[0][17]
     503            '            return Integer(1)\n'
     504        """
     505        from sage.misc.sageinspect import sage_getsourcelines
     506        return sage_getsourcelines(self.f)
     507
     508    def _sage_argspec_(self):
     509        """
     510        Return the argument specification of the wrapped function or method.
     511
     512        TESTS::
     513
     514            sage: from sage.misc.sageinspect import sage_getargspec
     515            sage: from sage.sets.set_from_iterator import Decorator
     516            sage: d = Decorator()
     517            sage: d.f = find_local_minimum
     518            sage: sage_getargspec(d) # indirect doctest
     519            ArgSpec(args=['f', 'a', 'b', 'tol', 'maxfun'], varargs=None, keywords=None, defaults=(1.48e-08, 500))
     520        """
     521        from sage.misc.sageinspect import sage_getargspec
     522        return sage_getargspec(self.f)
     523
     524    def __call__(self, *args, **kwds):
     525        r"""
     526        Call function.
     527
     528        Needs to be implemented in derived subclass.
     529
     530        TEST::
     531
     532            sage: from sage.sets.set_from_iterator import Decorator
     533            sage: d = Decorator()
     534            sage: d()
     535            Traceback (most recent call last):
     536            ...
     537            NotImplementedError
     538        """
     539        raise NotImplementedError
     540
     541class EnumeratedSetFromIterator_function_decorator(Decorator):
     542    r"""
     543    Decorator for :class:`EnumeratedSetFromIterator`.
     544
     545    Name could be string or a function ``(args,kwds) -> string``.
     546
     547    .. WARNING::
     548
     549        If you are going to use this with the decorator ``cached_function``,
     550        you must place the ``cached_function`` first. See the example below.
     551
     552    EXAMPLES::
     553
     554        sage: from sage.sets.set_from_iterator import set_from_function
     555        sage: @set_from_function
     556        ... def f(n):
     557        ...    for i in xrange(n):
     558        ...        yield i**2 + i + 1
     559        sage: f(3)
     560        {1, 3, 7}
     561        sage: f(100)
     562        {1, 3, 7, 13, 21, ...}
     563
     564    To avoid ambiguity, it is always better to use it with a call which
     565    provides optional global initialization for the call to
     566    :class:`EnumeratedSetFromIterator`::
     567
     568        sage: @set_from_function(category=InfiniteEnumeratedSets())
     569        ... def Fibonacci():
     570        ...    a = 1; b = 2
     571        ...    while True:
     572        ...       yield a
     573        ...       a,b = b,a+b
     574        sage: F = Fibonacci()
     575        sage: F
     576        {1, 2, 3, 5, 8, ...}
     577        sage: F.cardinality()
     578        +Infinity
     579
     580    A simple example with many options::
     581
     582        sage: @set_from_function(
     583        ...        name = "From %(m)d to %(n)d",
     584        ...        category = FiniteEnumeratedSets())
     585        ... def f(m,n): return xsrange(m,n+1)
     586        sage: E = f(3,10); E
     587        From 3 to 10
     588        sage: E.list()
     589        [3, 4, 5, 6, 7, 8, 9, 10]
     590        sage: E = f(1,100); E
     591        From 1 to 100
     592        sage: E.cardinality()
     593        100
     594        sage: f(n=100,m=1) == E
     595        True
     596
     597    An example which mixes together ``set_from_function`` and
     598    ``cached_method``::
     599
     600        sage: @cached_function
     601        ... @set_from_function(
     602        ...    name = "Graphs on %(n)d vertices",
     603        ...    category = FiniteEnumeratedSets(),
     604        ...    cache = True)
     605        ... def Graphs(n): return graphs(n)
     606        sage: Graphs(10)
     607        Graphs on 10 vertices
     608        sage: Graphs(10).unrank(0)
     609        Graph on 10 vertices
     610        sage: Graphs(10) is Graphs(10)
     611        True
     612
     613    The ``cached_function`` must go first::
     614
     615        sage: @set_from_function(
     616        ...    name = "Graphs on %(n)d vertices",
     617        ...    category = FiniteEnumeratedSets(),
     618        ...    cache = True)
     619        ... @cached_function
     620        ... def Graphs(n): return graphs(n)
     621        sage: Graphs(10)
     622        Graphs on 10 vertices
     623        sage: Graphs(10).unrank(0)
     624        Graph on 10 vertices
     625        sage: Graphs(10) is Graphs(10)
     626        False
     627    """
     628    def __init__(self, f=None, name=None, **options):
     629        r"""
     630        Initialize ``self``.
     631
     632        TESTS::
     633
     634            sage: from sage.sets.set_from_iterator import set_from_function
     635            sage: F = set_from_function(category=FiniteEnumeratedSets())(xsrange)
     636            sage: TestSuite(F(100)).run()
     637            sage: TestSuite(F(1,5,2)).run()
     638            sage: TestSuite(F(0)).run()
     639        """
     640        if f is not None:
     641            self.f = f
     642            if hasattr(f, "func_name"):
     643                self.__name__ = f.func_name
     644            else:
     645                self.__name__ = f.__name__
     646            self.__module__ = f.__module__
     647            self.af = ArgumentFixer(f)
     648        if name is not None:
     649            self.name = name
     650        self.options = options
     651
     652    def __call__(self, *args, **kwds):
     653        r"""
     654        Build a new :class:`EnumeratedSet` by calling ``self.f`` with
     655        apropriate argument. If ``f`` is ``None``, then returns a new instance
     656        of :class:`EnumeratedSetFromIterator`.
     657
     658        EXAMPLES::
     659
     660            sage: from sage.sets.set_from_iterator import set_from_function
     661            sage: F = set_from_function(category=FiniteEnumeratedSets())(xsrange)
     662            sage: F(3)
     663            {0, 1, 2}
     664            sage: F(end=7,start=3)
     665            {3, 4, 5, 6}
     666            sage: F(10).cardinality()
     667            10
     668        """
     669        options = self.options
     670
     671        if hasattr(self, 'f'): # yet initialized
     672            if hasattr(self,'name'):
     673                if isinstance(self.name,str):
     674                    if args or kwds:
     675                        _,kk = self.af.fix_to_named(*args,**kwds)
     676                        name = self.name%dict(kk)
     677                    else:
     678                        name = self.name
     679                else:
     680                    name = self.name(*args,**kwds)
     681                return EnumeratedSetFromIterator(self.f, args, kwds, name=name, **self.options)
     682            return EnumeratedSetFromIterator(self.f, args, kwds, **self.options)
     683
     684        else: # potential global options
     685            if args == ():
     686                assert len(kwds.keys()) == 1
     687                f = kwds.values()[0]
     688            else:
     689                assert len(args) == 1
     690                f = args[0]
     691            return EnumeratedSetFromIterator_function_decorator(
     692                f,
     693                name=getattr(self,'name',None),
     694                **self.options)
     695
     696set_from_function = EnumeratedSetFromIterator_function_decorator
     697
     698class EnumeratedSetFromIterator_method_caller(Decorator):
     699    r"""
     700    Caller for decorated method in class.
     701
     702    INPUT:
     703
     704    - ``inst`` -- an instance of a class
     705
     706    - ``f`` -- a method of a class of ``inst`` (and not of the instance itself)
     707
     708    - ``name`` -- optional -- either a string (which may contains substitution
     709      rules from argument or a function args,kwds -> string.
     710
     711    - ``options`` -- any option accepted by :class:`EnumeratedSetFromIterator`
     712    """
     713    def __init__(self, inst, f, name=None, **options):
     714        r"""
     715        Initialize ``self``.
     716
     717        TESTS::
     718
     719            sage: from sage.sets.set_from_iterator import DummyExampleForPicklingTest
     720            sage: d = DummyExampleForPicklingTest()
     721            sage: d.f()
     722            {10, 11, 12, 13, 14, ...}
     723
     724        It is possible to pickle/unpickle the class and the instance::
     725
     726            sage: loads(dumps(DummyExampleForPicklingTest))().f()
     727            {10, 11, 12, 13, 14, ...}
     728            sage: loads(dumps(d)).f()
     729            {10, 11, 12, 13, 14, ...}
     730
     731        But not the enumerated set::
     732
     733            sage: loads(dumps(d.f()))
     734            Traceback (most recent call last):
     735            ...
     736            PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed
     737        """
     738        self.inst = inst
     739        self.f = f
     740        self.af = ArgumentFixer(self.f)
     741        if hasattr(f, "func_name"):
     742            self.__name__ = f.func_name
     743        else:
     744            self.__name__ = f.__name__
     745        self.__module__ = f.__module__
     746
     747        self.name = name
     748        self.options = options
     749
     750    def __call__(self,*args,**kwds):
     751        r"""
     752        Returns an instance of :class:`EnumeratedSetFromIterator` with
     753        proper argument.
     754
     755        TESTS::
     756
     757            sage: from sage.sets.set_from_iterator import set_from_method
     758            sage: class A:
     759            ...    @set_from_method(name = lambda self,n: str(self)*n)
     760            ...    def f(self,n):
     761            ...        return xsrange(n)
     762            ...    def __repr__(self):
     763            ...        return "A"
     764            sage: a = A()
     765            sage: a.f(3)                         # indirect doctest
     766            AAA
     767            sage: A.f(a,3)                       # indirect doctest
     768            AAA
     769            sage: [x for x in a.f(6)]            # indirect doctest
     770            [0, 1, 2, 3, 4, 5]
     771        """
     772        if self.inst:
     773            args = (self.inst,) + args
     774        if self.name:
     775            if isinstance(self.name,str):
     776                aa,kk = self.af.fix_to_named(*args,**kwds)
     777                name = self.name%dict(kk)
     778            else:
     779                name = self.name(*args, **kwds)
     780            return EnumeratedSetFromIterator(self.f, args, kwds, name, **self.options)
     781        return EnumeratedSetFromIterator(self.f, args, kwds, **self.options)
     782
     783    def __get__(self, inst, cls):
     784        r"""
     785        Get a :class:`EnumeratedSetFromIterator_method_caller` bound to a
     786        specific instance of the class of the cached method.
     787
     788        .. NOTE::
     789
     790            :class:`EnumeratedSetFromIterator_method_caller` has a separate
     791            ``__get__`` because of the special behavior of category framework
     792            for element classes which are not of extension type (see
     793            :meth:`sage.structure.element.Element.__get__`).
     794
     795        TESTS::
     796
     797            sage: from sage.sets.set_from_iterator import set_from_method
     798            sage: from sage.structure.misc import getattr_from_other_class
     799            sage: class A:
     800            ...      stop = 10000
     801            ...      @set_from_method
     802            ...      def f(self,start):
     803            ...          return xsrange(start,self.stop)
     804            sage: a = A()
     805            sage: getattr_from_other_class(a, A, 'f')(4)
     806            {4, 5, 6, 7, 8, ...}
     807
     808            sage: class B:
     809            ...      stop = 10000
     810            ...      @set_from_method(category=FiniteEnumeratedSets())
     811            ...      def f(self,start):
     812            ...          return xsrange(start,self.stop)
     813            sage: b = B()
     814            sage: getattr_from_other_class(b, B, 'f')(2)
     815            {2, 3, 4, 5, 6, ...}
     816        """
     817        return EnumeratedSetFromIterator_method_caller(
     818                inst, self.f,
     819                self.name,
     820                **self.options)
     821
     822class EnumeratedSetFromIterator_method_decorator(object):
     823    r"""
     824    Decorator for enumerated set built from a method.
     825
     826    INPUT:
     827
     828    - ``f`` -- Optional function from which are built the enumerated sets at
     829      each call
     830
     831    - ``name`` -- Optional string (which may contains substitution rules from
     832      argument) or a function ``(args,kwds) -> string``.
     833
     834    - any option accepted by :class:`EnumeratedSetFromIterator`.
     835
     836    EXAMPLES::
     837
     838        sage: from sage.sets.set_from_iterator import set_from_method
     839        sage: class A():
     840        ...    def n(self): return 12
     841        ...    @set_from_method
     842        ...    def f(self): return xsrange(self.n())
     843        sage: a = A()
     844        sage: print a.f.__class__
     845        sage.sets.set_from_iterator.EnumeratedSetFromIterator_method_caller
     846        sage: a.f()
     847        {0, 1, 2, 3, 4, ...}
     848        sage: A.f(a)
     849        {0, 1, 2, 3, 4, ...}
     850
     851    A more complicated example with a parametrized name::
     852
     853        sage: class B():
     854        ...    @set_from_method(
     855        ...        name = "Graphs(%(n)d)",
     856        ...        category = FiniteEnumeratedSets())
     857        ...    def graphs(self, n): return graphs(n)
     858        sage: b = B()
     859        sage: G3 = b.graphs(3)
     860        sage: G3
     861        Graphs(3)
     862        sage: G3.cardinality()
     863        4
     864        sage: G3.category()
     865        Category of facade finite enumerated sets
     866        sage: B.graphs(b,3)
     867        Graphs(3)
     868
     869    And a last example with a name parametrized by a function::
     870
     871        sage: class D():
     872        ...    def __init__(self, name): self.name = str(name)
     873        ...    def __str__(self): return self.name
     874        ...    @set_from_method(
     875        ...        name = lambda self,n: str(self)*n,
     876        ...        category = FiniteEnumeratedSets())
     877        ...    def subset(self, n):
     878        ...        return xsrange(n)
     879        sage: d = D('a')
     880        sage: E = d.subset(3); E
     881        aaa
     882        sage: E.list()
     883        [0, 1, 2]
     884        sage: F = d.subset(n=10); F
     885        aaaaaaaaaa
     886        sage: F.list()
     887        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     888
     889    .. TODO::
     890
     891        It is not yet possible to use ``set_from_method`` in conjunction with
     892        ``cached_method``.
     893    """
     894    def __init__(self, f=None, **options):
     895        r"""
     896        Initialize ``self``.
     897
     898        TESTS:
     899
     900        We test if pickling works correctly on the Permutation class (in
     901        :mod:`sage.combinat.permutation`) because its method ``bruhat_succ``
     902        and ``bruhat_pred`` are decorated with ``set_from_method``::
     903
     904            sage: from sage.combinat.permutation import Permutation_class
     905            sage: loads(dumps(Permutation_class))
     906            <class 'sage.combinat.permutation.Permutation_class'>
     907            sage: p = Permutation([3,2,1])
     908            sage: loads(dumps(p)) == p
     909            True
     910        """
     911        if f is not None:
     912            import types
     913            self.f = f
     914            if hasattr(f,"func_name"):
     915                self.__name__ = f.func_name
     916                self.__module__ = f.__module__
     917
     918            else:
     919                if hasattr(f, '__module__'):
     920                    self.__module__ = f.__module__
     921                elif hasattr(f, 'im_func'):
     922                    self.__module__ = f.im_func.__module__
     923
     924                if hasattr(f, '__name__'):
     925                    self.__name__ = f.__name__
     926                elif hasattr(f, 'im_func'):
     927                    self.__name__ = f.im_func.__name__
     928
     929        self.options = options
     930
     931    def __call__(self, f):
     932        r"""
     933        Trick if :class:`EnumeratedSetFromIterator_method` was created with
     934        some options and is called with a function as argument.
     935
     936        TESTS::
     937
     938            sage: from sage.sets.set_from_iterator import set_from_method
     939            sage: class A:
     940            ...    @set_from_method()    # indirect doctest
     941            ...    def f(self):
     942            ...        return xsrange(3)
     943            sage: a = A()
     944            sage: a.f()
     945            {0, 1, 2}
     946        """
     947        return EnumeratedSetFromIterator_method_decorator(f,**self.options)
     948
     949    def __get__(self, inst, cls):
     950        r"""
     951        TESTS::
     952
     953            sage: from sage.sets.set_from_iterator import set_from_method
     954            sage: class A():
     955            ...    def n(self): return 12
     956            ...    @set_from_method
     957            ...    def f(self): return xsrange(self.n())
     958            sage: a = A()
     959            sage: print A.f.__class__
     960            sage.sets.set_from_iterator.EnumeratedSetFromIterator_method_caller
     961            sage: print a.f.__class__
     962            sage.sets.set_from_iterator.EnumeratedSetFromIterator_method_caller
     963        """
     964        # You would hardly ever see an instance of this class alive.
     965        return EnumeratedSetFromIterator_method_caller(inst, self.f, **self.options)
     966
     967set_from_method = EnumeratedSetFromIterator_method_decorator
     968
     969class DummyExampleForPicklingTest:
     970    r"""
     971    Class example to test pickling with the decorator :class:`set_from_method`.
     972
     973    .. WARNING::
     974
     975        This class is intended to be used in doctest only.
     976
     977    EXAMPLES::
     978
     979        sage: from sage.sets.set_from_iterator import DummyExampleForPicklingTest
     980        sage: DummyExampleForPicklingTest().f()
     981        {10, 11, 12, 13, 14, ...}
     982    """
     983    start = 10
     984    stop  = 100
     985    @set_from_method
     986    def f(self):
     987        r"""
     988        Returns the set between ``self.start`` and ``self.stop``.
     989
     990        EXAMPLES::
     991
     992            sage: from sage.sets.set_from_iterator import DummyExampleForPicklingTest
     993            sage: d = DummyExampleForPicklingTest()
     994            sage: d.f()
     995            {10, 11, 12, 13, 14, ...}
     996            sage: d.start = 4
     997            sage: d.stop = 200
     998            sage: d.f()
     999            {4, 5, 6, 7, 8, ...}
     1000        """
     1001        from sage.misc.misc import xsrange
     1002        return xsrange(self.start, self.stop)