Decorators - Utilities

Skip to content
Back to top
View this page
Edit this page

# Decorators

Python decorators for use in Sage.

AUTHORS:

- 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) – improve introspection of sage_wraps. Put this file into the reference manual.

- Julian Rueth (2014-03-19): added `decorator_keywords` decorator

sage.misc.decorators.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

      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

  Python

      >>> from sage.all import *
      >>> from sage.misc.decorators import decorator_defaults
      >>> @decorator_defaults
      ... def my_decorator(f, *args, **kwds):
      ...   print(kwds)
      ...   print(args)
      ...   print(f.__name__)

      >>> @my_decorator
      ... def my_fun(a, b):
      ...   return a,b
      {}
      ()
      my_fun
      >>> @my_decorator(Integer(3),Integer(4),c=Integer(1),d=Integer(2))
      ... def my_fun(a, b):
      ...   return a,b
      {'c': 1, 'd': 2}
      (3, 4)
      my_fun

sage.misc.decorators.decorator_keywords(func)

  A decorator for decorators with optional keyword arguments.

  EXAMPLES:

  Sage

      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

  Python

      >>> from sage.all import *
      >>> from sage.misc.decorators import decorator_keywords
      >>> @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

      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

  [Python]

      >>> from sage.all import *
      >>> @preprocess
      ... def foo(x): return x
      >>> foo(None)
      >>> foo(Integer(1))
      1

      >>> def normalize(x): return ((Integer(0),),{}) if x is None else ((x,),{})
      >>> @preprocess(processor=normalize)
      ... def foo(x): return x
      >>> foo(None)
      0
      >>> foo(Integer(1))
      1

class sage.misc.decorators.infix_operator(precedence)

  Bases: `object`

  A decorator for functions which allows for a hack that makes the function behave like an infix operator.

  This decorator exists as a convenience for interactive use.

  EXAMPLES:

  An infix dot product operator:

  Sage

      sage: @infix_operator('multiply')
      ....: def dot(a, b):
      ....:     '''Dot product.'''
      ....:     return a.dot_product(b)
      sage: u = vector([1, 2, 3])                                                     # needs sage.modules
      sage: v = vector([5, 4, 3])                                                     # needs sage.modules
      sage: u *dot* v                                                                 # needs sage.modules
      22

  Python

      >>> from sage.all import *
      >>> @infix_operator('multiply')
      ... def dot(a, b):
      ...     '''Dot product.'''
      ...     return a.dot_product(b)
      >>> u = vector([Integer(1), Integer(2), Integer(3)])                                                     # needs sage.modules
      >>> v = vector([Integer(5), Integer(4), Integer(3)])                                                     # needs sage.modules
      >>> u *dot* v                                                                 # needs sage.modules
      22

  An infix element-wise addition operator:

  Sage

      sage: # needs sage.modules
      sage: @infix_operator('add')
      ....: def eadd(a, b):
      ....:   return a.parent([i + j for i, j in zip(a, b)])
      sage: u = vector([1, 2, 3])
      sage: v = vector([5, 4, 3])
      sage: u +eadd+ v
      (6, 6, 6)
      sage: 2*u +eadd+ v
      (7, 8, 9)

  [Python]

      >>> from sage.all import *
      >>> # needs sage.modules
      >>> @infix_operator('add')
      ... def eadd(a, b):
      ...   return a.parent([i + j for i, j in zip(a, b)])
      >>> u = vector([Integer(1), Integer(2), Integer(3)])
      >>> v = vector([Integer(5), Integer(4), Integer(3)])
      >>> u +eadd+ v
      (6, 6, 6)
      >>> Integer(2)*u +eadd+ v
      (7, 8, 9)

  A hack to simulate a postfix operator:

  Sage

      sage: @infix_operator('or')
      ....: def thendo(a, b):
      ....:     return b(a)
      sage: x |thendo| cos |thendo| (lambda x: x^2)                                   # needs sage.symbolic
      cos(x)^2

  Python

      >>> from sage.all import *
      >>> @infix_operator('or')
      ... def thendo(a, b):
      ...     return b(a)
      >>> x |thendo| cos |thendo| (lambda x: x**Integer(2))                                   # needs sage.symbolic
      cos(x)^2

  operators = {'add': {'left': '__add__', 'right': '__radd__'}, 'multiply': {'left': '__mul__', 'right': '__rmul__'}, 'or': {'left': '__or__', 'right': '__ror__'}}

class sage.misc.decorators.options(**options)

  Bases: `object`

  A decorator for functions which allows for default options to be set and reset by the end user. Additionally, if one needs to, one can get at the original keyword arguments passed into the decorator.

class sage.misc.decorators.rename_keyword(deprecated=None, deprecation=None, **renames)

  Bases: `object`

  A decorator which renames keyword arguments and optionally deprecates the new keyword.

  INPUT:

  - `deprecation` – integer; the github issue number where the deprecation was introduced

  - the rest of the arguments is a list of keyword arguments in the form `renamed_option='existing_option'`. This will have the effect of renaming `renamed_option` so that the function only sees `existing_option`. If both `renamed_option` and `existing_option` are passed to the function, `existing_option` will override the `renamed_option` value.

  EXAMPLES:

  Sage

      sage: from sage.misc.decorators import rename_keyword
      sage: r = rename_keyword(color='rgbcolor')
      sage: r.renames
      {'color': 'rgbcolor'}
      sage: loads(dumps(r)).renames
      {'color': 'rgbcolor'}

  Python

      >>> from sage.all import *
      >>> from sage.misc.decorators import rename_keyword
      >>> r = rename_keyword(color='rgbcolor')
      >>> r.renames
      {'color': 'rgbcolor'}
      >>> loads(dumps(r)).renames
      {'color': 'rgbcolor'}

  To deprecate an old keyword:

  Sage

      sage: r = rename_keyword(deprecation=13109, color='rgbcolor')

  [Python]

      >>> from sage.all import *
      >>> r = rename_keyword(deprecation=Integer(13109), color='rgbcolor')

sage.misc.decorators.sage_wraps(wrapped, assigned=('__module__', '__name__', '__qualname__', '__doc__', '__annotate__', '__type_params__'), updated=('__dict__',))

  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):

  Sage

      sage: import cython
      sage: def square(f):
      ....:     @sage_wraps(f)
      ....:     def new_f(x):
      ....:         return f(x)*f(x)
      ....:     return new_f

  Python

      >>> from sage.all import *
      >>> import cython
      >>> def square(f):
      ...     @sage_wraps(f)
      ...     def new_f(x):
      ...         return f(x)*f(x)
      ...     return new_f

  EXAMPLES:

  Demonstrate that documentation string and source are retained from the decorated function:

  Sage

      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)                                                                      # needs sage.symbolic
      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)...'

  [Python]

      >>> from sage.all import *
      >>> def square(f):
      ...     @sage_wraps(f)
      ...     def new_f(x):
      ...         return f(x)*f(x)
      ...     return new_f
      >>> @square
      ... def g(x):
      ...     "My little function"
      ...     return x
      >>> g(Integer(2))
      4
      >>> g(x)                                                                      # needs sage.symbolic
      x^2
      >>> g.__doc__
      'My little function'
      >>> from sage.misc.sageinspect import sage_getsource, sage_getsourcelines, sage_getfile
      >>> 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 Issue #9976):

  Sage

      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)
      FullArgSpec(args=['x'], varargs=None, varkw=None, defaults=None,
                  kwonlyargs=[], kwonlydefaults=None, annotations={})

  Python

      >>> from sage.all import *
      >>> def diff_arg_dec(f):
      ...     @sage_wraps(f)
      ...     def new_f(y, some_def_arg=Integer(2)):
      ...         return f(y+some_def_arg)
      ...     return new_f
      >>> @diff_arg_dec
      ... def g(x):
      ...     return x
      >>> g(Integer(1))
      3
      >>> g(Integer(1), some_def_arg=Integer(4))
      5
      >>> from sage.misc.sageinspect import sage_getargspec
      >>> sage_getargspec(g)
      FullArgSpec(args=['x'], varargs=None, varkw=None, defaults=None,
                  kwonlyargs=[], kwonlydefaults=None, annotations={})

  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

      sage: P.<x,y> = 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)                                 # needs sage.libs.singular
      (['    @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'], ...)

  [Python]

      >>> from sage.all import *
      >>> P = QQ['x, y']; (x, y,) = P._first_ngens(2)
      >>> I = P*[x,y]
      >>> sage_getfile(I.interreduced_basis)       # known bug
      '.../sage/rings/polynomial/multi_polynomial_ideal.py'
      >>> sage_getsourcelines(I.interreduced_basis)                                 # needs sage.libs.singular
      (['    @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

      sage: foo = object()
      sage: @sage_wraps(foo)
      ....: def func():
      ....:     pass
      sage: wrapped = sage_wraps(foo)(func)
      sage: wrapped.f is foo
      True

  Python

      >>> from sage.all import *
      >>> foo = object()
      >>> @sage_wraps(foo)
      ... def func():
      ...     pass
      >>> wrapped = sage_wraps(foo)(func)
      >>> wrapped.f is foo
      True

  Demonstrate that sage_wraps works for non-function callables (Issue #9919):

  Sage

      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'

  [Python]

      >>> from sage.all import *
      >>> def square_for_met(f):
      ...   @sage_wraps(f)
      ...   def new_f(self, x):
      ...       return f(self,x)*f(self,x)
      ...   return new_f
      >>> class T:
      ...   @square_for_met
      ...   def g(self, x):
      ...       "My little method"
      ...       return x
      >>> t = T()
      >>> t.g(Integer(2))
      4
      >>> t.g.__doc__
      'My little method'

  The bug described in Issue #11734 is fixed:

  Sage

      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

  Python

      >>> from sage.all import *
      >>> def square(f):
      ...     @sage_wraps(f)
      ...     def new_f(x):
      ...         return f(x)*f(x)
      ...     return new_f
      >>> f = lambda x:x**Integer(2)
      >>> g = square(f)
      >>> g(Integer(3))  # this line used to fail for some people if these command were manually entered on the sage prompt
      81

class sage.misc.decorators.specialize(*args, **kwargs)

  Bases: `object`

  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

      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!

  Python

      >>> from sage.all import *
      >>> f = specialize(Integer(5))(lambda x, y: x+y)
      >>> f(Integer(10))
      15
      >>> f(Integer(5))
      10
      >>> @specialize("Bon Voyage")
      ... def greet(greeting, name):
      ...     print("{0}, {1}!".format(greeting, name))
      >>> greet("Monsieur Jean Valjean")
      Bon Voyage, Monsieur Jean Valjean!
      >>> greet(name = 'Javert')
      Bon Voyage, Javert!

class sage.misc.decorators.suboptions(name, **options)

  Bases: `object`

  A decorator for functions which collects all keywords starting with `name+'_'` and collects them into a dictionary which will be passed on to the wrapped function as a dictionary called `name_options`.

  The keyword arguments passed into the constructor are taken to be default for the `name_options` dictionary.

  EXAMPLES:

  Sage

      sage: from sage.misc.decorators import suboptions
      sage: s = suboptions('arrow', size=2)
      sage: s.name
      'arrow_'
      sage: s.options
      {'size': 2}

  Python

      >>> from sage.all import *
      >>> from sage.misc.decorators import suboptions
      >>> s = suboptions('arrow', size=Integer(2))
      >>> s.name
      'arrow_'
      >>> s.options
      {'size': 2}