Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/misc/decorators.py
8814 views
1
"""
2
Decorators
3
4
Python decorators for use in Sage.
5
6
AUTHORS:
7
8
- Tim Dumol (5 Dec 2009) -- initial version.
9
- Johan S. R. Nielsen (2010) -- collect decorators from various modules.
10
- Johan S. R. Nielsen (8 apr 2011) -- improve introspection on decorators.
11
- Simon King (2011-05-26) -- improve introspection of sage_wraps. Put this
12
file into the reference manual.
13
14
"""
15
from functools import (partial, update_wrapper, WRAPPER_ASSIGNMENTS,
16
WRAPPER_UPDATES)
17
from copy import copy
18
from sage.misc.sageinspect import (sage_getsource, sage_getsourcelines,
19
sage_getargspec)
20
from inspect import ArgSpec
21
22
23
def sage_wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
24
r"""
25
Decorator factory which should be used in decorators for making sure that
26
meta-information on the decorated callables are retained through the
27
decorator, such that the introspection functions of
28
``sage.misc.sageinspect`` retrieves them correctly. This includes
29
documentation string, source, and argument specification. This is an
30
extension of the Python standard library decorator functools.wraps.
31
32
That the argument specification is retained from the decorated functions
33
implies, that if one uses ``sage_wraps`` in a decorator which intentionally
34
changes the argument specification, one should add this information to
35
the special attribute ``_sage_argspec_`` of the wrapping function (for an
36
example, see e.g. ``@options`` decorator in this module).
37
38
EXAMPLES:
39
40
Demonstrate that documentation string and source are retained from the
41
decorated function::
42
43
sage: def square(f):
44
... @sage_wraps(f)
45
... def new_f(x):
46
... return f(x)*f(x)
47
... return new_f
48
sage: @square
49
... def g(x):
50
... "My little function"
51
... return x
52
sage: g(2)
53
4
54
sage: g(x)
55
x^2
56
sage: g.__doc__
57
'My little function'
58
sage: from sage.misc.sageinspect import sage_getsource, sage_getsourcelines, sage_getfile
59
sage: sage_getsource(g)
60
'@square...def g(x)...'
61
62
Demonstrate that the argument description are retained from the
63
decorated function through the special method (when left
64
unchanged) (see :trac:`9976`)::
65
66
sage: def diff_arg_dec(f):
67
... @sage_wraps(f)
68
... def new_f(y, some_def_arg=2):
69
... return f(y+some_def_arg)
70
... return new_f
71
sage: @diff_arg_dec
72
... def g(x):
73
... return x
74
sage: g(1)
75
3
76
sage: g(1, some_def_arg=4)
77
5
78
sage: from sage.misc.sageinspect import sage_getargspec
79
sage: sage_getargspec(g)
80
ArgSpec(args=['x'], varargs=None, keywords=None, defaults=None)
81
82
Demonstrate that it correctly gets the source lines and the source
83
file, which is essential for interactive code edition; note that we
84
do not test the line numbers, as they may easily change::
85
86
sage: P.<x,y> = QQ[]
87
sage: I = P*[x,y]
88
sage: sage_getfile(I.interreduced_basis)
89
'...sage/rings/polynomial/multi_polynomial_ideal.py'
90
sage: sage_getsourcelines(I.interreduced_basis)
91
([' @singular_standard_options\n',
92
' @libsingular_standard_options\n',
93
' def interreduced_basis(self):\n',
94
...
95
' return ret\n'], ...)
96
97
Demonstrate that sage_wraps works for non-function callables
98
(:trac:`9919`)::
99
100
sage: def square_for_met(f):
101
... @sage_wraps(f)
102
... def new_f(self, x):
103
... return f(self,x)*f(self,x)
104
... return new_f
105
sage: class T:
106
... @square_for_met
107
... def g(self, x):
108
... "My little method"
109
... return x
110
sage: t = T()
111
sage: t.g(2)
112
4
113
sage: t.g.__doc__
114
'My little method'
115
116
The bug described in :trac:`11734` is fixed::
117
118
sage: def square(f):
119
... @sage_wraps(f)
120
... def new_f(x):
121
... return f(x)*f(x)
122
... return new_f
123
sage: f = lambda x:x^2
124
sage: g = square(f)
125
sage: g(3) # this line used to fail for some people if these command were manually entered on the sage prompt
126
81
127
128
"""
129
#TRAC 9919: Workaround for bug in @update_wrapper when used with
130
#non-function callables.
131
assigned = set(assigned).intersection(set(dir(wrapped)))
132
#end workaround
133
134
def f(wrapper):
135
update_wrapper(wrapper, wrapped, assigned=assigned, updated=updated)
136
wrapper._sage_src_ = lambda: sage_getsource(wrapped)
137
wrapper._sage_src_lines_ = lambda: sage_getsourcelines(wrapped)
138
#Getting the signature right in documentation by Sphinx (Trac 9976)
139
#The attribute _sage_argspec_() is read by Sphinx if present and used
140
#as the argspec of the function instead of using reflection.
141
wrapper._sage_argspec_ = lambda: sage_getargspec(wrapped)
142
return wrapper
143
return f
144
145
146
# Infix operator decorator
147
class infix_operator(object):
148
"""
149
A decorator for functions which allows for a hack that makes
150
the function behave like an infix operator.
151
152
This decorator exists as a convenience for interactive use.
153
154
EXAMPLES:
155
156
An infix dot product operator::
157
158
sage: def dot(a,b): return a.dot_product(b)
159
sage: dot=infix_operator('multiply')(dot)
160
sage: u=vector([1,2,3])
161
sage: v=vector([5,4,3])
162
sage: u *dot* v
163
22
164
165
An infix element-wise addition operator::
166
167
sage: def eadd(a,b):
168
... return a.parent([i+j for i,j in zip(a,b)])
169
sage: eadd=infix_operator('add')(eadd)
170
sage: u=vector([1,2,3])
171
sage: v=vector([5,4,3])
172
sage: u +eadd+ v
173
(6, 6, 6)
174
sage: 2*u +eadd+ v
175
(7, 8, 9)
176
177
A hack to simulate a postfix operator::
178
179
sage: def thendo(a,b): return b(a)
180
sage: thendo=infix_operator('or')(thendo)
181
sage: x |thendo| cos |thendo| (lambda x: x^2)
182
cos(x)^2
183
"""
184
185
def __init__(self, precedence):
186
"""
187
A decorator for functions which allows for a hack that makes
188
the function behave like an infix operator.
189
190
This decorator exists as a convenience for interactive use.
191
192
EXAMPLES::
193
194
sage: def dot(a,b): return a.dot_product(b)
195
sage: dot=infix_operator('multiply')(dot)
196
sage: u=vector([1,2,3])
197
sage: v=vector([5,4,3])
198
sage: u *dot* v
199
22
200
201
sage: from sage.misc.decorators import infix_operator
202
sage: def eadd(a,b):
203
... return a.parent([i+j for i,j in zip(a,b)])
204
sage: eadd=infix_operator('add')(eadd)
205
sage: u=vector([1,2,3])
206
sage: v=vector([5,4,3])
207
sage: u +eadd+ v
208
(6, 6, 6)
209
sage: 2*u +eadd+ v
210
(7, 8, 9)
211
212
sage: from sage.misc.decorators import infix_operator
213
sage: def thendo(a,b): return b(a)
214
sage: thendo=infix_operator('or')(thendo)
215
sage: x |thendo| cos |thendo| (lambda x: x^2)
216
cos(x)^2
217
"""
218
self.precedence = precedence
219
220
operators = {'add': {'left': '__add__', 'right': '__radd__'},
221
'multiply': {'left': '__mul__', 'right': '__rmul__'},
222
'or': {'left': '__or__', 'right': '__ror__'},
223
}
224
225
def __call__(self, func):
226
"""
227
Returns a function which acts as an inline operator.
228
229
EXAMPLES::
230
231
sage: from sage.misc.decorators import infix_operator
232
sage: def dot(a,b): return a.dot_product(b)
233
sage: dot=infix_operator('multiply')(dot)
234
sage: u=vector([1,2,3])
235
sage: v=vector([5,4,3])
236
sage: u *dot* v
237
22
238
239
sage: def eadd(a,b):
240
... return a.parent([i+j for i,j in zip(a,b)])
241
sage: eadd=infix_operator('add')(eadd)
242
sage: u=vector([1,2,3])
243
sage: v=vector([5,4,3])
244
sage: u +eadd+ v
245
(6, 6, 6)
246
sage: 2*u +eadd+ v
247
(7, 8, 9)
248
249
sage: def thendo(a,b): return b(a)
250
sage: thendo=infix_operator('or')(thendo)
251
sage: x |thendo| cos |thendo| (lambda x: x^2)
252
cos(x)^2
253
"""
254
def left_func(self, right):
255
"""
256
The function for the operation on the left (e.g., __add__).
257
258
EXAMPLES::
259
260
sage: def dot(a,b): return a.dot_product(b)
261
sage: dot=infix_operator('multiply')(dot)
262
sage: u=vector([1,2,3])
263
sage: v=vector([5,4,3])
264
sage: u *dot* v
265
22
266
"""
267
268
if self.left is None:
269
if self.right is None:
270
new = copy(self)
271
new.right = right
272
return new
273
else:
274
raise SyntaxError("Infix operator already has its "
275
"right argument")
276
else:
277
return self.function(self.left, right)
278
279
def right_func(self, left):
280
"""
281
The function for the operation on the right (e.g., __radd__).
282
283
EXAMPLES::
284
285
sage: def dot(a,b): return a.dot_product(b)
286
sage: dot=infix_operator('multiply')(dot)
287
sage: u=vector([1,2,3])
288
sage: v=vector([5,4,3])
289
sage: u *dot* v
290
22
291
"""
292
if self.right is None:
293
if self.left is None:
294
new = copy(self)
295
new.left = left
296
return new
297
else:
298
raise SyntaxError("Infix operator already has its "
299
"left argument")
300
else:
301
return self.function(left, self.right)
302
303
@sage_wraps(func)
304
class wrapper:
305
def __init__(self, left=None, right=None):
306
"""
307
Initialize the actual infix object, with possibly a
308
specified left and/or right operand.
309
310
EXAMPLES::
311
312
sage: def dot(a,b): return a.dot_product(b)
313
sage: dot=infix_operator('multiply')(dot)
314
sage: u=vector([1,2,3])
315
sage: v=vector([5,4,3])
316
sage: u *dot* v
317
22
318
"""
319
320
self.function = func
321
self.left = left
322
self.right = right
323
324
def __call__(self, *args, **kwds):
325
"""
326
Call the passed function.
327
328
EXAMPLES::
329
330
sage: def dot(a,b): return a.dot_product(b)
331
sage: dot=infix_operator('multiply')(dot)
332
sage: u=vector([1,2,3])
333
sage: v=vector([5,4,3])
334
sage: dot(u,v)
335
22
336
"""
337
return self.function(*args, **kwds)
338
339
setattr(wrapper, self.operators[self.precedence]['left'], left_func)
340
setattr(wrapper, self.operators[self.precedence]['right'], right_func)
341
342
wrapper._sage_src_ = lambda: sage_getsource(func)
343
344
return wrapper()
345
346
347
def decorator_defaults(func):
348
"""
349
This function allows a decorator to have default arguments.
350
351
Normally, a decorator can be called with or without arguments.
352
However, the two cases call for different types of return values.
353
If a decorator is called with no parentheses, it should be run
354
directly on the function. However, if a decorator is called with
355
parentheses (i.e., arguments), then it should return a function
356
that is then in turn called with the defined function as an
357
argument.
358
359
This decorator allows us to have these default arguments without
360
worrying about the return type.
361
362
EXAMPLES::
363
364
sage: from sage.misc.decorators import decorator_defaults
365
sage: @decorator_defaults
366
... def my_decorator(f,*args,**kwds):
367
... print kwds
368
... print args
369
... print f.__name__
370
...
371
sage: @my_decorator
372
... def my_fun(a,b):
373
... return a,b
374
...
375
{}
376
()
377
my_fun
378
sage: @my_decorator(3,4,c=1,d=2)
379
... def my_fun(a,b):
380
... return a,b
381
...
382
{'c': 1, 'd': 2}
383
(3, 4)
384
my_fun
385
"""
386
@sage_wraps(func)
387
def my_wrap(*args, **kwds):
388
if len(kwds) == 0 and len(args) == 1:
389
# call without parentheses
390
return func(*args)
391
else:
392
return lambda f: func(f, *args, **kwds)
393
return my_wrap
394
395
396
class suboptions(object):
397
def __init__(self, name, **options):
398
"""
399
A decorator for functions which collects all keywords
400
starting with ``name+'_'`` and collects them into a dictionary
401
which will be passed on to the wrapped function as a
402
dictionary called ``name_options``.
403
404
The keyword arguments passed into the constructor are taken
405
to be default for the ``name_options`` dictionary.
406
407
EXAMPLES::
408
409
sage: from sage.misc.decorators import suboptions
410
sage: s = suboptions('arrow', size=2)
411
sage: s.name
412
'arrow_'
413
sage: s.options
414
{'size': 2}
415
"""
416
self.name = name + "_"
417
self.options = options
418
419
def __call__(self, func):
420
"""
421
Returns a wrapper around func
422
423
EXAMPLES::
424
425
sage: from sage.misc.decorators import suboptions
426
sage: def f(*args, **kwds): print list(sorted(kwds.items()))
427
sage: f = suboptions('arrow', size=2)(f)
428
sage: f(size=2)
429
[('arrow_options', {'size': 2}), ('size', 2)]
430
sage: f(arrow_size=3)
431
[('arrow_options', {'size': 3})]
432
sage: f(arrow_options={'size':4})
433
[('arrow_options', {'size': 4})]
434
sage: f(arrow_options={'size':4}, arrow_size=5)
435
[('arrow_options', {'size': 5})]
436
437
Demonstrate that the introspected argument specification of the
438
wrapped function is updated (see :trac:`9976`).
439
440
sage: from sage.misc.sageinspect import sage_getargspec
441
sage: sage_getargspec(f)
442
ArgSpec(args=['arrow_size'], varargs='args', keywords='kwds', defaults=(2,))
443
"""
444
@sage_wraps(func)
445
def wrapper(*args, **kwds):
446
suboptions = copy(self.options)
447
suboptions.update(kwds.pop(self.name+"options", {}))
448
449
#Collect all the relevant keywords in kwds
450
#and put them in suboptions
451
for key, value in kwds.items():
452
if key.startswith(self.name):
453
suboptions[key[len(self.name):]] = value
454
del kwds[key]
455
456
kwds[self.name + "options"] = suboptions
457
458
return func(*args, **kwds)
459
460
#Add the options specified by @options to the signature of the wrapped
461
#function in the Sphinx-generated documentation (Trac 9976), using the
462
#special attribute _sage_argspec_ (see e.g. sage.misc.sageinspect)
463
def argspec():
464
argspec = sage_getargspec(func)
465
466
def listForNone(l):
467
return l if not l is None else []
468
newArgs = [self.name + opt for opt in self.options.keys()]
469
args = (argspec.args if not argspec.args is None else []) + newArgs
470
defaults = (argspec.defaults if not argspec.defaults is None else ()) \
471
+ tuple(self.options.values())
472
#Note: argspec.defaults is not always a tuple for some reason
473
return ArgSpec(args, argspec.varargs, argspec.keywords, defaults)
474
wrapper._sage_argspec_ = argspec
475
476
return wrapper
477
478
479
class options(object):
480
def __init__(self, **options):
481
"""
482
A decorator for functions which allows for default options to be
483
set and reset by the end user. Additionally, if one needs to, one
484
can get at the original keyword arguments passed into the
485
decorator.
486
487
TESTS::
488
489
sage: from sage.misc.decorators import options
490
sage: o = options(rgbcolor=(0,0,1))
491
sage: o.options
492
{'rgbcolor': (0, 0, 1)}
493
sage: o = options(rgbcolor=(0,0,1), __original_opts=True)
494
sage: o.original_opts
495
True
496
sage: loads(dumps(o)).options
497
{'rgbcolor': (0, 0, 1)}
498
499
Demonstrate that the introspected argument specification of the wrapped
500
function is updated (see :trac:`9976`)::
501
502
sage: from sage.misc.decorators import options
503
sage: o = options(rgbcolor=(0,0,1))
504
sage: def f(*args, **kwds): print args, list(sorted(kwds.items()))
505
sage: f1 = o(f)
506
sage: from sage.misc.sageinspect import sage_getargspec
507
sage: sage_getargspec(f1)
508
ArgSpec(args=['rgbcolor'], varargs='args', keywords='kwds', defaults=((0, 0, 1),))
509
"""
510
self.options = options
511
self.original_opts = options.pop('__original_opts', False)
512
513
def __call__(self, func):
514
"""
515
EXAMPLES::
516
517
sage: from sage.misc.decorators import options
518
sage: o = options(rgbcolor=(0,0,1))
519
sage: def f(*args, **kwds): print args, list(sorted(kwds.items()))
520
sage: f1 = o(f)
521
sage: f1()
522
() [('rgbcolor', (0, 0, 1))]
523
sage: f1(rgbcolor=1)
524
() [('rgbcolor', 1)]
525
sage: o = options(rgbcolor=(0,0,1), __original_opts=True)
526
sage: f2 = o(f)
527
sage: f2(alpha=1)
528
() [('__original_opts', {'alpha': 1}), ('alpha', 1), ('rgbcolor', (0, 0, 1))]
529
530
"""
531
@sage_wraps(func)
532
def wrapper(*args, **kwds):
533
options = copy(wrapper.options)
534
if self.original_opts:
535
options['__original_opts'] = kwds
536
options.update(kwds)
537
return func(*args, **options)
538
539
#Add the options specified by @options to the signature of the wrapped
540
#function in the Sphinx-generated documentation (Trac 9976), using the
541
#special attribute _sage_argspec_ (see e.g. sage.misc.sageinspect)
542
def argspec():
543
argspec = sage_getargspec(func)
544
args = (argspec.args if not argspec.args is None else []) + self.options.keys()
545
defaults = tuple(argspec.defaults if not argspec.defaults is None else ()) + tuple(self.options.values())
546
#Note: argspec.defaults is not always a tuple for some reason
547
return ArgSpec(args, argspec.varargs, argspec.keywords, defaults)
548
wrapper._sage_argspec_ = argspec
549
550
def defaults():
551
"""
552
Return the default options.
553
554
EXAMPLES::
555
556
sage: from sage.misc.decorators import options
557
sage: o = options(rgbcolor=(0,0,1))
558
sage: def f(*args, **kwds): print args, list(sorted(kwds.items()))
559
sage: f = o(f)
560
sage: f.options['rgbcolor']=(1,1,1)
561
sage: f.defaults()
562
{'rgbcolor': (0, 0, 1)}
563
"""
564
return copy(self.options)
565
566
def reset():
567
"""
568
Reset the options to the defaults.
569
570
EXAMPLES::
571
572
sage: from sage.misc.decorators import options
573
sage: o = options(rgbcolor=(0,0,1))
574
sage: def f(*args, **kwds): print args, list(sorted(kwds.items()))
575
sage: f = o(f)
576
sage: f.options
577
{'rgbcolor': (0, 0, 1)}
578
sage: f.options['rgbcolor']=(1,1,1)
579
sage: f.options
580
{'rgbcolor': (1, 1, 1)}
581
sage: f()
582
() [('rgbcolor', (1, 1, 1))]
583
sage: f.reset()
584
sage: f.options
585
{'rgbcolor': (0, 0, 1)}
586
sage: f()
587
() [('rgbcolor', (0, 0, 1))]
588
"""
589
wrapper.options = copy(self.options)
590
591
wrapper.options = copy(self.options)
592
wrapper.reset = reset
593
wrapper.reset.__doc__ = """
594
Reset the options to the defaults.
595
596
Defaults:
597
%s
598
""" % self.options
599
600
wrapper.defaults = defaults
601
wrapper.defaults.__doc__ = """
602
Return the default options.
603
604
Defaults:
605
%s
606
""" % self.options
607
608
return wrapper
609
610
611
class rename_keyword(object):
612
def __init__(self, deprecated=None, deprecation=None, **renames):
613
"""
614
A decorator which renames keyword arguments and optionally
615
deprecates the new keyword.
616
617
INPUT:
618
619
- ``deprecation`` -- integer. The trac ticket number where the
620
deprecation was introduced.
621
622
- the rest of the arguments is a list of keyword arguments in the
623
form ``renamed_option='existing_option'``. This will have the
624
effect of renaming ``renamed_option`` so that the function only
625
sees ``existing_option``. If both ``renamed_option`` and
626
``existing_option`` are passed to the function, ``existing_option``
627
will override the ``renamed_option`` value.
628
629
EXAMPLES::
630
631
sage: from sage.misc.decorators import rename_keyword
632
sage: r = rename_keyword(color='rgbcolor')
633
sage: r.renames
634
{'color': 'rgbcolor'}
635
sage: loads(dumps(r)).renames
636
{'color': 'rgbcolor'}
637
638
To deprecate an old keyword::
639
640
sage: r = rename_keyword(deprecation=13109, color='rgbcolor')
641
"""
642
assert deprecated is None, 'Use @rename_keyword(deprecation=<trac_number>, ...)'
643
self.renames = renames
644
self.renames = renames
645
self.deprecation = deprecation
646
647
def __call__(self, func):
648
"""
649
Rename keywords.
650
651
EXAMPLES::
652
653
sage: from sage.misc.decorators import rename_keyword
654
sage: r = rename_keyword(color='rgbcolor')
655
sage: def f(*args, **kwds): print args, kwds
656
sage: f = r(f)
657
sage: f()
658
() {}
659
sage: f(alpha=1)
660
() {'alpha': 1}
661
sage: f(rgbcolor=1)
662
() {'rgbcolor': 1}
663
sage: f(color=1)
664
() {'rgbcolor': 1}
665
666
We can also deprecate the renamed keyword::
667
668
sage: r = rename_keyword(deprecation=13109, deprecated_option='new_option')
669
sage: def f(*args, **kwds): print args, kwds
670
sage: f = r(f)
671
sage: f()
672
() {}
673
sage: f(alpha=1)
674
() {'alpha': 1}
675
sage: f(new_option=1)
676
() {'new_option': 1}
677
sage: f(deprecated_option=1)
678
doctest:...: DeprecationWarning: use the option 'new_option' instead of 'deprecated_option'
679
See http://trac.sagemath.org/13109 for details.
680
() {'new_option': 1}
681
"""
682
@sage_wraps(func)
683
def wrapper(*args, **kwds):
684
for old_name, new_name in self.renames.items():
685
if old_name in kwds and new_name not in kwds:
686
if self.deprecation is not None:
687
from sage.misc.superseded import deprecation
688
deprecation(self.deprecation, "use the option "
689
"%r instead of %r" % (new_name, old_name))
690
kwds[new_name] = kwds[old_name]
691
del kwds[old_name]
692
return func(*args, **kwds)
693
694
return wrapper
695
696
697
class specialize:
698
r"""
699
A decorator generator that returns a decorator that in turn
700
returns a specialized function for function ``f``. In other words,
701
it returns a function that acts like ``f`` with arguments
702
``*args`` and ``**kwargs`` supplied.
703
704
INPUT:
705
706
- ``*args``, ``**kwargs`` -- arguments to specialize the function for.
707
708
OUTPUT:
709
710
- a decorator that accepts a function ``f`` and specializes it
711
with ``*args`` and ``**kwargs``
712
713
EXAMPLES::
714
715
sage: f = specialize(5)(lambda x, y: x+y)
716
sage: f(10)
717
15
718
sage: f(5)
719
10
720
sage: @specialize("Bon Voyage")
721
... def greet(greeting, name):
722
... print "{0}, {1}!".format(greeting, name)
723
sage: greet("Monsieur Jean Valjean")
724
Bon Voyage, Monsieur Jean Valjean!
725
sage: greet(name = 'Javert')
726
Bon Voyage, Javert!
727
"""
728
def __init__(self, *args, **kwargs):
729
self.args = args
730
self.kwargs = kwargs
731
732
def __call__(self, f):
733
return sage_wraps(f)(partial(f, *self.args, **self.kwargs))
734
735