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. In a low-level representation of a
68 program, the target will be held in either a register or some other
69 convenient storage (perhaps in the frame itself) so that defaults and other
70 state-related information can be accessed inside the invoked function.
71
72 Methods vs. functions:
73
74 f(obj, 1, 2) # f known as function at compile-time:
75 # f(obj, 1, 2)
76 # f known as C.m at compile-time:
77 # m(obj "assert isinstance(obj, C)", 1, 2)
78 # f not known at compile-time:
79 # f(<context>, obj, 1, 2) for instance-accessed methods
80 # f(obj, 1, 2) for class-accessed methods
81 # f(obj, 1, 2) for functions
82
83 (Could either have universal context usage even for functions, which would
84 ignore them, or attempt to remove contexts when functions are called.)
85
86 Argument lists for functions:
87
88 f(obj, 1, 2) # f known as function at compile-time
89
90 f -> f (context is null)
91 obj -> argument #1
92 1 -> argument #2
93 2 -> argument #3
94
95 Argument lists for methods:
96
97 f(obj, 1, 2) # f known as C.m at compile-time (context is C)
98
99 f -> C.m (context is class C)
100 obj -> argument #1 (must be tested against the context)
101 1 -> argument #2
102 2 -> argument #3
103
104 Argument lists for methods:
105
106 f(obj, 1, 2) # f known as C.m at compile-time (context is an instance)
107
108 f -> C.m
109 -> context is argument #1
110 obj -> argument #2
111 1 -> argument #3
112 2 -> argument #4
113
114 Argument lists for classes:
115
116 f(obj, 1, 2) # f known as C at compile-time
117
118 f -> instantiator of C
119 -> (argument #1 reserved for a new instance made by the instantiator)
120 obj -> argument #2
121 1 -> argument #3
122 2 -> argument #4
123
124 The new instance must be provided as the result of the call.
125
126 Argument lists for unknown callables:
127
128 f(obj, 1, 2) # f not known at compile-time
129
130 f -> f
131 -> load context for argument #1
132 obj -> argument #2
133 1 -> argument #3
134 2 -> argument #4
135
136 Then, check the context and shift the frame if necessary:
137
138 f is class: no change
139
140 <context> is class:
141 (<context>, obj, 1, 2) -> (obj, 1, 2)
142
143 <context> is instance: no change
144
145 Argument lists in instantiators:
146
147 f(obj, 1, 2) # f not known at compile-time
148
149 f -> C.__new__ (known and called at run-time)
150 -> load context for argument #1
151 obj -> argument #2
152 1 -> argument #3
153 2 -> argument #4
154
155 f(obj, 1, 2) # f known at compile-time
156
157 f -> C.__new__ (known and called at run-time)
158 -> argument #1 left blank
159 obj -> argument #2
160 1 -> argument #3
161 2 -> argument #4
162
163 Frame re-use in instantiators:
164
165 Need to call C.__init__(<instance>, obj, 1, 2), preferably with the existing
166 frame:
167
168 *** -> instance overwrites argument #1
169 obj -> argument #2
170 1 -> argument #3
171 2 -> argument #4
172
173 Then jump without switching frames.
174
175 If no context argument (or blank argument) were provided, a new frame would
176 need to be allocated and filled with a new instance and all remaining
177 arguments from the current frame.
178
179 Defaults for unknown callables:
180
181 f(obj) # f not known at compile-time
182
183 f -> f
184 -> load context for argument #1
185 obj -> argument #2
186
187 Then, check the number of arguments and the availability of defaults against
188 the details provided by the callable's structure.
189
190 Checking defaults for unknown callables:
191
192 Approach #1 - pre-fill defaults, add arguments, check frame
193
194 Approach #2 - add arguments, add defaults while checking frame
195
196 Dynamic functions:
197
198 def f(x):
199 def g(y=x): # dynamic: y depends on non-constant value
200 ...
201 def h(y=2): # static: y depends on constant value
202 ...
203
204 def f(x):
205 g = lambda y=x: ... # dynamic: y depends on non-constant value
206 h = lambda y=2: ... # static: y depends on constant value
207
208 Representation of dynamic functions:
209
210 f = lambda x, y=nonconst: ...
211
212 def f(x, y=nonconst):
213 ...
214
215 Defines object referencing function:
216
217 def <lambda>(x, y=nonconst): # references lambda x, ...
218 # Obtain nonconst from <lambda> for y, if necessary
219
220 def <f>(x, y=nonconst): # references f
221 # Obtain nonconst from <f> for y, if necessary
222
223 Dynamic default information and methods:
224
225 class C:
226 def f(self, y=nonconst):
227 ...
228
229 Defines additional state for the method:
230
231 class C:
232 def <f>(self, y=nonconst): # references C.f
233 # Obtain nonconst from <f> for y, if necessary
234
235 Non-constant information is only likely to be introduced to method defaults
236 within loops or if classes are permitted within functions.
237
238 Functions as methods:
239
240 def f(x, y, z): ...
241 class C:
242 m = f
243 c = C()
244 ...
245 f(obj, 1, 2) # no restrictions on obj
246 obj.m(1, 2) # f(obj, 1, 2)
247 C.m(obj, 1, 2) # f(obj "assert isinstance(obj, C)", 1, 2)
248
249 Context propagation:
250
251 fn = C.m # has context C
252 fn(obj, 1, 2) # non-instance context -> explicit context required
253 # must perform isinstance(obj, C)
254 fn = c.m # table entry for m on C -> replace context
255 # gives context c
256 fn(1, 2) # instance context -> no explicit context required
257 # context c inserted in call
258
259 Star parameters are a convenience:
260
261 max(1, 2, 3) # call to max(*args) where args == (1, 2, 3)
262 max((1, 2, 3)) # but why not just do this instead?
263
264 One motivation: avoid explicitly making sequences.
265 Opportunity: avoid expensive dynamic allocation of sequences?
266
267 Star parameters, approach #1:
268
269 Make a sequence to hold the extra arguments, either in the caller for known
270 callables or in the function itself.
271
272 Such a sequence would need allocating and its contents copying from the
273 stack.
274
275 Star parameters, approach #2:
276
277 Keep the extra arguments in the stack.
278
279 Access to the star parameter would need to consider assignment to other
280 things and "escape situations" for the parameter:
281
282 def f(*args):
283 return args # need to allocate and return the sequence
284
285 Access to elements of the extra argument sequence would behave slightly
286 differently to normal sequences, but this could be identified at
287 compile-time.
288
289 Star parameters, known callables and sequences, approach #1:
290
291 g(1, 2, 3, 4) # g known as function g(a, *args) at compile-time
292
293 g -> don't get any context information
294 1 -> argument #1
295 2 -> reference to sequence containing arguments #2, #3, #4
296
297 Star parameters, known callables and sequences, approach #2:
298
299 g(1, 2, 3, 4) # g known as function g(a, *args) at compile-time
300
301 g -> don't get any context information
302 1 -> argument #1
303 2 -> argument #2
304 3 -> argument #3
305 4 -> argument #4
306
307 Star parameters, unknown callables, both approach #1 and #2:
308
309 g(1, 2, 3, 4) # g not known at compile-time
310
311 g -> g
312 -> load context for argument #1
313 1 -> argument #2
314 2 -> argument #3
315 3 -> argument #4
316 4 -> argument #5
317
318 Then, check the context and shift the frame if necessary (described above).
319
320 If g has a star parameter - g(a, *args) - then...
321
322 Approach #1 - move arguments #3, #4, #5 (or shifted to #2, #3, #4) into a
323 sequence, adding a reference to the sequence in their place
324
325 Approach #2 - maintain special access rules to arguments #3, #4, #5 (perhaps
326 shifted to #2, #3, #4) as a C-like array
327
328 Tradeoffs for star parameter approaches:
329
330 Approach #1 - potentially costly at run-time as arguments need moving around,
331 but the arguments would behave normally in functions
332
333 Approach #2 - need to track usage of the star parameter and to possibly copy
334 its contents if assigned, as well as providing special access
335 mechanisms, but the invocation procedure would be simpler