1 Invocations in classic Python:
2
3 f(1, 2, 3) # positional
4 f(1, 2) # positional with defaults
5 f(1, 2, c=3) # keywords
6 f(1, c=3) # keywords with defaults
7 f(1, 2, 3, 4) # extra positional arguments
8 f(1, 2, 3, d=4) # extra keyword arguments
9 f(1, 2, *args) # positional bundles (possibly with defaults)
10 f(1, 2, **kw) # keyword bundles (possibly with defaults)
11
12 Note that f is never fixed before run-time in Python.
13
14 Comparison to invocations in C:
15
16 f(1, 2, 3) # positional, f known at compile-time
17 f(1, 2, 3) # positional, f is appropriate function pointer
18 # ie. (*f)(A, B, C)
19
20 Least expensive cases (positional plus defaults):
21
22 f(1, 2, 3) # put arguments in frame
23 # if f is not known, add arguments vs. parameters check
24 f(1, 2) # to handle defaults, introduce default "filling" where
25 # not enough arguments are given
26 # if f is not known, this is obviously done at run-time
27
28 More expensive cases (keywords plus defaults):
29
30 f(1, 2, c=3) # prepare frame using parameter details
31 # (provided c is a known parameter)
32 # if f is not known, this is obviously done at run-time
33 f(1, c=3) # as with the previous case, with default "filling" done
34 # where not enough arguments are given
35 # if f is not known, this is obviously done at run-time
36 # but with all defaults copied in before keywords are
37 # assigned (since their positions and thus the positions
38 # of missing parameters cannot be known)
39
40 Awkward cases (extra arguments):
41
42 f(1, 2, 3, 4) # put arguments in frame
43 # if f is not known, add arguments vs. parameters check;
44 # to handle superfluous arguments, make a suitable object
45 # and fill it with all such arguments
46
47 Very awkward cases:
48
49 f(1, 2, 3, d=4) # extra keyword arguments
50 f(1, 2, *args) # positional bundles (possibly with defaults)
51 f(1, 2, **kw) # keyword bundles (possibly with defaults)
52
53 These cases require additional structures to be created, potentially at
54 run-time.
55
56 Invocations and argument lists:
57
58 General procedure:
59
60 1. Load target.
61 2. Put target in list (if appropriate).
62 3. Put context in list (if appropriate).
63 4. Put arguments in list.
64 5. Jump to target.
65
66 The target is needed for dynamic functions and methods, but is external to
67 any notion of arguments or locals.
68
69 Methods vs. functions:
70
71 f(obj, 1, 2) # f known as function at compile-time:
72 # f(obj, 1, 2)
73 # f known as C.m at compile-time:
74 # m(obj "assert isinstance(obj, C)", 1, 2)
75 # f not known at compile-time:
76 # f(<context>, obj, 1, 2) for instance-accessed methods
77 # f(obj, 1, 2) for class-accessed methods
78 # f(obj, 1, 2) for functions
79
80 (Could either have universal context usage even for functions, which would
81 ignore them, or attempt to remove contexts when functions are called.)
82
83 Argument lists for functions:
84
85 f(obj, 1, 2) # f known as function at compile-time
86
87 f -> f (context is null)
88 obj -> argument #1
89 1 -> argument #2
90 2 -> argument #3
91
92 Argument lists for methods:
93
94 f(obj, 1, 2) # f known as C.m at compile-time (context is C)
95
96 f -> C.m (context is class C)
97 obj -> argument #1 (must be tested against the context)
98 1 -> argument #2
99 2 -> argument #3
100
101 Argument lists for methods:
102
103 f(obj, 1, 2) # f known as C.m at compile-time (context is an instance)
104
105 f -> C.m
106 -> context is argument #1
107 obj -> argument #2
108 1 -> argument #3
109 2 -> argument #4
110
111 Argument lists for classes:
112
113 f(obj, 1, 2) # f known as C at compile-time
114
115 f -> instantiator of C
116 -> (argument #1 reserved for a new instance made by the instantiator)
117 obj -> argument #2
118 1 -> argument #3
119 2 -> argument #4
120
121 The new instance must be provided as the result of the call.
122
123 Argument lists for unknown callables:
124
125 f(obj, 1, 2) # f not known at compile-time
126
127 f -> f
128 -> load context for argument #1
129 obj -> argument #2
130 1 -> argument #3
131 2 -> argument #4
132
133 Then, check the context and shift the frame if necessary:
134
135 f is class: no change
136
137 <context> is class:
138 (<context>, obj, 1, 2) -> (obj, 1, 2)
139
140 <context> is instance: no change
141
142 Argument lists in instantiators:
143
144 f(obj, 1, 2) # f not known at compile-time
145
146 f -> C.__new__ (known and called at run-time)
147 -> load context for argument #1
148 obj -> argument #2
149 1 -> argument #3
150 2 -> argument #4
151
152 f(obj, 1, 2) # f known at compile-time
153
154 f -> C.__new__ (known and called at run-time)
155 -> argument #1 left blank
156 obj -> argument #2
157 1 -> argument #3
158 2 -> argument #4
159
160 Frame re-use in instantiators:
161
162 Need to call C.__init__(<instance>, obj, 1, 2), preferably with the existing
163 frame:
164
165 *** -> instance overwrites argument #1
166 obj -> argument #2
167 1 -> argument #3
168 2 -> argument #4
169
170 Then jump without switching frames.
171
172 If no context argument (or blank argument) were provided, a new frame would
173 need to be allocated and filled with a new instance and all remaining
174 arguments from the current frame.
175
176 Defaults for unknown callables:
177
178 f(obj) # f not known at compile-time
179
180 f -> f
181 -> load context for argument #1
182 obj -> argument #2
183
184 Then, check the number of arguments and the availability of defaults against
185 the details provided by the callable's structure.
186
187 Checking defaults for unknown callables:
188
189 Approach #1 - pre-fill defaults, add arguments, check frame
190
191 Approach #2 - add arguments, add defaults while checking frame
192
193 Dynamic functions:
194
195 def f(x):
196 def g(y=x): # dynamic: y depends on non-constant value
197 ...
198 def h(y=2): # static: y depends on constant value
199 ...
200
201 def f(x):
202 g = lambda y=x: ... # dynamic: y depends on non-constant value
203 h = lambda y=2: ... # static: y depends on constant value
204
205 Representation of dynamic functions:
206
207 f = lambda x, y=nonconst: ...
208
209 def f(x, y=nonconst):
210 ...
211
212 Defines object referencing function:
213
214 def <lambda>(x, y=nonconst): # references lambda x, ...
215 # Obtain nonconst from <lambda> for y, if necessary
216
217 def <f>(x, y=nonconst): # references f
218 # Obtain nonconst from <f> for y, if necessary
219
220 Dynamic default information and methods:
221
222 class C:
223 def f(self, y=nonconst):
224 ...
225
226 Defines additional state for the method:
227
228 class C:
229 def <f>(self, y=nonconst): # references C.f
230 # Obtain nonconst from <f> for y, if necessary
231
232 Non-constant information is only likely to be introduced to method defaults
233 within loops or if classes are permitted within functions.
234
235 Functions as methods:
236
237 def f(x, y, z): ...
238 class C:
239 m = f
240 c = C()
241 ...
242 f(obj, 1, 2) # no restrictions on obj
243 obj.m(1, 2) # f(obj, 1, 2)
244 C.m(obj, 1, 2) # f(obj "assert isinstance(obj, C)", 1, 2)
245
246 Context propagation:
247
248 fn = C.m # has context C
249 fn(obj, 1, 2) # non-instance context -> explicit context required
250 # must perform isinstance(obj, C)
251 fn = c.m # table entry for m on C -> replace context
252 # gives context c
253 fn(1, 2) # instance context -> no explicit context required
254 # context c inserted in call
255
256 Star parameters are a convenience:
257
258 max(1, 2, 3) # call to max(*args) where args == (1, 2, 3)
259 max((1, 2, 3)) # but why not just do this instead?
260
261 One motivation: avoid explicitly making sequences.
262 Opportunity: avoid expensive dynamic allocation of sequences?
263
264 Star parameters, approach #1:
265
266 Make a sequence to hold the extra arguments, either in the caller for known
267 callables or in the function itself.
268
269 Such a sequence would need allocating and its contents copying from the
270 stack.
271
272 Star parameters, approach #2:
273
274 Keep the extra arguments in the stack.
275
276 Access to the star parameter would need to consider assignment to other
277 things and "escape situations" for the parameter:
278
279 def f(*args):
280 return args # need to allocate and return the sequence
281
282 Access to elements of the extra argument sequence would behave slightly
283 differently to normal sequences, but this could be identified at
284 compile-time.
285
286 Star parameters, known callables and sequences, approach #1:
287
288 g(1, 2, 3, 4) # g known as function g(a, *args) at compile-time
289
290 g -> don't get any context information
291 1 -> argument #1
292 2 -> reference to sequence containing arguments #2, #3, #4
293
294 Star parameters, known callables and sequences, approach #2:
295
296 g(1, 2, 3, 4) # g known as function g(a, *args) at compile-time
297
298 g -> don't get any context information
299 1 -> argument #1
300 2 -> argument #2
301 3 -> argument #3
302 4 -> argument #4
303
304 Star parameters, unknown callables, both approach #1 and #2:
305
306 g(1, 2, 3, 4) # g not known at compile-time
307
308 g -> g
309 -> load context for argument #1
310 1 -> argument #2
311 2 -> argument #3
312 3 -> argument #4
313 4 -> argument #5
314
315 Then, check the context and shift the frame if necessary (described above).
316
317 If g has a star parameter - g(a, *args) - then...
318
319 Approach #1 - move arguments #3, #4, #5 (or shifted to #2, #3, #4) into a
320 sequence, adding a reference to the sequence in their place
321
322 Approach #2 - maintain special access rules to arguments #3, #4, #5 (perhaps
323 shifted to #2, #3, #4) as a C-like array
324
325 Tradeoffs for star parameter approaches:
326
327 Approach #1 - potentially costly at run-time as arguments need moving around,
328 but the arguments would behave normally in functions
329
330 Approach #2 - need to track usage of the star parameter and to possibly copy
331 its contents if assigned, as well as providing special access
332 mechanisms, but the invocation procedure would be simpler