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