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