From 8b0558242511b4bba4ca04b20bb586b74d3d7a8e Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Wed, 11 Nov 2020 16:51:39 +0100 Subject: [PATCH] Readd unused functions, but deprecated them --- src/sage/misc/decorators.py | 278 +++++++++++++++++++++++++++++++++++- src/sage/misc/superseded.py | 32 +++++ 2 files changed, 307 insertions(+), 3 deletions(-) diff --git a/src/sage/misc/decorators.py b/src/sage/misc/decorators.py index a9f747bff7e..0dee7e9af7d 100644 --- a/src/sage/misc/decorators.py +++ b/src/sage/misc/decorators.py @@ -8,7 +8,9 @@ - Tim Dumol (5 Dec 2009) -- initial version. - Johan S. R. Nielsen (2010) -- collect decorators from various modules. - Johan S. R. Nielsen (8 apr 2011) -- improve introspection on decorators. -- Simon King (2011-05-26) -- put this file into the reference manual. +- Simon King (2011-05-26) -- improve introspection of sage_wraps. Put this + file into the reference manual. +- Julian Rueth (2014-03-19): added ``decorator_keywords`` decorator """ #***************************************************************************** @@ -28,13 +30,154 @@ from functools import (partial, update_wrapper, WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES) from copy import copy +from sage.misc.superseded import deprecated -from decorator import decorate, decorator +from decorator import decorator from sage.misc.sageinspect import (sage_getsource, sage_getsourcelines, sage_getargspec) from inspect import ArgSpec +@deprecated(30884, 'replaced by the decorator library') +def sage_wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES): + r""" + Decorator factory which should be used in decorators for making sure that + meta-information on the decorated callables are retained through the + decorator, such that the introspection functions of + ``sage.misc.sageinspect`` retrieves them correctly. This includes + documentation string, source, and argument specification. This is an + extension of the Python standard library decorator functools.wraps. + + That the argument specification is retained from the decorated functions + implies, that if one uses ``sage_wraps`` in a decorator which intentionally + changes the argument specification, one should add this information to + the special attribute ``_sage_argspec_`` of the wrapping function (for an + example, see e.g. ``@options`` decorator in this module). + + EXAMPLES: + + Demonstrate that documentation string and source are retained from the + decorated function:: + + sage: def square(f): + ....: @sage_wraps(f) + ....: def new_f(x): + ....: return f(x)*f(x) + ....: return new_f + sage: @square + ....: def g(x): + ....: "My little function" + ....: return x + sage: g(2) + 4 + sage: g(x) + x^2 + sage: g.__doc__ + 'My little function' + sage: from sage.misc.sageinspect import sage_getsource, sage_getsourcelines, sage_getfile + sage: sage_getsource(g) + '@square...def g(x)...' + + Demonstrate that the argument description are retained from the + decorated function through the special method (when left + unchanged) (see :trac:`9976`):: + + sage: def diff_arg_dec(f): + ....: @sage_wraps(f) + ....: def new_f(y, some_def_arg=2): + ....: return f(y+some_def_arg) + ....: return new_f + sage: @diff_arg_dec + ....: def g(x): + ....: return x + sage: g(1) + 3 + sage: g(1, some_def_arg=4) + 5 + sage: from sage.misc.sageinspect import sage_getargspec + sage: sage_getargspec(g) + ArgSpec(args=['x'], varargs=None, keywords=None, defaults=None) + + Demonstrate that it correctly gets the source lines and the source + file, which is essential for interactive code edition; note that we + do not test the line numbers, as they may easily change:: + + sage: P. = QQ[] + sage: I = P*[x,y] + sage: sage_getfile(I.interreduced_basis) # known bug + '.../sage/rings/polynomial/multi_polynomial_ideal.py' + sage: sage_getsourcelines(I.interreduced_basis) + ([' @handle_AA_and_QQbar\n', + ' @singular_gb_standard_options\n', + ' @libsingular_gb_standard_options\n', + ' def interreduced_basis(self):\n', + ... + ' return self.basis.reduced()\n'], ...) + + The ``f`` attribute of the decorated function refers to the + original function:: + + sage: foo = object() + sage: @sage_wraps(foo) + ....: def func(): + ....: pass + sage: wrapped = sage_wraps(foo)(func) + sage: wrapped.f is foo + True + + Demonstrate that sage_wraps works for non-function callables + (:trac:`9919`):: + + sage: def square_for_met(f): + ....: @sage_wraps(f) + ....: def new_f(self, x): + ....: return f(self,x)*f(self,x) + ....: return new_f + sage: class T: + ....: @square_for_met + ....: def g(self, x): + ....: "My little method" + ....: return x + sage: t = T() + sage: t.g(2) + 4 + sage: t.g.__doc__ + 'My little method' + + The bug described in :trac:`11734` is fixed:: + + sage: def square(f): + ....: @sage_wraps(f) + ....: def new_f(x): + ....: return f(x)*f(x) + ....: return new_f + sage: f = lambda x:x^2 + sage: g = square(f) + sage: g(3) # this line used to fail for some people if these command were manually entered on the sage prompt + 81 + + """ + #TRAC 9919: Workaround for bug in @update_wrapper when used with + #non-function callables. + assigned = set(assigned).intersection(set(dir(wrapped))) + #end workaround + + def f(wrapper, assigned=assigned, updated=updated): + update_wrapper(wrapper, wrapped, assigned=assigned, updated=updated) + # For backwards-compatibility with old versions of sage_wraps + wrapper.f = wrapped + # For forwards-compatibility with functools.wraps on Python 3 + wrapper.__wrapped__ = wrapped + wrapper._sage_src_ = lambda: sage_getsource(wrapped) + wrapper._sage_src_lines_ = lambda: sage_getsourcelines(wrapped) + #Getting the signature right in documentation by Sphinx (Trac 9976) + #The attribute _sage_argspec_() is read by Sphinx if present and used + #as the argspec of the function instead of using reflection. + wrapper._sage_argspec_ = lambda: sage_getargspec(wrapped) + return wrapper + return f + + # Infix operator decorator class infix_operator(object): """ @@ -159,6 +302,54 @@ def _right(self, left): return self.function(left, self.right) +@deprecated(30884, 'replaced by the decorator library') +def decorator_defaults(func): + """ + This function allows a decorator to have default arguments. + + Normally, a decorator can be called with or without arguments. + However, the two cases call for different types of return values. + If a decorator is called with no parentheses, it should be run + directly on the function. However, if a decorator is called with + parentheses (i.e., arguments), then it should return a function + that is then in turn called with the defined function as an + argument. + + This decorator allows us to have these default arguments without + worrying about the return type. + + EXAMPLES:: + + sage: from sage.misc.decorators import decorator_defaults + sage: @decorator_defaults + ....: def my_decorator(f,*args,**kwds): + ....: print(kwds) + ....: print(args) + ....: print(f.__name__) + + sage: @my_decorator + ....: def my_fun(a,b): + ....: return a,b + {} + () + my_fun + sage: @my_decorator(3,4,c=1,d=2) + ....: def my_fun(a,b): + ....: return a,b + {'c': 1, 'd': 2} + (3, 4) + my_fun + """ + @sage_wraps(func) + def my_wrap(*args, **kwds): + if len(kwds) == 0 and len(args) == 1: + # call without parentheses + return func(*args) + else: + return lambda f: func(f, *args, **kwds) + return my_wrap + + class suboptions(object): def __init__(self, name, **options): """ @@ -301,7 +492,7 @@ def wrapper(func, *args, **kwds): options['__original_opts'] = kwds options.update(kwds) return func(*args, **options) - + #Add the options specified by @options to the signature of the wrapped #function in the Sphinx-generated documentation (Trac 9976), using the #special attribute _sage_argspec_ (see e.g. sage.misc.sageinspect) @@ -453,3 +644,84 @@ def wrapper(func, *args, **kwds): return func(*args, **kwds) return decorator(wrapper) + + +@deprecated(30884, 'not used anymore') +class specialize: + r""" + A decorator generator that returns a decorator that in turn + returns a specialized function for function ``f``. In other words, + it returns a function that acts like ``f`` with arguments + ``*args`` and ``**kwargs`` supplied. + + INPUT: + + - ``*args``, ``**kwargs`` -- arguments to specialize the function for. + + OUTPUT: + + - a decorator that accepts a function ``f`` and specializes it + with ``*args`` and ``**kwargs`` + + EXAMPLES:: + + sage: f = specialize(5)(lambda x, y: x+y) + sage: f(10) + 15 + sage: f(5) + 10 + sage: @specialize("Bon Voyage") + ....: def greet(greeting, name): + ....: print("{0}, {1}!".format(greeting, name)) + sage: greet("Monsieur Jean Valjean") + Bon Voyage, Monsieur Jean Valjean! + sage: greet(name = 'Javert') + Bon Voyage, Javert! + """ + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + def __call__(self, f): + return sage_wraps(f)(partial(f, *self.args, **self.kwargs)) + + +@deprecated(30884, 'replaced by the decorator library') +def decorator_keywords(func): + r""" + A decorator for decorators with optional keyword arguments. + + EXAMPLES:: + + sage: from sage.misc.decorators import decorator_keywords + sage: @decorator_keywords + ....: def preprocess(f=None, processor=None): + ....: def wrapper(*args, **kwargs): + ....: if processor is not None: + ....: args, kwargs = processor(*args, **kwargs) + ....: return f(*args, **kwargs) + ....: return wrapper + + This decorator can be called with and without arguments:: + + sage: @preprocess + ....: def foo(x): return x + sage: foo(None) + sage: foo(1) + 1 + + sage: def normalize(x): return ((0,),{}) if x is None else ((x,),{}) + sage: @preprocess(processor=normalize) + ....: def foo(x): return x + sage: foo(None) + 0 + sage: foo(1) + 1 + """ + @sage_wraps(func) + def wrapped(f=None, **kwargs): + if f is None: + return sage_wraps(func)(lambda f:func(f, **kwargs)) + else: + return func(f, **kwargs) + return wrapped diff --git a/src/sage/misc/superseded.py b/src/sage/misc/superseded.py index 35382bb443c..ac6d3265538 100644 --- a/src/sage/misc/superseded.py +++ b/src/sage/misc/superseded.py @@ -101,6 +101,38 @@ def deprecation(trac_number, message, stacklevel=4): """ warning(trac_number, message, DeprecationWarning, stacklevel) +def deprecated(trac_number, message, stacklevel=4): + """ + Decorator that issues a deprecation warning on use. + + INPUT: + + - ``trac_number`` -- integer. The trac ticket number where the + deprecation is introduced. + + - ``message`` -- string. An explanation why things are deprecated + and by what it should be replaced. + + - ``stack_level`` -- (default: ``4``) an integer. This is passed on to + :func:`warnings.warn`. + + EXAMPLES:: + + sage: @sage.misc.superseded.deprecated(13109, 'the function foo is replaced by bar') + sage: def foo(): pass + sage: foo() + doctest:...: DeprecationWarning: the function foo is replaced by bar + See http://trac.sagemath.org/13109 for details. + + .. SEEALSO:: + + :func:`deprecation` + """ + def wrapper(func, *args, **kw): + deprecation(trac_number, message, stacklevel) + func(args, kw) + return decorator(wrapper) + def warning(trac_number, message, warning_class=Warning, stacklevel=3): r"""