Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/misc/function_mangling.pyx
4036 views
1
# Copyright (c) 2009, Tom Boothby <[email protected]>
2
# All rights reserved.
3
#
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are met:
6
# * Redistributions of source code must retain the above copyright
7
# notice, this list of conditions and the following disclaimer.
8
# * Redistributions in binary form must reproduce the above copyright
9
# notice, this list of conditions and the following disclaimer in the
10
# documentation and/or other materials provided with the distribution.
11
# * Neither the name of Sage nor the
12
# names of its contributors may be used to endorse or promote products
13
# derived from this software without specific prior written permission.
14
#
15
# THIS SOFTWARE IS PROVIDED BY Tom Boothby ''AS IS'' AND ANY
16
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
# DISCLAIMED. IN NO EVENT SHALL Tom Boothby BE LIABLE FOR ANY
19
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26
"""
27
28
Function Mangling
29
30
This module provides utilities for extracting information about python
31
functions.
32
33
AUTHORS:
34
35
- Tom Boothby (2009): Original version in Python
36
- Simon King (2011): Use Cython. Speedup of ``fix_to_pos``, cleaning documentation.
37
38
"""
39
40
41
cdef class ArgumentFixer:
42
"""
43
This class provides functionality to normalize the arguments
44
passed into a function. While the various ways of calling a
45
function are perfectly equivalent from the perspective of the
46
callee, they don't always look the same for an object
47
watching the caller. For example,
48
::
49
50
sage: def f(x = 10):
51
... return min(1,x)
52
53
the following calls are equivalent,
54
::
55
56
sage: f()
57
1
58
sage: f(10)
59
1
60
sage: f(x=10)
61
1
62
63
but from the perspective of a wrapper, they are different::
64
65
sage: def wrap(g):
66
... def _g(*args,**kwargs):
67
... print args, kwargs
68
... return g(*args, **kwargs)
69
... return _g
70
sage: h = wrap(f)
71
sage: t = h()
72
() {}
73
sage: t = h(10)
74
(10,) {}
75
sage: t = h(x=10)
76
() {'x': 10}
77
78
For the purpose of cached functions, it is important not
79
to distinguish between these uses.
80
81
INPUTS:
82
83
- f -- a function
84
- classmethod -- boolean (default False) -- True if the function
85
is a classmethod and therefore the first
86
argument is expected to be the class instance.
87
In that case, we ignore the first argument.
88
89
EXAMPLES::
90
91
sage: from sage.misc.function_mangling import ArgumentFixer
92
sage: def wrap2(g):
93
... af = ArgumentFixer(g)
94
... def _g(*args, **kwargs):
95
... print af.fix_to_pos()
96
... return g(*args,**kwargs)
97
... return _g
98
sage: h2 = wrap2(f)
99
sage: t = h2()
100
((10,), ())
101
sage: t = h2(10)
102
((10,), ())
103
sage: t = h2(x=10)
104
((10,), ())
105
106
::
107
108
sage: class one:
109
... def __init__(self, x = 1):
110
... self.x = x
111
sage: af = ArgumentFixer(one.__init__.im_func, classmethod=True)
112
sage: af.fix_to_pos(1,2,3,a=31,b=2,n=3)
113
((1, 2, 3), (('a', 31), ('b', 2), ('n', 3)))
114
115
"""
116
117
cdef public object f
118
cdef public int _ndefault
119
cdef public int _nargs
120
cdef tuple _arg_names
121
cdef bint _classmethod
122
cdef dict _defaults
123
cdef public tuple _default_tuple
124
def __init__(self, f, classmethod = False):
125
from sage.misc.sageinspect import sage_getargspec
126
arg_names, varargs, varkw, defaults = sage_getargspec(f)
127
if defaults is None:
128
self._default_tuple = defaults = ()
129
else:
130
self._default_tuple = tuple(defaults)
131
132
#code = f.func_code
133
134
self.f = f
135
self._ndefault = len(defaults)
136
if classmethod:
137
self._nargs = len(arg_names)-1 #code.co_argcount-1
138
self._arg_names = tuple(arg_names[1:]) #code.co_varnames[1:self._nargs+1]
139
else:
140
self._nargs = len(arg_names) #code.co_argcount
141
self._arg_names = tuple(arg_names) #code.co_varnames[:self._nargs]
142
self._classmethod = classmethod
143
144
cdef dict default_map
145
self._defaults = default_map = {}
146
for k,v in zip(self._arg_names[-self._ndefault:], defaults):
147
default_map[k] = v
148
149
def __repr__(self):
150
"""
151
EXAMPLES::
152
153
sage: from sage.misc.function_mangling import ArgumentFixer
154
sage: g = ArgumentFixer(number_of_partitions)
155
sage: g
156
Argument Fixer of <function number_of_partitions at 0x...>
157
"""
158
return "Argument Fixer of %s"%self.f
159
160
def fix_to_named(self, *args,**kwargs):
161
"""
162
Normalize the arguments with a preference for named arguments.
163
164
INPUT:
165
166
- any positional and named arguments.
167
168
OUTPUT:
169
170
We return a tuple
171
172
`(e_1, e_2, ..., e_k), ((n_1, v_1), ... , (n_m, v_m))`
173
174
where `n_1, ... , n_m` are the names of the arguments and
175
`v_1, ..., v_m` are the values passed in; and `e_1, ..., e_k` are
176
the unnamed arguments. We minimize `k`.
177
178
The defaults are extracted from the function and filled
179
into the list ``K`` of named arguments. The names `n_1, ..., n_t`
180
are in order of the function definition, where `t` is the number
181
of named arguments. The remaining names, `n_{t+1}, ..., n_m` are
182
given in alphabetical order. This is useful to extract
183
the names of arguments, but **does not** maintain
184
equivalence of
185
::
186
187
A,K = self.fix_to_pos(...)
188
self.f(*A,**dict(K))`
189
190
and
191
::
192
193
self.f(...)
194
195
in all cases.
196
197
EXAMPLE::
198
199
sage: from sage.misc.function_mangling import ArgumentFixer
200
sage: def sum3(a,b,c=3,*args,**kwargs):
201
... return a+b+c
202
sage: AF = ArgumentFixer(sum3)
203
sage: AF.fix_to_named(1,2,3,4,5,6,f=14,e=16)
204
((4, 5, 6), (('a', 1), ('b', 2), ('c', 3), ('e', 16), ('f', 14)))
205
sage: AF.fix_to_named(1,2,f=14)
206
((), (('a', 1), ('b', 2), ('c', 3), ('f', 14)))
207
208
"""
209
cdef list ARGS = []
210
cdef tuple arg_names = self._arg_names
211
cdef int lenargs = len(args)
212
cdef dict defaults = self._defaults
213
cdef int i
214
cdef dict kwargs_ = dict(kwargs)
215
for i from 0<=i<self._nargs:
216
name = arg_names[i]
217
if i >= lenargs:
218
if name in kwargs_:
219
val = kwargs_[name]
220
del kwargs_[name]
221
else:
222
val = defaults[name]
223
else:
224
val = args[i]
225
ARGS.append((name,val))
226
extra_args = args[self._nargs:]
227
for k in sorted(kwargs_.keys()):
228
ARGS.append((k,kwargs_[k]))
229
return tuple(extra_args), tuple(ARGS)
230
231
cpdef tuple defaults_to_pos(self, tuple Args):
232
cdef int lenargs = len(Args)
233
cdef int nargs = self._nargs
234
if lenargs>=nargs:
235
return Args, ()
236
return Args+self._default_tuple[-nargs+lenargs:],()
237
238
def fix_to_pos(self, *args, **kwds):
239
"""
240
Normalize the arguments with a preference for positional arguments.
241
242
INPUT:
243
244
Any positional or named arguments
245
246
OUTPUT:
247
248
We return a tuple
249
250
`(e_1, e_2, ..., e_k), ((n_1, v_1), ... , (n_m, v_m))`
251
252
where `n_1, ... , n_m` are the names of the arguments and
253
`v_1, ..., v_m` are the values passed in; and `e_1, ..., e_k`
254
are the unnamed arguments. We minimize `m`.
255
256
The commands
257
::
258
259
A,K = self.fix_to_pos(...)
260
self.f(*A,**dict(K))
261
262
are equivalent to
263
::
264
265
self.f(...)
266
267
though defaults are extracted from the function and
268
appended to the tuple ``A`` of positional arguments.
269
The names `n_1, ..., n_m` are given in alphabetical
270
order.
271
272
EXAMPLE::
273
274
sage: from sage.misc.function_mangling import ArgumentFixer
275
sage: def do_something(a,b,c=3,*args,**kwargs):
276
... print a,b,c, args, kwargs
277
sage: AF = ArgumentFixer(do_something)
278
sage: A,K = AF.fix_to_pos(1,2,3,4,5,6,f=14,e=16); print A,K
279
(1, 2, 3, 4, 5, 6) (('e', 16), ('f', 14))
280
sage: do_something(*A,**dict(K))
281
1 2 3 (4, 5, 6) {'e': 16, 'f': 14}
282
sage: do_something(1,2,3,4,5,6,f=14,e=16)
283
1 2 3 (4, 5, 6) {'e': 16, 'f': 14}
284
"""
285
cdef tuple Args = args
286
cdef dict kwargs = kwds
287
cdef int lenargs = len(Args)
288
cdef int nargs = self._nargs
289
cdef tuple arg_names = self._arg_names
290
cdef dict defaults = self._defaults
291
# a shortpath for the case of no named arguments:
292
if not kwargs:
293
if lenargs>=nargs:
294
return args, ()
295
# we take the given arguments, plus the default arguments
296
return Args+self._default_tuple[-nargs+lenargs:],() #tuple(list(Args)+[defaults[k] for k in arg_names[lenargs:]]),()
297
cdef list Largs = list(Args)
298
cdef int i
299
for i from lenargs<=i<nargs:
300
#for name,val in defaults.iteritems():
301
# in addition to the positional arguments, we take the
302
# ones with default values, unless they are overridded by
303
# the named arguments.
304
name = arg_names[i]
305
if name in kwargs:
306
val = kwargs[name]
307
del kwargs[name]
308
else:
309
val = defaults[name]
310
Largs.append(val) #kwargs.pop(name,val))
311
cdef list Items = kwargs.items()
312
Items.sort()
313
return tuple(Largs), tuple(Items) #(k,kwargs_[k]) for k in sorted(kwargs_.keys()))
314
315
316
317